<script setup lang="ts">
import { computed } from "vue"
import type { Composition } from "@/data/blueprint"

const props = defineProps<{ composition: Composition }>()

const PAL = {
  boxFill: "#103763",
  boxStroke: "#3d5f85",
  ink: "#dbe9f7",
  inkFaint: "#4f7099",
  cyan: "#8fd0ff",
  amber: "#ffb84d",
}

const W = 640
const lx = 24
const lw = 170
const rx = 446
const rw = 170
const bh = 34
const top = 16
const lstep = 40
const rstep = 52

const capY = (i: number) => top + i * lstep + bh / 2
const funcY = (i: number) => top + i * rstep + bh / 2

// viewBox height must fit whichever column is taller — the functions column
// (rstep = 52) usually outruns the capabilities column (lstep = 40) — plus room
// for the bottom labels. Sizing off caps.length alone clipped the last row.
const H = computed(() => {
  const leftBottom = top + (props.composition.caps.length - 1) * lstep + bh
  const rightBottom = top + (props.composition.funcs.length - 1) * rstep + bh
  return Math.max(leftBottom, rightBottom) + 26
})

const capIndex = computed(
  () =>
    Object.fromEntries(props.composition.caps.map((c, i) => [c.id, i])) as Record<string, number>,
)
const funcIndex = computed(
  () => Object.fromEntries(props.composition.funcs.map((f, i) => [f, i])) as Record<string, number>,
)

const leftBoxes = computed(() =>
  props.composition.caps.map((c, i) => ({ ...c, x: lx, y: capY(i) - bh / 2, w: lw, h: bh })),
)
const rightBoxes = computed(() =>
  props.composition.funcs.map((f, i) => ({ label: f, x: rx, y: funcY(i) - bh / 2, w: rw, h: bh })),
)

const edges = computed(() =>
  props.composition.links.map(([a, b]) => {
    const y1 = capY(capIndex.value[a])
    const y2 = funcY(funcIndex.value[b])
    const x1 = lx + lw
    const x2 = rx
    const mx = (x1 + x2) / 2
    const color = a === "core" ? PAL.inkFaint : PAL.cyan
    return {
      key: `${a}-${b}`,
      d: `M${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`,
      color,
      x2,
      y2,
    }
  }),
)
</script>

<template>
  <svg :viewBox="`0 0 ${W} ${H}`" role="img" aria-label="Composition map" class="schematic">
    <path
      v-for="e in edges"
      :key="e.key"
      :d="e.d"
      fill="none"
      stroke-width="1.4"
      :stroke="e.color"
      opacity="0.55"
    />
    <circle v-for="e in edges" :key="`c-${e.key}`" :cx="e.x2" :cy="e.y2" r="2.4" :fill="e.color" />

    <g v-for="b in leftBoxes" :key="b.id">
      <rect
        :x="b.x"
        :y="b.y"
        :width="b.w"
        :height="b.h"
        rx="2"
        :fill="PAL.boxFill"
        :stroke="b.core ? PAL.amber : PAL.boxStroke"
        stroke-width="1.5"
      />
      <text :x="b.x + 12" :y="b.y + 15" :fill="PAL.ink" font-size="12">{{ b.label }}</text>
      <text :x="b.x + 12" :y="b.y + 27" :fill="PAL.inkFaint" font-size="9.5">{{ b.sub }}</text>
    </g>

    <g v-for="b in rightBoxes" :key="b.label">
      <rect
        :x="b.x"
        :y="b.y"
        :width="b.w"
        :height="b.h"
        rx="2"
        :fill="PAL.boxFill"
        :stroke="PAL.amber"
        stroke-width="1.5"
      />
      <text :x="b.x + 12" :y="b.y + 21" :fill="PAL.amber" font-size="12">{{ b.label }}</text>
    </g>

    <text :x="lx" :y="H - 4" :fill="PAL.inkFaint" font-size="10" letter-spacing="1.5">
      CAPABILITIES (compose onto host)
    </text>
    <text :x="rx" :y="H - 4" :fill="PAL.inkFaint" font-size="10" letter-spacing="1.5">
      FUNCTIONS
    </text>
  </svg>
</template>

<style scoped>
.schematic {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 auto;
  font-family: var(--font-mono);
}
</style>
