// scene.jsx — "How a sentence becomes a graph"
// 10s auto-loop, paper aesthetic.

const SENTENCE = "Brandschottung in Schacht S-04 offen — bis nächste Woche, wenn die Lieferung kommt.";

// Highlighted spans (start/end indices into SENTENCE)
//
//   Brandschottung in Schacht S-04 offen — bis nächste Woche, wenn die Lieferung kommt.
//   ^^^^^^^^^^^^^^                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//   open item (Prüfgegenstand)            deadline (Frist) + condition
//                  loc                          ^^^^^^^^^^
//                                               cond (Lieferung) — folded into the Frist
//
// We highlight 4 spans → spawn 5 nodes (the open item implies two Gewerke and one Aufgabe).
const SPANS = [
  { id: "mangel",  text: "Brandschottung",        from: 0,  to: 14, kind: "Prüfgegenstand"  },
  { id: "loc",     text: "Schacht S-04",          from: 18, to: 30, kind: "Ort"     },
  { id: "frist",   text: "bis nächste Woche",     from: 39, to: 56, kind: "Frist"   },
  { id: "cond",    text: "wenn die Lieferung kommt", from: 58, to: 82, kind: "Cond" },
];

// Node positions in a 1280×720 stage (graph area roughly 200..1080 × 230..600)
const NODES = {
  mangel:  { x: 360, y: 360, w: 220, h: 64, label: "Brandschottung",   kind: "Prüfgegenstand"   },
  loc:     { x: 130, y: 360, w: 180, h: 56, label: "Schacht S-04",     kind: "Ort"      },
  gewerk1: { x: 640, y: 250, w: 180, h: 56, label: "Brandschutz",      kind: "Gewerk"   },
  gewerk2: { x: 640, y: 470, w: 180, h: 56, label: "Lüftung",          kind: "Gewerk"   },
  aufgabe: { x: 920, y: 360, w: 220, h: 64, label: "Schott schließen", kind: "Aufgabe"  },
  frist:   { x: 920, y: 540, w: 220, h: 64, label: "Frist",            kind: "Frist"    },
};

// Edges: source, target, role label, draw-time (sec)
const EDGES = [
  { from: "loc",     to: "mangel",  label: "located_in",   t0: 4.7 },
  { from: "mangel",  to: "gewerk1", label: "trade",        t0: 4.9 },
  { from: "mangel",  to: "gewerk2", label: "trade",        t0: 5.1 },
  { from: "mangel",  to: "aufgabe", label: "resolved_by",  t0: 5.4 },
  { from: "aufgabe", to: "frist",   label: "due_by",       t0: 5.7 },
];

// ── Helpers ──────────────────────────────────────────────────────────────

const C = {
  paper:   "#f6f1e7",
  ink:     "#1a1a18",
  ink2:    "#3b3b35",
  ink3:    "#7a7568",
  rule:    "#dcd4bf",
  accent:  "#7a5cf0",   // Alago purple
  accent2: "#5a3fd9",
  hi:      "#efe6c8",   // highlight background
  hi2:     "#e9d96b",   // highlight underline (warm)
};

// Smooth pulse ∈ [0,1]
const pulse = (t, period = 2) => 0.5 - 0.5 * Math.cos((2 * Math.PI * t) / period);

// ── Scene ────────────────────────────────────────────────────────────────

function SentenceToGraphScene() {
  const t = useTime();

  return (
    <svg
      viewBox="0 0 1280 720"
      width="1280"
      height="720"
      style={{ position: 'absolute', inset: 0, fontFamily: 'Inter, system-ui, sans-serif' }}
    >
      {/* Paper texture: dotted grid */}
      <defs>
        <pattern id="dots" width="20" height="20" patternUnits="userSpaceOnUse">
          <circle cx="1" cy="1" r="0.6" fill="#d6cdb6" />
        </pattern>
        <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M 0 0 L 10 5 L 0 10 z" fill={C.ink} />
        </marker>
        <marker id="arrowAcc" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M 0 0 L 10 5 L 0 10 z" fill={C.accent} />
        </marker>
      </defs>
      <rect x="0" y="0" width="1280" height="720" fill={C.paper} />
      <rect x="0" y="0" width="1280" height="720" fill="url(#dots)" opacity="0.6" />

      {/* Top frame: kicker */}
      <Kicker t={t} />

      {/* Sentence — types in 0..1.6s, highlights 1.6..2.8s, then "lifts" 2.8..4.6s */}
      <Sentence t={t} />

      {/* Caption */}
      <Caption t={t} />

      {/* Nodes — appear as the spans lift (2.8..4.6) and persist */}
      <Graph t={t} />

      {/* Date badge — resolves at the Frist 6.4..7.6s */}
      <DateBadge t={t} />

      {/* Footer mark */}
      <FooterMark t={t} />

      {/* Loop wash */}
      <LoopWash t={t} />
    </svg>
  );
}

// ── Kicker ───────────────────────────────────────────────────────────────

function Kicker({ t }) {
  return (
    <g>
      <rect x="80" y="60" width="14" height="14" fill={C.accent} transform="rotate(45 87 67)" />
      <text x="110" y="73" fontFamily="JetBrains Mono, monospace" fontSize="13" fill={C.ink} letterSpacing="2">
        ALAGO R&amp;D · ANIM. 01
      </text>
      <text x="110" y="92" fontFamily="JetBrains Mono, monospace" fontSize="11" fill={C.ink3} letterSpacing="1.5">
        HOW A SENTENCE BECOMES A GRAPH
      </text>
      <line x1="80" y1="118" x2="1200" y2="118" stroke={C.rule} strokeWidth="1" />
      <text x="1200" y="73" fontFamily="JetBrains Mono, monospace" fontSize="11" fill={C.ink3} textAnchor="end" letterSpacing="1.5">
        BV RIEMERSCHMIDT · MÜNCHEN · 09.02.26
      </text>
      <text x="1200" y="92" fontFamily="JetBrains Mono, monospace" fontSize="11" fill={C.ink3} textAnchor="end" letterSpacing="1.5">
        00:0{Math.floor(t)}.{String(Math.floor((t * 100) % 100)).padStart(2, "0")} / 00:10.00
      </text>
    </g>
  );
}

// ── Sentence ─────────────────────────────────────────────────────────────
// We render the sentence char-by-char in monospace so we can compute span x
// positions cheaply. ~7.2 px per char at 14px mono.

const CHAR_W = 13.4;  // px width per character at fontSize 22 mono
const SENT_X = 100;
const SENT_Y = 175;
const SENT_FS = 22;

function charX(i) { return SENT_X + i * CHAR_W; }

function Sentence({ t }) {
  // Type-in: 0 → 1.6
  const typeProgress = clamp((t - 0.05) / 1.55, 0, 1);
  const charsShown = Math.floor(typeProgress * SENTENCE.length);
  const visibleText = SENTENCE.slice(0, charsShown);
  const cursorOn = (Math.floor(t * 4) % 2 === 0) && t < 1.7;

  // Lift phase: 2.8 → 4.6 — spans fade out of the sentence
  const liftStart = 2.8;
  const liftEnd = 4.6;
  const liftP = clamp((t - liftStart) / (liftEnd - liftStart), 0, 1);

  // Sentence whole fades out 4.4..4.9 once spans have lifted
  const sentOpacity = clamp(1 - (t - 4.4) / 0.5, 0, 1);

  return (
    <g opacity={sentOpacity}>
      {/* Quote marks framing */}
      <text x={SENT_X - 28} y={SENT_Y - 6} fontSize="38" fontFamily="Instrument Serif, serif" fill={C.ink3}>
        “
      </text>

      {/* Highlight rects (drawn under text). Appear 1.6..2.4, fade with lift. */}
      {SPANS.map((s, i) => {
        const startHi = 1.6 + i * 0.18;
        const endHi = 2.4 + i * 0.18;
        const hiP = clamp((t - startHi) / (endHi - startHi), 0, 1);
        const liftI = clamp((t - (liftStart + i * 0.12)) / 0.6, 0, 1);
        const hiOpacity = hiP * (1 - liftI);
        const x1 = charX(s.from);
        const x2 = charX(s.to);
        return (
          <g key={s.id} opacity={hiOpacity}>
            <rect
              x={x1 - 2}
              y={SENT_Y - 22}
              width={(x2 - x1) * hiP}
              height={32}
              fill={C.hi2}
              opacity="0.55"
              rx="2"
            />
            <line
              x1={x1}
              y1={SENT_Y + 6}
              x2={x1 + (x2 - x1) * hiP}
              y2={SENT_Y + 6}
              stroke={C.accent}
              strokeWidth="2"
            />
          </g>
        );
      })}

      {/* Sentence text */}
      <text
        x={SENT_X}
        y={SENT_Y}
        fontFamily="JetBrains Mono, monospace"
        fontSize={SENT_FS}
        fill={C.ink}
        style={{ whiteSpace: 'pre' }}
        letterSpacing="0"
      >
        {visibleText}
      </text>
      {cursorOn && (
        <rect x={charX(charsShown)} y={SENT_Y - 18} width="10" height="22" fill={C.ink} opacity="0.7" />
      )}

      {/* Source attribution */}
      <text x={SENT_X} y={SENT_Y + 38} fontFamily="JetBrains Mono, monospace" fontSize="11" fill={C.ink3} letterSpacing="1.5">
        — JOUR-FIXE · PROJEKTSTEUERUNG · BV RIEMERSCHMIDT · KW 16
      </text>

      {/* Lifting span ghosts: each highlighted phrase rises toward its node target. */}
      {SPANS.map((s, i) => {
        const startL = liftStart + i * 0.12;
        const localP = clamp((t - startL) / (liftEnd - startL), 0, 1);
        if (localP <= 0) return null;
        const eased = Easing.easeInOutCubic(localP);
        const xStart = charX(s.from);
        const yStart = SENT_Y - 4;
        const target = NODES[
          s.id === "mangel" ? "mangel" :
          s.id === "loc"    ? "loc" :
          s.id === "frist"  ? "frist" :
          /* cond */          "frist"
        ];
        const xEnd = target.x - target.w / 2 + 14;
        const yEnd = target.y + 4;
        const x = xStart + (xEnd - xStart) * eased;
        const y = yStart + (yEnd - yStart) * eased;
        const opacity = (1 - localP) * 0.95;
        return (
          <text
            key={"lift-" + s.id}
            x={x}
            y={y}
            fontFamily="JetBrains Mono, monospace"
            fontSize={SENT_FS - 6 * eased}
            fill={C.accent2}
            opacity={opacity}
          >
            {s.text}
          </text>
        );
      })}
    </g>
  );
}

// ── Caption (mono, bottom of sentence area) ─────────────────────────────

function Caption({ t }) {
  const captions = [
    { from: 0.1,  to: 1.7,  text: "Step · Read." },
    { from: 1.7,  to: 2.8,  text: "Step · Detect entities." },
    { from: 2.8,  to: 4.7,  text: "Step · Lift to graph." },
    { from: 4.7,  to: 6.3,  text: "Step · Type the edges." },
    { from: 6.3,  to: 7.7,  text: "Step · Resolve the deadline." },
    { from: 7.7,  to: 9.4,  text: "5 nodes · 5 edges · 1 deadline · 1 condition." },
  ];
  const active = captions.find(c => t >= c.from && t <= c.to);
  if (!active) return null;
  const local = (t - active.from) / (active.to - active.from);
  const opacity = local < 0.1 ? local / 0.1 : local > 0.9 ? (1 - local) / 0.1 : 1;

  return (
    <g opacity={opacity}>
      <line x1="80" y1="660" x2="100" y2="660" stroke={C.accent} strokeWidth="2" />
      <text
        x="112" y="665"
        fontFamily="JetBrains Mono, monospace"
        fontSize="13"
        fill={C.ink}
        letterSpacing="1.5"
      >
        {active.text.toUpperCase()}
      </text>
    </g>
  );
}

// ── Graph ────────────────────────────────────────────────────────────────

function Graph({ t }) {
  // Each node has an "appear at" time tied to the lift of its source span.
  const appear = {
    mangel:  3.0,
    loc:     3.4,
    gewerk1: 4.9,   // emerges from the typed edge from Prüfgegenstand
    gewerk2: 5.1,
    aufgabe: 5.5,   // emerges as the resolution
    frist:   3.8,   // lifts from "bis nächste Woche"
  };

  return (
    <g>
      {/* Edges first (so they sit under nodes) */}
      {EDGES.map((e, i) => (
        <Edge key={i} t={t} edge={e} />
      ))}

      {/* Nodes */}
      {Object.entries(NODES).map(([id, n]) => (
        <Node key={id} t={t} id={id} node={n} appearAt={appear[id]} />
      ))}
    </g>
  );
}

function Node({ t, id, node, appearAt }) {
  const localP = clamp((t - appearAt) / 0.55, 0, 1);
  if (localP <= 0) return null;
  const eased = Easing.easeOutBack(localP);
  const opacity = clamp(localP * 1.3, 0, 1);
  const scale = 0.7 + 0.3 * eased;

  // Highlight the Frist when its date resolves (6.4..7.6)
  const isFrist = id === "frist";
  const fristGlow = isFrist ? clamp((t - 6.4) / 0.4, 0, 1) - clamp((t - 7.4) / 0.4, 0, 1) : 0;

  const w = node.w;
  const h = node.h;
  const x = node.x - w / 2;
  const y = node.y - h / 2;
  const cx = node.x;
  const cy = node.y;

  return (
    <g
      opacity={opacity}
      transform={`translate(${cx} ${cy}) scale(${scale}) translate(${-cx} ${-cy})`}
    >
      {/* Type label above */}
      <text
        x={cx} y={y - 10}
        textAnchor="middle"
        fontFamily="JetBrains Mono, monospace"
        fontSize="10"
        fill={C.accent2}
        letterSpacing="2"
      >
        {node.kind.toUpperCase()}
      </text>

      {/* Body */}
      <rect
        x={x} y={y} width={w} height={h}
        fill="white"
        stroke={isFrist ? C.accent : C.ink}
        strokeWidth={isFrist ? 1.5 + fristGlow * 1.5 : 1}
        rx="2"
      />
      {isFrist && fristGlow > 0 && (
        <rect
          x={x - 4} y={y - 4} width={w + 8} height={h + 8}
          fill="none"
          stroke={C.accent}
          strokeOpacity={fristGlow * 0.45}
          strokeWidth="1"
          rx="4"
        />
      )}

      {/* Ticked corners */}
      {[[x, y], [x + w, y], [x, y + h], [x + w, y + h]].map(([px, py], i) => (
        <g key={i}>
          <line x1={px - 4} y1={py} x2={px + 4} y2={py} stroke={C.ink} strokeWidth="1" />
          <line x1={px} y1={py - 4} x2={px} y2={py + 4} stroke={C.ink} strokeWidth="1" />
        </g>
      ))}

      {/* Label */}
      <text
        x={cx} y={cy + 5}
        textAnchor="middle"
        fontFamily="Instrument Serif, serif"
        fontSize="22"
        fill={C.ink}
      >
        {node.label}
      </text>
    </g>
  );
}

function Edge({ t, edge }) {
  const t0 = edge.t0;
  const localP = clamp((t - t0) / 0.5, 0, 1);
  if (localP <= 0) return null;
  const eased = Easing.easeInOutCubic(localP);

  const a = NODES[edge.from];
  const b = NODES[edge.to];

  // Compute edge endpoints: shrink to box edges (rough box-edge approximation)
  const [x1, y1, x2, y2] = lineBetweenBoxes(a, b);

  const dx = x2 - x1;
  const dy = y2 - y1;
  const cx = x1 + dx * eased;
  const cy = y1 + dy * eased;

  // Label position: midpoint, perpendicular offset
  const mx = x1 + dx * 0.5;
  const my = y1 + dy * 0.5;
  const len = Math.hypot(dx, dy) || 1;
  const nx = -dy / len;
  const ny = dx / len;
  const labelOpacity = clamp((t - t0 - 0.3) / 0.3, 0, 1);

  return (
    <g>
      <line
        x1={x1} y1={y1} x2={cx} y2={cy}
        stroke={C.ink}
        strokeWidth="1.25"
        markerEnd={localP >= 0.99 ? "url(#arrow)" : undefined}
      />
      {labelOpacity > 0 && (
        <g opacity={labelOpacity}>
          <rect
            x={mx + nx * 14 - 50}
            y={my + ny * 14 - 9}
            width="100" height="16"
            fill={C.paper}
            stroke="none"
          />
          <text
            x={mx + nx * 14}
            y={my + ny * 14 + 3}
            textAnchor="middle"
            fontFamily="JetBrains Mono, monospace"
            fontSize="10"
            fill={C.ink2}
            letterSpacing="1"
          >
            {edge.label}
          </text>
        </g>
      )}
    </g>
  );
}

// Box-edge intersection for a line between two rectangle centers.
function lineBetweenBoxes(a, b) {
  const dx = b.x - a.x;
  const dy = b.y - a.y;
  const aP = boxEdge(a, dx, dy);
  const bP = boxEdge(b, -dx, -dy);
  return [aP.x, aP.y, bP.x, bP.y];
}
function boxEdge(box, dx, dy) {
  const hw = box.w / 2;
  const hh = box.h / 2;
  if (dx === 0 && dy === 0) return { x: box.x, y: box.y };
  const tx = dx === 0 ? Infinity : hw / Math.abs(dx);
  const ty = dy === 0 ? Infinity : hh / Math.abs(dy);
  const tt = Math.min(tx, ty);
  return { x: box.x + dx * tt, y: box.y + dy * tt };
}

// ── Date badge ───────────────────────────────────────────────────────────

function DateBadge({ t }) {
  const t0 = 6.4;
  const localP = clamp((t - t0) / 0.6, 0, 1);
  if (localP <= 0) return null;
  const eased = Easing.easeOutBack(localP);
  const opacity = clamp(localP * 1.5, 0, 1);

  const frist = NODES.frist;
  const cx = frist.x + frist.w / 2 + 30;
  const cy = frist.y;
  const w = 220;
  const h = 80;

  // Ticker — strings rolling into the resolved date.
  const tickP = clamp((t - 6.5) / 0.7, 0, 1);
  const candidates = ["KW 16 · Mo 14.04.", "KW 17 · Do 17.04.", "KW 18 · Mi 23.04.", "KW 18 · Do 24.04.", "KW 18 · Do 24.04."];
  const idx = Math.min(candidates.length - 1, Math.floor(tickP * candidates.length));
  const dateLabel = candidates[idx];

  const condLocked = t > 7.0;

  return (
    <g
      opacity={opacity}
      transform={`translate(${cx} ${cy}) scale(${0.7 + 0.3 * eased}) translate(${-cx} ${-cy})`}
    >
      {/* Connector tick */}
      <line x1={frist.x + frist.w / 2} y1={cy} x2={cx - w / 2} y2={cy} stroke={C.accent} strokeWidth="1.25" strokeDasharray="3 3" />

      {/* Body */}
      <rect x={cx - w / 2} y={cy - h / 2} width={w} height={h} fill="white" stroke={C.accent} strokeWidth="1.5" rx="2" />
      <text x={cx} y={cy - h / 2 + 14} textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontSize="10" fill={C.accent2} letterSpacing="2">
        RESOLVED · INTERPRETATION
      </text>
      <text x={cx} y={cy + 4} textAnchor="middle" fontFamily="Instrument Serif, serif" fontSize="22" fill={C.ink}>
        {dateLabel}
      </text>
      <text x={cx} y={cy + 24} textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontSize="10" fill={C.ink3} letterSpacing="1.5">
        {condLocked ? "GATED · Lieferung pending" : "GATING · evaluating condition"}
      </text>
    </g>
  );
}

// ── Footer mark ──────────────────────────────────────────────────────────

function FooterMark({ t }) {
  return (
    <g>
      <line x1="80" y1="690" x2="1200" y2="690" stroke={C.rule} strokeWidth="1" />
      <text x="80" y="705" fontFamily="JetBrains Mono, monospace" fontSize="11" fill={C.ink3} letterSpacing="1.5">
        FIG. 01 · ANATOMY
      </text>
      <text x="1200" y="705" textAnchor="end" fontFamily="JetBrains Mono, monospace" fontSize="11" fill={C.ink3} letterSpacing="1.5">
        ALAGO.AI/RD
      </text>
    </g>
  );
}

// ── Loop wash ────────────────────────────────────────────────────────────
// Soft fade-to-paper at the very end so the loop isn't a hard cut.

function LoopWash({ t }) {
  const opacity = t > 9.4 ? clamp((t - 9.4) / 0.5, 0, 1) * 0.95 : 0;
  if (opacity <= 0) return null;
  return <rect x="0" y="0" width="1280" height="720" fill={C.paper} opacity={opacity} />;
}

window.SentenceToGraphScene = SentenceToGraphScene;
