<script setup lang="ts">
/**
 * The cross-dock timeline stage. One shared time axis (x = days), two lanes:
 * inbound purchase orders on top, outbound sales orders below, a dock strip in
 * between where the allocation ribbons cross.
 *
 * Pointer interaction follows labeldesigner/Stage.vue: screen→model conversion
 * via getScreenCTM().inverse(), non-reactive drag bookkeeping, window-level
 * pointermove/pointerup. Client-only (mounted inside <ClientOnly>).
 *
 * Drag semantics (resolved at pointerup):
 *  - dropped over a truck in the OPPOSITE lane → open the allocation dialog
 *  - dropped on another day in the OWN lane   → write DatePromised
 *  - barely moved                              → select (side panel)
 */
import type { CdOrder } from '~/composables/useCrossdockBoard'

const board = inject('cdBoard') as ReturnType<typeof useCrossdockBoard>

// ---- geometry constants (SVG user units == px, no viewBox scaling) ----
const DAY_W = 116
const TRUCK_W = 104
const TRUCK_H = 64
const ROW_H = 86
const HEADER_H = 46
const DOCK_H = 116
const PAD = 10

const svgRef = ref<SVGSVGElement | null>(null)

// ---- day strip ----
const localMidnight = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate())
const lang = ref('de-DE')
onMounted(() => {
  try { lang.value = localStorage.getItem('logship_lang') || 'de-DE' } catch {}
})
const dayFmt = computed(() => new Intl.DateTimeFormat(lang.value, { weekday: 'short' }))
const daysArr = computed(() => {
  const out: Array<{ i: number; label: string; dateLabel: string; isWeekend: boolean; isToday: boolean }> = []
  const today = localMidnight(new Date()).getTime()
  for (let i = 0; i < board.days.value; i++) {
    const d = board.dayToDate(i)
    out.push({
      i,
      label: dayFmt.value.format(d),
      dateLabel: `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.`,
      isWeekend: d.getDay() === 0 || d.getDay() === 6,
      isToday: d.getTime() === today
    })
  }
  return out
})

// ---- lane layout: stack trucks per day ----
interface LayoutEntry { order: CdOrder; lane: 'in' | 'out'; day: number; row: number; x: number; y: number }

const stackLane = (orders: CdOrder[], lane: 'in' | 'out'): { entries: LayoutEntry[]; rows: number } => {
  const perDay: Record<number, number> = {}
  const entries: LayoutEntry[] = []
  let rows = 1
  for (const o of orders) {
    let day = board.dayIndex(o.datePromised)
    day = Math.max(0, Math.min(board.days.value - 1, day))
    const row = perDay[day] ?? 0
    perDay[day] = row + 1
    rows = Math.max(rows, row + 1)
    entries.push({ order: o, lane, day, row, x: day * DAY_W + (DAY_W - TRUCK_W) / 2, y: 0 })
  }
  return { entries, rows }
}

const inboundStack = computed(() => stackLane(board.inbound.value, 'in'))
const outboundStack = computed(() => stackLane(board.outbound.value, 'out'))
const inRows = computed(() => Math.max(2, inboundStack.value.rows))
const outRows = computed(() => Math.max(2, outboundStack.value.rows))

const inY0 = HEADER_H + PAD
const dockY = computed(() => inY0 + inRows.value * ROW_H + 4)
const outY0 = computed(() => dockY.value + DOCK_H + 4)
const totalW = computed(() => board.days.value * DAY_W)
const totalH = computed(() => outY0.value + outRows.value * ROW_H + 24)

const inboundLayout = computed<LayoutEntry[]>(() =>
  inboundStack.value.entries.map(e => ({ ...e, y: inY0 + e.row * ROW_H + 6 }))
)
const outboundLayout = computed<LayoutEntry[]>(() =>
  outboundStack.value.entries.map(e => ({ ...e, y: outY0.value + e.row * ROW_H + 6 }))
)
const inLayoutById = computed(() => {
  const m: Record<number, LayoutEntry> = {}
  for (const e of inboundLayout.value) m[e.order.id] = e
  return m
})
const outLayoutById = computed(() => {
  const m: Record<number, LayoutEntry> = {}
  for (const e of outboundLayout.value) m[e.order.id] = e
  return m
})

// ---- ribbons (allocation links between visible trucks) ----
const cubicPoint = (t: number, p0: number, p1: number, p2: number, p3: number) => {
  const u = 1 - t
  return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3
}
const ribbons = computed(() => {
  return board.visibleLinks.value.map(link => {
    const a = inLayoutById.value[link.poOrderId]
    const b = outLayoutById.value[link.soOrderId]
    if (!a || !b) return null
    const x0 = a.x + TRUCK_W / 2
    const y0 = a.y + TRUCK_H - 3
    const x3 = b.x + TRUCK_W / 2
    const y3 = b.y - 2
    const c1y = y0 + (y3 - y0) * 0.45
    const c2y = y0 + (y3 - y0) * 0.55
    const selected = board.selection.value?.type === 'link'
      && board.selection.value?.poOrderId === link.poOrderId
      && board.selection.value?.soOrderId === link.soOrderId
    return {
      link,
      d: `M ${x0} ${y0} C ${x0} ${c1y}, ${x3} ${c2y}, ${x3} ${y3}`,
      mx: cubicPoint(0.5, x0, x0, x3, x3),
      my: cubicPoint(0.5, y0, c1y, c2y, y3),
      selected
    }
  }).filter(Boolean) as any[]
})

const linkedInbound = computed(() => new Set(board.links.value.map(l => l.poOrderId)))
const linkedOutbound = computed(() => new Set(board.links.value.map(l => l.soOrderId)))

// ---- pointer interaction ----
const localPoint = (e: PointerEvent) => {
  const svg = svgRef.value
  const ctm = svg?.getScreenCTM()
  if (!svg || !ctm) return { x: 0, y: 0 }
  const p = new DOMPoint(e.clientX, e.clientY).matrixTransform(ctm.inverse())
  return { x: p.x, y: p.y }
}

// non-reactive drag bookkeeping
let dragEntry: LayoutEntry | null = null
let pointerStart = { x: 0, y: 0 }
let movedFar = false

// reactive ghost (drives the dragged truck + day highlight)
const ghost = ref<{ x: number; y: number; day: number } | null>(null)

const onTruckDown = (e: PointerEvent, entry: LayoutEntry) => {
  dragEntry = entry
  pointerStart = localPoint(e)
  movedFar = false
  ghost.value = { x: entry.x, y: entry.y, day: entry.day }
  board.draggingId.value = entry.order.id
}

const onMove = (e: PointerEvent) => {
  if (!dragEntry || !ghost.value) return
  const p = localPoint(e)
  const dx = p.x - pointerStart.x
  const dy = p.y - pointerStart.y
  if (Math.abs(dx) > 4 || Math.abs(dy) > 4) movedFar = true
  ghost.value.x = dragEntry.x + dx
  ghost.value.y = dragEntry.y + dy
  ghost.value.day = Math.max(0, Math.min(board.days.value - 1, Math.round((ghost.value.x + TRUCK_W / 2 - DAY_W / 2) / DAY_W)))

  // hit test against the opposite lane for link creation
  const opposite = dragEntry.lane === 'in' ? outboundLayout.value : inboundLayout.value
  let hit: number | null = null
  for (const t of opposite) {
    if (p.x >= t.x - 4 && p.x <= t.x + TRUCK_W + 4 && p.y >= t.y - 6 && p.y <= t.y + TRUCK_H + 6) {
      hit = t.order.id
      break
    }
  }
  board.dropTargetId.value = hit
}

const onUp = () => {
  if (!dragEntry || !ghost.value) return
  const entry = dragEntry
  const target = board.dropTargetId.value
  const day = ghost.value.day

  dragEntry = null
  ghost.value = null
  board.draggingId.value = null
  board.dropTargetId.value = null

  if (!movedFar) {
    board.selection.value = { type: 'order', lane: entry.lane, id: entry.order.id }
    return
  }
  if (target) {
    const poOrderId = entry.lane === 'in' ? entry.order.id : target
    const soOrderId = entry.lane === 'in' ? target : entry.order.id
    board.openAllocation(poOrderId, soOrderId)
    return
  }
  if (day !== entry.day) {
    board.moveOrder(entry.lane, entry.order.id, day)
  }
}

const onBackgroundDown = () => { board.selection.value = null }

const selectLink = (link: any) => {
  board.selection.value = { type: 'link', poOrderId: link.poOrderId, soOrderId: link.soOrderId }
}

onMounted(() => {
  window.addEventListener('pointermove', onMove)
  window.addEventListener('pointerup', onUp)
})
onBeforeUnmount(() => {
  window.removeEventListener('pointermove', onMove)
  window.removeEventListener('pointerup', onUp)
})

const isSelected = (lane: 'in' | 'out', id: number) =>
  board.selection.value?.type === 'order' && board.selection.value?.lane === lane && board.selection.value?.id === id

const truckState = (lane: 'in' | 'out', o: CdOrder): string | null =>
  lane === 'in' ? (o.received ? 'arrived' : null) : board.soLinkState(o.id)
</script>

<template>
  <div class="cdb">
    <svg
      ref="svgRef"
      :width="totalW"
      :height="totalH"
      class="cdb__svg"
      @pointerdown.self="onBackgroundDown"
    >
      <defs>
        <linearGradient id="cdGradBoxIn" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#42628c" />
          <stop offset="1" stop-color="#2c4661" />
        </linearGradient>
        <linearGradient id="cdGradBoxOut" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#6a5da8" />
          <stop offset="1" stop-color="#4a3f7d" />
        </linearGradient>
        <linearGradient id="cdGradBoxGreen" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#3d9e66" />
          <stop offset="1" stop-color="#2a7047" />
        </linearGradient>
        <linearGradient id="cdGradGlass" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0" stop-color="#cfe8ff" />
          <stop offset="1" stop-color="#7fb2d9" />
        </linearGradient>
        <linearGradient id="cdGradDock" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="rgba(120, 134, 156, 0.10)" />
          <stop offset="0.5" stop-color="rgba(120, 134, 156, 0.22)" />
          <stop offset="1" stop-color="rgba(120, 134, 156, 0.10)" />
        </linearGradient>
        <filter id="cdSoftShadow" x="-20%" y="-20%" width="140%" height="150%">
          <feDropShadow dx="0" dy="1.2" stdDeviation="1.1" flood-color="#0a0e14" flood-opacity="0.28" />
        </filter>
      </defs>

      <!-- day columns: weekend tint + grid + header -->
      <g @pointerdown="onBackgroundDown">
        <template v-for="d in daysArr" :key="'day' + d.i">
          <rect
            v-if="d.isWeekend"
            :x="d.i * DAY_W" :y="HEADER_H" :width="DAY_W" :height="totalH - HEADER_H"
            class="cdb__weekend"
          />
          <rect
            v-if="ghost && ghost.day === d.i"
            :x="d.i * DAY_W" :y="HEADER_H" :width="DAY_W" :height="totalH - HEADER_H"
            class="cdb__dayTarget"
          />
          <line :x1="d.i * DAY_W" :y1="HEADER_H" :x2="d.i * DAY_W" :y2="totalH" class="cdb__grid" />
          <!-- header cell -->
          <g :class="{ 'cdb__headToday': d.isToday }">
            <rect :x="d.i * DAY_W" y="0" :width="DAY_W" :height="HEADER_H" class="cdb__headCell" />
            <text :x="d.i * DAY_W + DAY_W / 2" y="19" text-anchor="middle" class="cdb__headDow">{{ d.label }}</text>
            <text :x="d.i * DAY_W + DAY_W / 2" y="36" text-anchor="middle" class="cdb__headDate">{{ d.dateLabel }}</text>
          </g>
        </template>
        <!-- today marker -->
        <template v-for="d in daysArr" :key="'today' + d.i">
          <line
            v-if="d.isToday"
            :x1="d.i * DAY_W + DAY_W / 2" :y1="HEADER_H" :x2="d.i * DAY_W + DAY_W / 2" :y2="totalH"
            class="cdb__todayLine"
          />
        </template>
      </g>

      <!-- lane labels -->
      <g pointer-events="none">
        <text x="10" :y="inY0 + 2" class="cdb__laneLabel">▼ WARENEINGANG — Bestellungen (Lieferanten)</text>
        <text x="10" :y="outY0 + 2" class="cdb__laneLabel">▲ WARENAUSGANG — Aufträge (Kunden)</text>
      </g>

      <!-- dock strip -->
      <g pointer-events="none">
        <rect x="0" :y="dockY" :width="totalW" :height="DOCK_H" fill="url(#cdGradDock)" />
        <line x1="0" :y1="dockY" :x2="totalW" :y2="dockY" class="cdb__dockEdge" />
        <line x1="0" :y1="dockY + DOCK_H" :x2="totalW" :y2="dockY + DOCK_H" class="cdb__dockEdge" />
        <!-- repeating dock doors -->
        <template v-for="d in daysArr" :key="'dock' + d.i">
          <g :transform="`translate(${d.i * DAY_W + DAY_W / 2 - 14}, ${dockY + DOCK_H / 2 - 13})`" class="cdb__dockDoor">
            <path d="M0 26 V8 L14 0 L28 8 V26" fill="none" stroke-width="1.6" />
            <rect x="7" y="14" width="14" height="12" rx="1" stroke-width="1.4" fill="none" />
            <line x1="7" y1="18" x2="21" y2="18" stroke-width="1" />
            <line x1="7" y1="22" x2="21" y2="22" stroke-width="1" />
          </g>
        </template>
        <text :x="totalW / 2" :y="dockY + DOCK_H / 2 + 2" text-anchor="middle" class="cdb__dockLabel">CROSS-DOCK</text>
      </g>

      <!-- ribbons -->
      <g>
        <g
          v-for="r in ribbons"
          :key="'rib' + r.link.poOrderId + '-' + r.link.soOrderId"
          class="cdb__ribbonG"
          @pointerdown.stop
          @click.stop="selectLink(r.link)"
        >
          <!-- fat invisible hit path -->
          <path :d="r.d" fill="none" stroke="transparent" stroke-width="14" />
          <path
            :d="r.d"
            fill="none"
            class="cdb__ribbon"
            :class="{ 'cdb__ribbon--arrived': r.link.arrived, 'cdb__ribbon--selected': r.selected }"
          />
          <!-- coverage badge -->
          <g :transform="`translate(${r.mx}, ${r.my})`">
            <rect x="-21" y="-10" width="42" height="20" rx="10"
                  class="cdb__badge" :class="{ 'cdb__badge--arrived': r.link.arrived, 'cdb__badge--selected': r.selected }" />
            <text y="4" text-anchor="middle" class="cdb__badgeText">{{ r.link.coveragePct }}%</text>
          </g>
        </g>
      </g>

      <!-- inbound trucks -->
      <g
        v-for="e in inboundLayout"
        :key="'in' + e.order.id"
        v-show="!(ghost && board.draggingId.value === e.order.id)"
        :transform="`translate(${e.x}, ${e.y})`"
        @pointerdown.stop.prevent="onTruckDown($event, e)"
      >
        <FulfillmentCrossdockTruck
          :order="e.order"
          lane="in"
          :state="truckState('in', e.order)"
          :selected="isSelected('in', e.order.id)"
          :drop-target="board.dropTargetId.value === e.order.id"
          :linked="linkedInbound.has(e.order.id)"
        />
      </g>

      <!-- outbound trucks -->
      <g
        v-for="e in outboundLayout"
        :key="'out' + e.order.id"
        v-show="!(ghost && board.draggingId.value === e.order.id)"
        :transform="`translate(${e.x}, ${e.y})`"
        @pointerdown.stop.prevent="onTruckDown($event, e)"
      >
        <FulfillmentCrossdockTruck
          :order="e.order"
          lane="out"
          :state="truckState('out', e.order)"
          :selected="isSelected('out', e.order.id)"
          :drop-target="board.dropTargetId.value === e.order.id"
          :linked="linkedOutbound.has(e.order.id)"
        />
      </g>

      <!-- drag ghost: the moving truck, rendered on top -->
      <g
        v-if="ghost && board.draggingId.value"
        :transform="`translate(${ghost.x}, ${ghost.y})`"
        pointer-events="none"
      >
        <FulfillmentCrossdockTruck
          v-if="inLayoutById[board.draggingId.value]"
          :order="inLayoutById[board.draggingId.value].order"
          lane="in"
          :state="truckState('in', inLayoutById[board.draggingId.value].order)"
          :dragging="true"
        />
        <FulfillmentCrossdockTruck
          v-else-if="outLayoutById[board.draggingId.value]"
          :order="outLayoutById[board.draggingId.value].order"
          lane="out"
          :state="truckState('out', outLayoutById[board.draggingId.value].order)"
          :dragging="true"
        />
      </g>

      <!-- empty-lane hints -->
      <text
        v-if="board.loadedOnce.value && !board.inbound.value.length"
        :x="totalW / 2" :y="inY0 + ROW_H" text-anchor="middle" class="cdb__empty"
      >Keine Bestellungen mit Liefertermin in diesem Zeitraum</text>
      <text
        v-if="board.loadedOnce.value && !board.outbound.value.length"
        :x="totalW / 2" :y="outY0 + ROW_H" text-anchor="middle" class="cdb__empty"
      >Keine offenen Aufträge mit Termin in diesem Zeitraum</text>
    </svg>
  </div>
</template>

<style scoped>
.cdb {
  overflow: auto;
  border: 1px solid var(--theme-border, #e3e5ea);
  border-radius: 10px;
  background: var(--theme-bg-primary, #ffffff);
}
.cdb__svg {
  display: block;
  touch-action: none;
  user-select: none;
}

.cdb__headCell {
  fill: var(--theme-bg-secondary, #f6f7f9);
  stroke: var(--theme-border, #e3e5ea);
  stroke-width: 0.6;
}
.cdb__headToday .cdb__headCell {
  fill: rgba(74, 95, 193, 0.12);
}
.cdb__headDow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.4px;
  fill: var(--theme-text-primary, #2c3140);
  text-transform: uppercase;
}
.cdb__headDate {
  font-size: 11px;
  fill: var(--theme-text-secondary, #7a8194);
}
.cdb__grid {
  stroke: var(--theme-border, #e8eaef);
  stroke-width: 0.6;
}
.cdb__weekend {
  fill: rgba(122, 129, 148, 0.07);
}
.cdb__dayTarget {
  fill: rgba(74, 95, 193, 0.10);
  stroke: rgba(74, 95, 193, 0.45);
  stroke-width: 1;
  stroke-dasharray: 5 4;
}
.cdb__todayLine {
  stroke: #4a5fc1;
  stroke-width: 1.6;
  stroke-dasharray: 2 4;
  stroke-linecap: round;
  opacity: 0.8;
}

.cdb__laneLabel {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 1.1px;
  fill: var(--theme-text-secondary, #7a8194);
}
.cdb__dockEdge {
  stroke: var(--theme-border, #d5d9e0);
  stroke-width: 1.2;
  stroke-dasharray: 10 6;
}
.cdb__dockDoor {
  stroke: rgba(122, 129, 148, 0.5);
}
.cdb__dockLabel {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 6px;
  fill: rgba(122, 129, 148, 0.4);
}

.cdb__ribbonG {
  cursor: pointer;
}
.cdb__ribbon {
  stroke: #8b93a7;
  stroke-width: 2.4;
  opacity: 0.85;
  transition: stroke-width 0.15s ease;
  /* draw-in effect; dash length must exceed the longest possible path
     (a ribbon spanning the whole visible window) or a gap appears */
  stroke-dasharray: 4000;
  stroke-dashoffset: 0;
  animation: cdRibbonDraw 0.7s ease-out;
}
@keyframes cdRibbonDraw {
  from { stroke-dashoffset: 4000; }
  to   { stroke-dashoffset: 0; }
}
.cdb__ribbon--arrived {
  stroke: #22a559;
}
.cdb__ribbon--selected,
.cdb__ribbonG:hover .cdb__ribbon {
  stroke-width: 4;
  opacity: 1;
}
.cdb__badge {
  fill: #8b93a7;
  stroke: var(--theme-bg-primary, #ffffff);
  stroke-width: 1.6;
}
.cdb__badge--arrived {
  fill: #22a559;
}
.cdb__badge--selected {
  fill: #4a5fc1;
}
.cdb__badgeText {
  font-size: 10.5px;
  font-weight: 700;
  fill: #ffffff;
  pointer-events: none;
}

.cdb__empty {
  font-size: 13px;
  fill: var(--theme-text-secondary, #9aa1b3);
}

@media (prefers-reduced-motion: reduce) {
  .cdb__ribbon { animation: none !important; }
}
</style>
