import { useMemo } from 'react';
import { fmt, useT } from '@/lib/i18n';
import { parseEvents } from '@/lib/matchEvents';
import type { LineupTeam, MatchLineup, MatchLiveEvent, PitchPlayer } from '@/lib/types';

/**
 * Both starting XIs on one vertical pitch — home defends the top goal, away
 * the bottom. Players sit at FotMob's normalized formation coordinates and
 * carry shirt number, live rating and goal markers. When a player is
 * substituted, the replacement takes over the slot (▲ minute) and the
 * outgoing player moves to the "subbed off" strip below, so the pitch always
 * shows the current XI while the page's live refetch keeps the data fresh.
 */

const W = 68;
const H = 105;

/** Diacritic/case/punctuation-insensitive name comparison (token sets), so
 *  ESPN's "Tomás Chory" pairs with FotMob's "Tomas Chorý". */
const norm = (s: string) =>
  s.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-'.]/g, ' ').replace(/\s+/g, ' ').trim();
function nameMatch(a: string, b: string): boolean {
  const na = norm(a);
  const nb = norm(b);
  if (!na || !nb) return false;
  if (na === nb) return true;
  const ta = na.split(' ');
  const tb = nb.split(' ');
  const sa = new Set(ta);
  const sb = new Set(tb);
  return ta.every((x) => sb.has(x)) || tb.every((x) => sa.has(x));
}

interface Slot {
  /** Who currently occupies the position. */
  player: PitchPlayer;
  /** The starter the slot belongs to (=== player until a swap). */
  starter: PitchPlayer;
  cameOnAt: number | null;
  /** The occupant went off but the replacement couldn't be paired — keep the
   *  dot with a ▼ marker instead of guessing. */
  unresolvedOff: boolean;
}

interface SubPair { in: string; out: string }

/** Resolve the current XI. Two pairing phases per round — ESPN's explicit
 *  "X replaces Y" text first (FotMob and ESPN order/romanize names
 *  differently, so this can miss), then unique same-minute matching among
 *  the subs not yet accounted for. Rounds repeat so chained substitutions
 *  and triple-sub minutes resolve as earlier pairings free up candidates. */
function currentXI(team: LineupTeam, pairs: SubPair[]): { slots: Slot[]; off: PitchPlayer[] } {
  const off: PitchPlayer[] = [];
  const used = new Set<PitchPlayer>();
  const slots: Slot[] = team.starters.map((starter) => ({ player: starter, starter, cameOnAt: null, unresolvedOff: false }));
  const swap = (slot: Slot, repl: PitchPlayer) => {
    used.add(repl);
    off.push(slot.player);
    slot.player = repl;
    slot.cameOnAt = repl.subIn;
  };
  const pending = () => slots.filter((s) => s.player.subOut != null && !s.unresolvedOff);

  for (let round = 0; round < 8; round++) {
    let progress = false;
    for (const slot of pending()) {
      const byText = pairs.find((p) => nameMatch(p.out, slot.player.name));
      const repl = byText ? team.subs.find((c) => !used.has(c) && nameMatch(c.name, byText.in)) : undefined;
      if (repl) { swap(slot, repl); progress = true; }
    }
    for (const slot of pending()) {
      const minute = slot.player.subOut;
      const candidates = team.subs.filter((c) => !used.has(c) && c.subIn === minute);
      const outs = pending().filter((s) => s.player.subOut === minute);
      if (candidates.length === 1 && outs.length === 1 && candidates[0]) { swap(slot, candidates[0]); progress = true; }
    }
    if (!progress) break;
  }
  for (const slot of pending()) slot.unresolvedOff = true;
  return { slots, off };
}

/** Last name token, capped — fits under a dot on a 68-unit-wide pitch. */
function shortName(name: string): string {
  const last = name.trim().split(' ').pop() ?? name;
  return last.length > 12 ? `${last.slice(0, 11)}…` : last;
}

function PlayerDot({ slot, side }: { slot: Slot; side: 'home' | 'away' }) {
  const p = slot.player;
  const st = slot.starter;
  if (st.x == null || st.y == null) return null;
  const cx = side === 'home' ? st.x * W : (1 - st.x) * W;
  const cy = side === 'home' ? Math.min(Math.max(st.y * (H / 2), 5), H / 2 - 8) : Math.max(Math.min(H - st.y * (H / 2), H - 5.3), H / 2 + 5);
  // The away keeper hugs the bottom goal — put the name in the empty space
  // above the dot instead of past the goal line / under the row above.
  const nameY = side === 'away' && st.y <= 0.14 ? cy - 4.8 : cy + 6.6;
  const stroke = side === 'home' ? 'var(--app-accent)' : 'var(--app-info)';
  return (
    <g opacity={slot.unresolvedOff ? 0.55 : 1}>
      <circle cx={cx} cy={cy} r={3.3} fill="var(--app-panel-2)" stroke={stroke} strokeWidth={0.6} />
      <text x={cx} y={cy + 1.05} textAnchor="middle" fontSize={3} fontWeight={700} fill="var(--app-ink)" className="tnum">
        {p.num ?? ''}
      </text>
      <text x={cx} y={nameY} textAnchor="middle" fontSize={2.4} fontWeight={600} fill="var(--app-ink-soft)">
        {shortName(p.name)}
      </text>
      {p.rating != null && (
        <g>
          <rect x={cx + 3.9} y={cy - 1.55} width={6} height={3.1} rx={1} fill="var(--app-elevated)" stroke="var(--app-line)" strokeWidth={0.2} />
          <text x={cx + 6.9} y={cy + 0.8} textAnchor="middle" fontSize={2.2} fontWeight={700} fill="var(--app-ink-soft)" className="tnum">
            {p.rating.toFixed(1)}
          </text>
        </g>
      )}
      {p.goals > 0 && (
        <g>
          {/* badge sits on the dot's rim so it never collides with neighbors */}
          <circle cx={cx - 2.7} cy={cy - 2.7} r={1.4} fill={stroke} stroke="var(--app-panel-2)" strokeWidth={0.25} />
          <text x={cx - 2.7} y={cy - 2.05} textAnchor="middle" fontSize={1.8} fontWeight={800} fill="var(--app-panel)">
            {p.goals}
          </text>
        </g>
      )}
      {slot.cameOnAt != null && (
        <g>
          <polygon
            points={`${cx - 6.2},${cy + 0.3} ${cx - 4.2},${cy + 0.3} ${cx - 5.2},${cy - 1.5}`}
            fill="var(--app-success)"
          />
          <text x={cx - 5.2} y={cy + 3} textAnchor="middle" fontSize={1.9} fill="var(--app-success)" className="tnum">
            {slot.cameOnAt}'
          </text>
        </g>
      )}
      {slot.unresolvedOff && (
        <g>
          <polygon
            points={`${cx - 6.2},${cy - 1.5} ${cx - 4.2},${cy - 1.5} ${cx - 5.2},${cy + 0.3}`}
            fill="var(--app-loss)"
          />
          <text x={cx - 5.2} y={cy + 3} textAnchor="middle" fontSize={1.9} fill="var(--app-loss)" className="tnum">
            {slot.player.subOut}'
          </text>
        </g>
      )}
    </g>
  );
}

/** Pitch furniture: outline, halfway line, center circle, boxes, spots. */
function PitchLines() {
  const box = { w: 40.32, d: 16.5 };
  const six = { w: 18.32, d: 5.5 };
  const lx = (w: number) => (W - w) / 2;
  return (
    <g stroke="var(--app-line)" strokeWidth={0.35} fill="none">
      <rect x={0.4} y={0.4} width={W - 0.8} height={H - 0.8} rx={1} />
      <line x1={0.4} y1={H / 2} x2={W - 0.4} y2={H / 2} />
      <circle cx={W / 2} cy={H / 2} r={9.15} />
      <rect x={lx(box.w)} y={0.4} width={box.w} height={box.d} />
      <rect x={lx(six.w)} y={0.4} width={six.w} height={six.d} />
      <rect x={lx(box.w)} y={H - 0.4 - box.d} width={box.w} height={box.d} />
      <rect x={lx(six.w)} y={H - 0.4 - six.d} width={six.w} height={six.d} />
      <circle cx={W / 2} cy={11} r={0.4} fill="var(--app-line)" />
      <circle cx={W / 2} cy={H - 11} r={0.4} fill="var(--app-line)" />
    </g>
  );
}

function SubbedOffStrip({ off, tone }: { off: PitchPlayer[]; tone: 'home' | 'away' }) {
  const t = useT();
  if (!off.length) return null;
  return (
    <div className="min-w-0">
      <div className={`mb-1 text-[10px] font-bold uppercase tracking-wide ${tone === 'home' ? 'text-accent' : 'text-info'}`}>
        {t.match.subbedOff}
      </div>
      <ul className="space-y-0.5 text-xs text-muted">
        {off.map((p) => (
          <li key={p.name} className="flex items-center gap-1.5">
            <span aria-hidden className="text-loss">▼</span>
            <span className="truncate">{p.name}</span>
            {p.subOut != null && <span className="tnum shrink-0 text-faint">{fmt(t.live.minute, { n: p.subOut })}</span>}
            {p.rating != null && <span className="tnum ml-auto shrink-0 text-faint">{p.rating.toFixed(1)}</span>}
          </li>
        ))}
      </ul>
    </div>
  );
}

export function LineupPitch({ lineup, home, away, events }: {
  lineup: MatchLineup;
  home: string;
  away: string;
  events: MatchLiveEvent[];
}) {
  const t = useT();

  const { homeXI, awayXI } = useMemo(() => {
    const subs = parseEvents(events).filter((e) => e.kind === 'sub' && e.subOut);
    const pairsFor = (side: 'home' | 'away'): SubPair[] =>
      subs.filter((e) => e.side === side).map((e) => ({ in: e.title, out: e.subOut! }));
    return {
      homeXI: currentXI(lineup.home, pairsFor('home')),
      awayXI: currentXI(lineup.away, pairsFor('away')),
    };
  }, [lineup, events]);

  const teamLabel = (name: string, tm: LineupTeam) =>
    [name, tm.formation, tm.coach].filter(Boolean).join(' · ');

  return (
    <div className="mx-auto max-w-105">
      <div className="mb-1.5 truncate text-center text-xs font-bold text-accent">{teamLabel(home, lineup.home)}</div>
      <svg
        viewBox={`0 0 ${W} ${H}`}
        role="img"
        aria-label={`${t.match.lineups}: ${home} – ${away}`}
        className="block w-full rounded-xl bg-elevated/40"
      >
        <PitchLines />
        {homeXI.slots.map((s) => <PlayerDot key={s.starter.name} slot={s} side="home" />)}
        {awayXI.slots.map((s) => <PlayerDot key={s.starter.name} slot={s} side="away" />)}
      </svg>
      <div className="mt-1.5 truncate text-center text-xs font-bold text-info">{teamLabel(away, lineup.away)}</div>
      {(homeXI.off.length > 0 || awayXI.off.length > 0) && (
        <div className="mt-3 grid grid-cols-2 gap-4">
          <SubbedOffStrip off={homeXI.off} tone="home" />
          <SubbedOffStrip off={awayXI.off} tone="away" />
        </div>
      )}
    </div>
  );
}
