// loading-pattern.jsx
// Trillic loading / branded motion: pattern-unlock animation that traces
// "Trillic" on a 3x3 grid, with celestial circles. Themed via design-system
// CSS variables (--brand-gold, --brand-gold-50, etc).

const { useMemo } = React;

// Read brand tokens off :root so the animation re-themes with the design system
// (light/dark, future palette swaps). Falls back to brand book values.
function readToken(name, fallback) {
  if (typeof window === 'undefined') return fallback;
  const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
  return v || fallback;
}

const THEME = {
  bg:        readToken('--background',     '#FAF5EC'),
  bgDeep:    readToken('--brand-gold-50',  '#FAF5EC'),
  ink:       readToken('--brand-gold',     '#CDA973'),
  inkBright: readToken('--brand-gold-300', '#D9BC83'),
  inkSoft:   'color-mix(in oklab, ' + readToken('--brand-gold', '#CDA973') + ' 25%, transparent)',
  inkLine:   'color-mix(in oklab, ' + readToken('--brand-gold', '#CDA973') + ' 40%, transparent)',
};

// ── Grid geometry ────────────────────────────────────────────────────────
const NODE_XY = (i) => ({ col: i % 3, row: Math.floor(i / 3) });

// Letter paths in upright (unrotated) grid space.
// 0 1 2
// 3 4 5
// 6 7 8
const LETTERS = [
  // T: trace top row 0->1->2, retrace 2->1, then down spine 1->4->7
  { ch: 'T', path: [0, 1, 2, 1, 4, 7] },
  // lowercase r: stem up the left column 6->3->0, then across top to 2
  { ch: 'r', path: [6, 3, 0, 1, 2] },
  // capital I: top serif 0->1->2, retrace 2->1, spine 1->4->7, bottom serif 7->6, retrace 6->7->8
  { ch: 'I', path: [0, 1, 2, 1, 4, 7, 6, 7, 8] },
  { ch: 'L', path: [0, 3, 6, 7, 8] },
  { ch: 'L', path: [0, 3, 6, 7, 8] },
  { ch: 'I', path: [0, 1, 2, 1, 4, 7, 6, 7, 8] },
  { ch: 'C', path: [2, 1, 0, 3, 6, 7, 8] },
];

// ── Polyline helpers ─────────────────────────────────────────────────────
function polyMeta(path) {
  const segs = [];
  let total = 0;
  for (let i = 0; i < path.length - 1; i++) {
    const a = NODE_XY(path[i]);
    const b = NODE_XY(path[i + 1]);
    const len = Math.hypot(b.col - a.col, b.row - a.row);
    segs.push({ from: a, to: b, len, t0: total, t1: total + len });
    total += len;
  }
  segs.forEach((s) => { s.t0 /= total; s.t1 /= total; });
  return { segs, total };
}

function pointAt(meta, f) {
  if (f <= 0) return { x: meta.segs[0].from.col, y: meta.segs[0].from.row };
  if (f >= 1) {
    const last = meta.segs[meta.segs.length - 1];
    return { x: last.to.col, y: last.to.row };
  }
  for (const s of meta.segs) {
    if (f >= s.t0 && f <= s.t1) {
      const local = (s.t1 === s.t0) ? 0 : (f - s.t0) / (s.t1 - s.t0);
      return {
        x: s.from.col + (s.to.col - s.from.col) * local,
        y: s.from.row + (s.to.row - s.from.row) * local,
      };
    }
  }
  const last = meta.segs[meta.segs.length - 1];
  return { x: last.to.col, y: last.to.row };
}

function partialPoints(path, meta, f) {
  if (f <= 0) return [];
  const pts = [{ x: NODE_XY(path[0]).col, y: NODE_XY(path[0]).row }];
  if (f >= 1) {
    for (let i = 1; i < path.length; i++) {
      pts.push({ x: NODE_XY(path[i]).col, y: NODE_XY(path[i]).row });
    }
    return pts;
  }
  for (const s of meta.segs) {
    if (f >= s.t1) {
      pts.push({ x: s.to.col, y: s.to.row });
    } else if (f > s.t0) {
      const local = (f - s.t0) / (s.t1 - s.t0);
      pts.push({
        x: s.from.col + (s.to.col - s.from.col) * local,
        y: s.from.row + (s.to.row - s.from.row) * local,
      });
      break;
    }
  }
  return pts;
}

function visitedNodes(path, meta, f) {
  const visited = new Set();
  if (f <= 0) return visited;
  visited.add(path[0]);
  for (let i = 0; i < meta.segs.length; i++) {
    if (f >= meta.segs[i].t1 - 1e-6) visited.add(path[i + 1]);
  }
  return visited;
}

// ── Persistent grid (the 9 dots — always visible) ────────────────────────
function PersistentGrid({ cx, cy, cell }) {
  const toX = (gx) => cx + (gx - 1) * cell;
  const toY = (gy) => cy + (gy - 1) * cell;
  return (
    <g>
      {/* outer frame ticks */}
      {[[-1, -1], [1, -1], [-1, 1], [1, 1]].map(([sx, sy], i) => {
        const x = cx + sx * (cell + 50);
        const y = cy + sy * (cell + 50);
        const len = 18;
        return (
          <g key={i} stroke={THEME.inkLine} strokeWidth="1.2">
            <line x1={x} y1={y} x2={x - sx * len} y2={y} />
            <line x1={x} y1={y} x2={x} y2={y - sy * len} />
          </g>
        );
      })}
      {/* base 9 dots */}
      {Array.from({ length: 9 }).map((_, i) => {
        const { col, row } = NODE_XY(i);
        return (
          <g key={i}>
            <circle cx={toX(col)} cy={toY(row)} r={16} fill="none" stroke={THEME.inkSoft} strokeWidth="1.2" />
            <circle cx={toX(col)} cy={toY(row)} r={6} fill={THEME.inkSoft} />
          </g>
        );
      })}
    </g>
  );
}

// ── Pattern stroke (drawn line + visited halos + head) ───────────────────
function PatternStroke({ cx, cy, cell, progress, path, opacity = 1 }) {
  const meta = useMemo(() => polyMeta(path), [path]);
  const pts = partialPoints(path, meta, progress);
  const head = pointAt(meta, progress);
  const visited = visitedNodes(path, meta, progress);

  const toX = (gx) => cx + (gx - 1) * cell;
  const toY = (gy) => cy + (gy - 1) * cell;
  const linePts = pts.map((p) => `${toX(p.x).toFixed(2)},${toY(p.y).toFixed(2)}`).join(' ');

  return (
    <g style={{ opacity }}>
      {/* visited node halos (overlay on top of base grid) */}
      {Array.from(visited).map((i) => {
        const { col, row } = NODE_XY(i);
        const x = toX(col), y = toY(row);
        return (
          <g key={i}>
            <circle cx={x} cy={y} r={26} fill="none" stroke={THEME.inkBright} strokeWidth="1.2" opacity="0.5" />
            <circle cx={x} cy={y} r={10} fill={THEME.inkBright} />
          </g>
        );
      })}

      {/* stroke */}
      {pts.length >= 2 && (
        <polyline
          points={linePts}
          fill="none"
          stroke={THEME.ink}
          strokeWidth="5"
          strokeLinecap="round"
          strokeLinejoin="round"
          opacity="0.95"
        />
      )}

      {/* head */}
      {progress > 0 && progress < 1 && (
        <>
          <circle cx={toX(head.x)} cy={toY(head.y)} r={14} fill="none" stroke={THEME.inkBright} strokeWidth="1.4" opacity="0.7" />
          <circle cx={toX(head.x)} cy={toY(head.y)} r={6} fill={THEME.inkBright} />
        </>
      )}
    </g>
  );
}

// ── Celestial circles (gold on cream) ────────────────────────────────────
function CelestialField({ cx, cy, time }) {
  const orbits = [
    { r: 380, w: 0.18,  br: 7,  op: 0.55, phase: 0.0 },
    { r: 470, w: -0.11, br: 4,  op: 0.42, phase: 1.1 },
    { r: 560, w: 0.07,  br: 11, op: 0.6,  phase: 2.3 },
    { r: 640, w: -0.05, br: 3,  op: 0.32, phase: 3.7 },
    { r: 740, w: 0.04,  br: 16, op: 0.7,  phase: 0.8 },
    { r: 820, w: -0.03, br: 5,  op: 0.4,  phase: 4.2 },
    { r: 910, w: 0.022, br: 24, op: 0.78, phase: 2.9 },
  ];
  return (
    <g>
      {orbits.map((o, i) => (
        <circle
          key={'r' + i}
          cx={cx} cy={cy} r={o.r}
          fill="none"
          stroke={THEME.ink}
          strokeWidth="1"
          opacity={0.08 + (i % 2) * 0.04}
          strokeDasharray={i % 2 === 0 ? '0' : '2 7'}
        />
      ))}
      {orbits.map((o, i) => {
        const ang = o.phase + time * o.w;
        const x = cx + Math.cos(ang) * o.r;
        const y = cy + Math.sin(ang) * o.r * 0.92;
        return (
          <g key={'b' + i}>
            <circle cx={x} cy={y} r={o.br + 8} fill={THEME.ink} opacity={o.op * 0.18} />
            <circle cx={x} cy={y} r={o.br} fill={THEME.ink} opacity={o.op} />
          </g>
        );
      })}
      {Array.from({ length: 60 }).map((_, i) => {
        const a = (i * 137.5) % 360 * (Math.PI / 180);
        const r = 240 + ((i * 53) % 760);
        const x = cx + Math.cos(a) * r;
        const y = cy + Math.sin(a) * r;
        const tw = 0.4 + 0.6 * Math.abs(Math.sin(time * 1.3 + i));
        return <circle key={'s' + i} cx={x} cy={y} r={1.4} fill={THEME.ink} opacity={0.22 * tw} />;
      })}
    </g>
  );
}

// ── Letter slot (just the stroke; no labels) ─────────────────────────────
function LetterSlot({ start, drawDur, holdDur, exitDur, idx, cx, cy, cell }) {
  return (
    <Sprite start={start} end={start + drawDur + holdDur + exitDur}>
      {({ localTime }) => {
        let progress = 0, op = 1;
        if (localTime < drawDur) {
          progress = Easing.easeInOutCubic(clamp(localTime / drawDur, 0, 1));
        } else if (localTime < drawDur + holdDur) {
          progress = 1;
        } else {
          progress = 1;
          op = 1 - Easing.easeInQuad(clamp((localTime - drawDur - holdDur) / exitDur, 0, 1));
        }
        return (
          <PatternStroke
            cx={cx} cy={cy} cell={cell}
            progress={progress}
            path={LETTERS[idx].path}
            opacity={op}
          />
        );
      }}
    </Sprite>
  );
}

// ── HUD letter readout (rotates with the world, but fights it visually) ──
// We stamp the current letter outside the rotated layer so it's readable.
function CurrentLetterReadout({ time, introDur, perLetter }) {
  // Determine current letter index based on time in the letter sequence.
  let idx = -1;
  let phase = '';
  const seqT = time - introDur;
  if (seqT >= 0) {
    const i = Math.floor(seqT / perLetter);
    if (i < LETTERS.length) {
      idx = i;
      const local = seqT - i * perLetter;
      phase = local < 0.95 ? 'DRAW' : local < 1.13 ? 'HOLD' : 'FADE';
    }
  }
  if (idx < 0) return null;

  return (
    <g fontFamily="var(--font-sans, 'Outfit', Helvetica, Arial, sans-serif)" fill={THEME.ink}>
      <text x="60" y="170" fontSize="220" fontWeight="700" letterSpacing="-0.04em">
        {LETTERS[idx].ch}
      </text>
      <text x="60" y="210" fontSize="12" letterSpacing="0.4em" opacity="0.6">
        {`GLYPH 0${idx + 1} / 0${LETTERS.length}  ·  ${phase}`}
      </text>
    </g>
  );
}

// ── Main scene ───────────────────────────────────────────────────────────
function TrillicScene() {
  const time = useTime();
  const W = 1600, H = 900;
  const CX = W / 2, CY = H / 2;
  const CELL = 130;

  // Per-letter durations (total lifecycle = drawDur + holdDur + exitDur).
  // `cadence` is the start-to-start gap between successive letters; letting
  // it be SHORTER than the full lifecycle means each letter starts drawing
  // while the previous is still fading out — smoother, less gappy.
  const drawDur = 0.85;
  const holdDur = 0.10;
  const exitDur = 0.55;
  const perLetter = drawDur + holdDur + exitDur;  // 1.50 — full lifecycle
  const cadence  = drawDur;                       // 0.85 — start-to-start
  const introDur = 0.5;
  const finaleStart = introDur + cadence * LETTERS.length;
  const finaleDur = 2.2;

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width={W} height={H} style={{ position: 'absolute', inset: 0, display: 'block' }}>
      <defs>
        <radialGradient id="vg" cx="50%" cy="50%" r="65%">
          <stop offset="0%"   stopColor={THEME.bg} />
          <stop offset="70%"  stopColor={THEME.bg} />
          <stop offset="100%" stopColor={THEME.bgDeep} />
        </radialGradient>
      </defs>
      <rect x="0" y="0" width={W} height={H} fill="url(#vg)" />

      {/* ── Rotated world: grid + letters + celestial all rotate 90° CW ── */}
      <g transform={`rotate(45 ${CX} ${CY})`}>
        <CelestialField cx={CX} cy={CY} time={time} />
        <PersistentGrid cx={CX} cy={CY} cell={CELL} />

        {LETTERS.map((L, i) => (
          <LetterSlot
            key={i}
            idx={i}
            start={introDur + i * cadence}
            drawDur={drawDur} holdDur={holdDur} exitDur={exitDur}
            cx={CX} cy={CY} cell={CELL}
          />
        ))}

        {/* Intro: contracting rings */}
        <Sprite start={0} end={introDur}>
          {({ progress }) => {
            const r = 380 - 320 * Easing.easeInOutCubic(progress);
            const op = 1 - progress;
            return (
              <g>
                <circle cx={CX} cy={CY} r={r} fill="none" stroke={THEME.ink} strokeWidth="1.4" opacity={0.55 * op} />
                <circle cx={CX} cy={CY} r={r * 0.55} fill="none" stroke={THEME.ink} strokeWidth="1" opacity={0.35 * op} />
              </g>
            );
          }}
        </Sprite>

        {/* Finale flash + bodies converge (no wordmark in rotated layer) */}
        <Sprite start={finaleStart} end={finaleStart + finaleDur}>
          {({ progress }) => {
            const t = Easing.easeOutCubic(clamp(progress / 0.55, 0, 1));
            const fade = progress > 0.85 ? 1 - Easing.easeInQuad((progress - 0.85) / 0.15) : 1;
            const flashR = 60 + 1100 * Easing.easeOutCubic(progress);
            const flashOp = 0.4 * (1 - progress);
            const N = 12;
            return (
              <g style={{ opacity: fade }}>
                <circle cx={CX} cy={CY} r={flashR} fill="none" stroke={THEME.inkBright} strokeWidth="1.2" opacity={flashOp} />
                {Array.from({ length: N }).map((_, i) => {
                  const ang = (i / N) * Math.PI * 2;
                  const r = 800 * (1 - t);
                  const bx = CX + Math.cos(ang) * r;
                  const by = CY + Math.sin(ang) * r * 0.92;
                  const br = 4 + 16 * t;
                  return <circle key={i} cx={bx} cy={by} r={br} fill={THEME.ink} opacity={0.7 * (1 - t * 0.4)} />;
                })}
              </g>
            );
          }}
        </Sprite>
      </g>

      {/* ── Unrotated HUD overlay (readable text) ───────────────────────── */}
      <g fontFamily="var(--font-sans, 'Outfit', Helvetica, Arial, sans-serif)" fill={THEME.ink}>
        <text x="60" y="70"  fontSize="12" letterSpacing="0.4em" opacity="0.7">TRILLIC / COSMIC SEQUENCE</text>
        <line x1="60" y1="82" x2="280" y2="82" stroke={THEME.ink} strokeWidth="1" opacity="0.45" />
        <text x="60" y="104" fontSize="11" letterSpacing="0.3em" opacity="0.55">PATTERN  ·  03×03  ·  ROT −45°</text>

        <text x={W - 60} y="70"  textAnchor="end" fontSize="12" letterSpacing="0.4em" opacity="0.7">{`T+${time.toFixed(2)}s`}</text>
        <line x1={W - 280} y1="82" x2={W - 60} y2="82" stroke={THEME.ink} strokeWidth="1" opacity="0.45" />
        <text x={W - 60} y="104" textAnchor="end" fontSize="11" letterSpacing="0.3em" opacity="0.55">LOOP / ∞</text>

        <text x="60"     y={H - 50} fontSize="11" letterSpacing="0.3em" opacity="0.6">⟡  TRILLIC MOTION  ·  LOADING PATTERN</text>
        <text x={W - 60} y={H - 50} textAnchor="end" fontSize="11" letterSpacing="0.3em" opacity="0.6">SEQ. 7 GLYPHS / 1 LOOP</text>
      </g>

      {/* Live current-letter readout — rotates with stage but stays at top-left of frame */}
      <CurrentLetterReadout time={time} introDur={introDur} perLetter={cadence} />

      {/* Finale wordmark overlay (unrotated, readable) */}
      <Sprite start={finaleStart} end={finaleStart + finaleDur}>
        {({ progress }) => {
          const t = Easing.easeOutCubic(clamp(progress / 0.55, 0, 1));
          const fade = progress > 0.85 ? 1 - Easing.easeInQuad((progress - 0.85) / 0.15) : 1;
          const ls = 0.6 - 0.55 * t;
          return (
            <g style={{ opacity: fade }}>
              <text
                x={CX} y={CY + 30}
                textAnchor="middle"
                fontFamily="var(--font-sans, 'Outfit', Helvetica, Arial, sans-serif)"
                fontSize="200"
                fontWeight="700"
                fill={THEME.ink}
                letterSpacing={`${ls}em`}
                opacity={t}
              >
                TRILLIC
              </text>
              <text
                x={CX} y={CY + 90}
                textAnchor="middle"
                fontFamily="var(--font-sans, 'Outfit', Helvetica, Arial, sans-serif)"
                fontSize="14"
                fontWeight="400"
                fill={THEME.ink}
                letterSpacing="0.7em"
                opacity={0.6 * t}
              >
                A COSMIC SEQUENCE
              </text>
            </g>
          );
        }}
      </Sprite>
    </svg>
  );
}

window.TrillicScene = TrillicScene;
