// Red-yarn threads for the investigation board's "Connect the Dots" mode: pair state
// persisted per case in localStorage, plus the SVG layer that draws the yarn. (Named
// ThreadPair, not Thread — Evidence.thread already means phone messages in types.ts.)
import { useState } from 'preact/hooks'

export type ThreadPair = [string, string]

const keyFor = (caseId: string) => `cz-threads-${caseId}`
export const pairKey = (a: string, b: string): string => (a < b ? `${a}|${b}` : `${b}|${a}`)
const canon = (a: string, b: string): ThreadPair => (a < b ? [a, b] : [b, a])

function loadPairs(caseId: string, valid: Set<string>): ThreadPair[] {
  try {
    const raw = localStorage.getItem(keyFor(caseId))
    if (!raw) return []
    const data = JSON.parse(raw) as { v?: number; pairs?: unknown }
    if (data?.v !== 1 || !Array.isArray(data.pairs)) return []
    const seen = new Set<string>()
    const out: ThreadPair[] = []
    for (const p of data.pairs) {
      if (!Array.isArray(p) || typeof p[0] !== 'string' || typeof p[1] !== 'string') continue
      const [a, b] = canon(p[0], p[1])
      if (a === b || !valid.has(a) || !valid.has(b) || seen.has(pairKey(a, b))) continue
      seen.add(pairKey(a, b))
      out.push([a, b])
    }
    return out
  } catch {
    return []
  }
}

function savePairs(caseId: string, pairs: ThreadPair[]): void {
  try {
    localStorage.setItem(keyFor(caseId), JSON.stringify({ v: 1, pairs }))
  } catch {
    /* ignore */
  }
}

export interface Threads {
  pairs: ThreadPair[]
  /** Add the pair — or remove it if it already exists. */
  toggle: (a: string, b: string) => 'added' | 'removed'
  remove: (a: string, b: string) => void
  clear: () => void
}

/** Thread pairs for one case, restored from and written through to localStorage so the
 *  yarn survives screen switches (the board unmounts on nav) and full reloads. */
export function useThreads(caseId: string, validIds: string[]): Threads {
  const [pairs, setPairs] = useState<ThreadPair[]>(() => loadPairs(caseId, new Set(validIds)))
  const commit = (next: ThreadPair[]) => {
    savePairs(caseId, next)
    setPairs(next)
  }
  return {
    pairs,
    toggle: (a, b) => {
      const k = pairKey(a, b)
      const without = pairs.filter(([x, y]) => pairKey(x, y) !== k)
      const removed = without.length < pairs.length
      commit(removed ? without : [...pairs, canon(a, b)])
      return removed ? 'removed' : 'added'
    },
    remove: (a, b) => {
      const k = pairKey(a, b)
      commit(pairs.filter(([x, y]) => pairKey(x, y) !== k))
    },
    clear: () => commit([]),
  }
}

/** Yarn path between two pushpins: a quadratic curve with a little gravity sag. */
export function threadD(x1: number, y1: number, x2: number, y2: number): string {
  const dist = Math.hypot(x2 - x1, y2 - y1)
  const sag = Math.min(48, 12 + dist * 0.14)
  return `M ${x1} ${y1} Q ${(x1 + x2) / 2} ${(y1 + y2) / 2 + sag} ${x2} ${y2}`
}

interface ThreadLayerProps {
  pairs: ThreadPair[]
  /** Pushpin position for a card id, in board coordinates (null until laid out). */
  anchor: (id: string) => { x: number; y: number } | null
  w: number
  h: number
  /** Connect mode: threads grow an invisible fat hit-stroke so a tap can cut them. */
  connect: boolean
  /** Live dashed thread from the armed pin to the pointer (desktop juice). */
  ghost?: { from: { x: number; y: number }; to: { x: number; y: number } } | null
  onRemove: (a: string, b: string) => void
}

export function ThreadLayer({ pairs, anchor, w, h, connect, ghost, onRemove }: ThreadLayerProps) {
  return (
    <svg class="thread-layer" viewBox={`0 0 ${w} ${h}`} width={w} height={h} aria-hidden="true">
      {pairs.map(([a, b]) => {
        const pa = anchor(a)
        const pb = anchor(b)
        if (!pa || !pb) return null
        const d = threadD(pa.x, pa.y, pb.x, pb.y)
        return (
          // Keyed by pair so the draw-in animation runs once on creation, not on drags.
          <g key={pairKey(a, b)}>
            <path class="thread thread--under" d={d} pathLength={1} />
            <path class="thread thread--draw" d={d} pathLength={1} />
            {connect && (
              <path
                class="thread-hit"
                d={d}
                onClick={(e) => {
                  e.stopPropagation()
                  onRemove(a, b)
                }}
              />
            )}
            <circle class="thread-knot" cx={pa.x} cy={pa.y} r={2.5} />
            <circle class="thread-knot" cx={pb.x} cy={pb.y} r={2.5} />
          </g>
        )
      })}
      {ghost && <path class="thread thread--ghost" d={threadD(ghost.from.x, ghost.from.y, ghost.to.x, ghost.to.y)} />}
    </svg>
  )
}
