/* global React */
const { useEffect, useState, useRef } = React;

// =============================================================
// GitHub-dark tokens (matches the existing TryMyRepo screenshot)
// =============================================================
const T = {
  bg: "#0d1117",
  bgDeep: "#010409",
  panel: "#161b22",
  panel2: "#1c2128",
  border: "#30363d",
  borderSoft: "#21262d",
  text: "#e6edf3",
  textMuted: "#7d8590",
  textDim: "#484f58",
  green: "#3fb950",
  greenDim: "#238636",
  blue: "#58a6ff",
  orange: "#f0883e",
  red: "#f85149",
  purple: "#a371f7",
  yellow: "#d29922",
};

const FONT_MONO =
  "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, monospace";
const FONT_SANS =
  "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";

// =============================================================
// Timeline phases — auto-advance once, hold on STREAM_READY
// =============================================================
const PHASES = {
  IDLE: 0,
  PROMPT_TYPING: 1,
  PROMPT_TO_SANDBOX: 2,
  SANDBOX_RUNNING: 3,
  BRANCHING: 4, // sandbox emits both video + transcript
  CONVERGING: 5, // videodb processes, llm narrates, both feed into vdb
  STREAM_READY: 6,
};

const TIMELINE = [
  { phase: PHASES.PROMPT_TYPING, at: 300 },
  { phase: PHASES.PROMPT_TO_SANDBOX, at: 2400 },
  { phase: PHASES.SANDBOX_RUNNING, at: 3100 },
  { phase: PHASES.BRANCHING, at: 5800 },
  { phase: PHASES.CONVERGING, at: 7800 },
  { phase: PHASES.STREAM_READY, at: 10200 },
];

function useAutoTimeline() {
  const [phase, setPhase] = useState(PHASES.IDLE);
  useEffect(() => {
    const ts = TIMELINE.map((step) =>
      setTimeout(() => setPhase(step.phase), step.at)
    );
    return () => ts.forEach(clearTimeout);
  }, []);
  return [phase, setPhase];
}

// =============================================================
// Reusable: node frame
// =============================================================
function NodeFrame({ children, label, index, active, done, accent = T.green, width, style }) {
  const status = done ? "done" : active ? "active" : "idle";
  const ring =
    active ? `0 0 0 1px ${accent}, 0 0 24px ${accent}33` :
    done ? `0 0 0 1px ${T.borderSoft}` :
    "0 0 0 1px transparent";
  return (
    <div
      style={{
        width,
        background: T.panel,
        borderRadius: 10,
        border: `1px solid ${active ? accent + "55" : T.border}`,
        boxShadow: ring,
        transition: "border-color 400ms ease, box-shadow 400ms ease",
        position: "relative",
        ...style,
      }}
    >
      {/* corner index */}
      <div
        style={{
          position: "absolute",
          top: 10,
          left: 12,
          fontFamily: FONT_MONO,
          fontSize: 10,
          letterSpacing: 1.4,
          color: active ? accent : T.textMuted,
          textTransform: "uppercase",
          display: "flex",
          alignItems: "center",
          gap: 8,
          transition: "color 300ms ease",
        }}
      >
        <span
          style={{
            width: 5,
            height: 5,
            borderRadius: 999,
            background: active ? accent : done ? T.textDim : T.borderSoft,
            boxShadow: active ? `0 0 10px ${accent}` : "none",
            transition: "background 300ms ease, box-shadow 300ms ease",
          }}
        />
        {String(index).padStart(2, "0")} · {label}
      </div>
      {children}
    </div>
  );
}

// =============================================================
// Logo squish — full wordmark when active/idle, compact pill when done
// =============================================================
function LogoMark({ full, short, accent = T.text, mode = "full" }) {
  const isFull = mode === "full";
  return (
    <div
      style={{
        display: "inline-flex",
        alignItems: "center",
        gap: 6,
        padding: isFull ? "5px 10px" : "2px 7px",
        borderRadius: isFull ? 6 : 999,
        border: `1px dashed ${accent}66`,
        background: `${accent}10`,
        color: accent,
        fontFamily: FONT_MONO,
        fontSize: isFull ? 11 : 9,
        letterSpacing: isFull ? 1.6 : 1,
        fontWeight: 700,
        transition: "all 500ms cubic-bezier(.16,1,.3,1)",
      }}
    >
      <span
        style={{
          width: isFull ? 8 : 5,
          height: isFull ? 8 : 5,
          borderRadius: 2,
          background: accent,
          opacity: 0.85,
          transition: "all 400ms ease",
        }}
      />
      {isFull ? full : short}
    </div>
  );
}

// =============================================================
// 01 · PROMPT NODE — typed prompt in a chat-input shaped pill
// =============================================================
const PROMPT_TEXT = "Run cypress app locally and record a smoke test.";
function PromptNode({ phase }) {
  const active = phase === PHASES.PROMPT_TYPING;
  const done = phase >= PHASES.PROMPT_TO_SANDBOX;
  const [typed, setTyped] = useState("");
  useEffect(() => {
    if (phase < PHASES.PROMPT_TYPING) {
      setTyped("");
      return;
    }
    if (phase >= PHASES.PROMPT_TO_SANDBOX) {
      setTyped(PROMPT_TEXT);
      return;
    }
    let i = 0;
    const t = setInterval(() => {
      i += 1;
      setTyped(PROMPT_TEXT.slice(0, i));
      if (i >= PROMPT_TEXT.length) clearInterval(t);
    }, 38);
    return () => clearInterval(t);
  }, [phase]);

  return (
    <NodeFrame index={1} label="prompt" active={active} done={done} width={240}>
      <div style={{ padding: "34px 14px 14px" }}>
        <div
          style={{
            background: T.bgDeep,
            border: `1px solid ${T.borderSoft}`,
            borderRadius: 8,
            padding: "10px 12px",
            display: "flex",
            alignItems: "flex-start",
            gap: 8,
            minHeight: 64,
          }}
        >
          <span
            style={{
              color: T.green,
              fontFamily: FONT_MONO,
              fontSize: 12,
              lineHeight: "16px",
              opacity: 0.9,
            }}
          >
            ❯
          </span>
          <span
            style={{
              color: T.text,
              fontFamily: FONT_MONO,
              fontSize: 11.5,
              lineHeight: "16px",
              flex: 1,
              wordBreak: "break-word",
            }}
          >
            {typed}
            {active && <CaretBlink />}
          </span>
        </div>
        <div
          style={{
            marginTop: 10,
            fontFamily: FONT_MONO,
            fontSize: 10,
            color: T.textMuted,
            display: "flex",
            justifyContent: "space-between",
          }}
        >
          <span>user request</span>
          <span style={{ opacity: done ? 1 : 0.4, color: done ? T.green : T.textMuted, transition: "all 300ms" }}>
            {done ? "✓ sent" : "⟂ idle"}
          </span>
        </div>
      </div>
    </NodeFrame>
  );
}
function CaretBlink() {
  return (
    <span
      style={{
        display: "inline-block",
        width: 7,
        height: 13,
        background: T.green,
        marginLeft: 2,
        transform: "translateY(2px)",
        animation: "caretBlink 900ms steps(2) infinite",
      }}
    />
  );
}

// =============================================================
// 02 · SANDBOX NODE — modal + pi session, terminal lines streaming
// =============================================================
const TERM_LINES = [
  { t: "▶", body: "git clone github.com/user/repo", c: T.textMuted },
  { t: "✓", body: "deps installed · 14.2s", c: T.green },
  { t: "▶", body: "npm run dev", c: T.textMuted },
  { t: "✓", body: "vite ready · port 5173", c: T.green },
  { t: "▶", body: "cypress run --headed smoke", c: T.textMuted },
  { t: "✓", body: "3 passed · 0 failed", c: T.green },
];

function SandboxNode({ phase }) {
  const active = phase >= PHASES.SANDBOX_RUNNING && phase < PHASES.CONVERGING;
  const done = phase >= PHASES.CONVERGING;
  const showLogoFull =
    phase < PHASES.SANDBOX_RUNNING || phase === PHASES.PROMPT_TO_SANDBOX;

  // line reveal driven by phase progression
  const [revealed, setRevealed] = useState(0);
  useEffect(() => {
    if (phase < PHASES.SANDBOX_RUNNING) {
      setRevealed(0);
      return;
    }
    if (phase >= PHASES.CONVERGING) {
      setRevealed(TERM_LINES.length);
      return;
    }
    let i = 0;
    const t = setInterval(() => {
      i = Math.min(i + 1, TERM_LINES.length);
      setRevealed(i);
      if (i >= TERM_LINES.length) clearInterval(t);
    }, 480);
    return () => clearInterval(t);
  }, [phase]);

  // recording timer
  const [recSec, setRecSec] = useState(0);
  useEffect(() => {
    if (phase < PHASES.SANDBOX_RUNNING) {
      setRecSec(0);
      return;
    }
    const t = setInterval(() => setRecSec((s) => s + 1), 220);
    return () => clearInterval(t);
  }, [phase]);
  const recLabel = `${String(Math.floor(recSec / 60)).padStart(2, "0")}:${String(
    recSec % 60
  ).padStart(2, "0")}`;

  return (
    <NodeFrame
      index={2}
      label="sandbox · pi session"
      active={active || phase === PHASES.PROMPT_TO_SANDBOX || phase === PHASES.BRANCHING}
      done={done}
      width={300}
      accent={T.green}
    >
      <div style={{ padding: "34px 14px 14px" }}>
        {/* header inside terminal */}
        <div
          style={{
            background: T.bgDeep,
            border: `1px solid ${T.borderSoft}`,
            borderRadius: 8,
            overflow: "hidden",
          }}
        >
          <div
            style={{
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              padding: "7px 10px",
              borderBottom: `1px solid ${T.borderSoft}`,
              background: T.panel2,
            }}
          >
            <div style={{ display: "flex", gap: 5 }}>
              <Dot c="#ff5f57" />
              <Dot c="#febc2e" />
              <Dot c="#28c940" />
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <LogoMark
                full="MODAL · π"
                short="π"
                accent={T.text}
                mode={showLogoFull ? "full" : "short"}
              />
              <RecBadge active={active} label={recLabel} />
            </div>
          </div>
          {/* terminal body */}
          <div
            style={{
              padding: "8px 12px",
              minHeight: 96,
              fontFamily: FONT_MONO,
              fontSize: 11,
              lineHeight: "15px",
              display: "flex",
              flexDirection: "column",
              gap: 2,
            }}
          >
            {TERM_LINES.slice(0, revealed).map((l, i) => (
              <div
                key={i}
                style={{
                  display: "flex",
                  gap: 7,
                  opacity: 1,
                  animation: "fadeUp 320ms ease both",
                }}
              >
                <span style={{ color: l.c, width: 8 }}>{l.t}</span>
                <span style={{ color: T.text, opacity: 0.92 }}>{l.body}</span>
              </div>
            ))}
            {active && revealed < TERM_LINES.length && (
              <div style={{ display: "flex", gap: 7 }}>
                <span style={{ color: T.textMuted }}>·</span>
                <span style={{ color: T.textMuted }}>
                  pi <CaretBlink />
                </span>
              </div>
            )}
          </div>
        </div>
        <div
          style={{
            marginTop: 10,
            fontFamily: FONT_MONO,
            fontSize: 10,
            color: T.textMuted,
            display: "flex",
            justifyContent: "space-between",
          }}
        >
          <span>screen + transcript</span>
          <span style={{ color: done ? T.green : T.textMuted }}>
            {done ? "✓ captured" : active ? "● recording" : "⟂ idle"}
          </span>
        </div>
      </div>
    </NodeFrame>
  );
}
function Dot({ c }) {
  return (
    <span
      style={{ width: 9, height: 9, borderRadius: 999, background: c, opacity: 0.9 }}
    />
  );
}
function RecBadge({ active, label }) {
  return (
    <div
      style={{
        display: "inline-flex",
        alignItems: "center",
        gap: 6,
        padding: "2px 7px",
        borderRadius: 999,
        background: active ? "#f8514922" : "transparent",
        border: `1px solid ${active ? T.red + "66" : T.borderSoft}`,
        fontFamily: FONT_MONO,
        fontSize: 9.5,
        letterSpacing: 1,
        color: active ? T.red : T.textMuted,
      }}
    >
      <span
        style={{
          width: 6,
          height: 6,
          borderRadius: 999,
          background: active ? T.red : T.textDim,
          animation: active ? "recPulse 1.2s ease-in-out infinite" : "none",
        }}
      />
      REC · {label}
    </div>
  );
}

// =============================================================
// 03 · LLM + VOICE NODE — branch from sandbox transcript → narration
// =============================================================
const LLM_LINES = [
  "agent cloned the repo",
  "installed dependencies",
  "started dev server on :5173",
  "ran cypress smoke",
];
function LlmVoiceNode({ phase }) {
  const active = phase === PHASES.BRANCHING || phase === PHASES.CONVERGING;
  const done = phase >= PHASES.STREAM_READY;
  const showLogoFull = phase < PHASES.CONVERGING;

  const [revealed, setRevealed] = useState(0);
  useEffect(() => {
    if (phase < PHASES.BRANCHING) {
      setRevealed(0);
      return;
    }
    if (phase >= PHASES.STREAM_READY) {
      setRevealed(LLM_LINES.length);
      return;
    }
    let i = 0;
    const t = setInterval(() => {
      i = Math.min(i + 1, LLM_LINES.length);
      setRevealed(i);
      if (i >= LLM_LINES.length) clearInterval(t);
    }, 520);
    return () => clearInterval(t);
  }, [phase]);

  return (
    <NodeFrame
      index={3}
      label="narrate"
      active={active}
      done={done}
      width={250}
      accent={T.purple}
    >
      <div style={{ padding: "34px 14px 14px" }}>
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: 6,
            marginBottom: 10,
          }}
        >
          <LogoMark
            full="OPENAI · LLM"
            short="LLM"
            accent={T.purple}
            mode={showLogoFull ? "full" : "short"}
          />
          <LogoMark
            full="ELEVENLABS"
            short="11L"
            accent={T.blue}
            mode={showLogoFull ? "full" : "short"}
          />
        </div>
        {/* transcript stream */}
        <div
          style={{
            background: T.bgDeep,
            border: `1px solid ${T.borderSoft}`,
            borderRadius: 8,
            padding: "8px 10px",
            minHeight: 86,
            fontFamily: FONT_MONO,
            fontSize: 10.5,
            lineHeight: "15px",
            display: "flex",
            flexDirection: "column",
            gap: 2,
          }}
        >
          {LLM_LINES.slice(0, revealed).map((l, i) => (
            <div
              key={i}
              style={{
                display: "flex",
                gap: 6,
                color: T.text,
                opacity: 0.9,
                animation: "fadeUp 320ms ease both",
              }}
            >
              <span style={{ color: T.purple, width: 10 }}>›</span>
              <span>{l}</span>
            </div>
          ))}
          {/* waveform reveal once narration is rendered */}
          {phase >= PHASES.CONVERGING && <Waveform animated={!done} />}
        </div>
        <div
          style={{
            marginTop: 10,
            fontFamily: FONT_MONO,
            fontSize: 10,
            color: T.textMuted,
            display: "flex",
            justifyContent: "space-between",
          }}
        >
          <span>transcript → voice</span>
          <span style={{ color: done ? T.green : T.textMuted }}>
            {done ? "✓ rendered" : active ? "● synth" : "⟂ idle"}
          </span>
        </div>
      </div>
    </NodeFrame>
  );
}
function Waveform({ animated }) {
  const bars = 32;
  return (
    <div
      style={{
        display: "flex",
        alignItems: "center",
        gap: 2,
        marginTop: 6,
        height: 18,
      }}
    >
      {Array.from({ length: bars }).map((_, i) => {
        const h = 4 + ((Math.sin(i * 0.7) + 1) * 6 + (i % 4 === 0 ? 4 : 0));
        return (
          <span
            key={i}
            style={{
              width: 2,
              height: h,
              background: T.blue,
              opacity: 0.55 + (i % 3) * 0.15,
              borderRadius: 1,
              animation: animated ? `wf ${800 + i * 33}ms ease-in-out infinite alternate` : "none",
              animationDelay: `${i * 30}ms`,
            }}
          />
        );
      })}
    </div>
  );
}

// =============================================================
// 04 · VIDEODB NODE — receives video + narration, composes
// =============================================================
function VideoDbNode({ phase }) {
  const active =
    phase === PHASES.BRANCHING ||
    phase === PHASES.CONVERGING ||
    phase === PHASES.SANDBOX_RUNNING;
  const done = phase >= PHASES.STREAM_READY;
  const showLogoFull = phase < PHASES.CONVERGING;

  const videoArrived = phase >= PHASES.CONVERGING;
  const transcriptArrived = phase >= PHASES.CONVERGING;
  const voiceArrived = phase >= PHASES.STREAM_READY || phase === PHASES.CONVERGING;

  const [progress, setProgress] = useState(0);
  useEffect(() => {
    if (phase < PHASES.CONVERGING) {
      setProgress(0);
      return;
    }
    if (phase >= PHASES.STREAM_READY) {
      setProgress(1);
      return;
    }
    let p = 0;
    const t = setInterval(() => {
      p = Math.min(p + 0.04, 0.98);
      setProgress(p);
    }, 80);
    return () => clearInterval(t);
  }, [phase]);

  return (
    <NodeFrame
      index={4}
      label="videodb · index"
      active={active && !done}
      done={done}
      width={260}
      accent={T.orange}
    >
      <div style={{ padding: "34px 14px 14px" }}>
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: 8,
            marginBottom: 10,
          }}
        >
          <LogoMark
            full="VIDEODB"
            short="VDB"
            accent={T.orange}
            mode={showLogoFull ? "full" : "short"}
          />
          <span
            style={{
              fontFamily: FONT_MONO,
              fontSize: 9.5,
              color: T.textMuted,
              letterSpacing: 0.8,
            }}
          >
            media memory
          </span>
        </div>

        <div
          style={{
            background: T.bgDeep,
            border: `1px solid ${T.borderSoft}`,
            borderRadius: 8,
            padding: "10px 12px",
            display: "flex",
            flexDirection: "column",
            gap: 6,
          }}
        >
          <Asset label="video.mp4" sub="00:14" arrived={videoArrived} tint={T.green} />
          <Asset
            label="transcript.json"
            sub="4 events"
            arrived={transcriptArrived}
            tint={T.purple}
          />
          <Asset
            label="narration.mp3"
            sub="00:14"
            arrived={voiceArrived}
            tint={T.blue}
          />
          {/* compose bar */}
          <div style={{ marginTop: 4 }}>
            <div
              style={{
                fontFamily: FONT_MONO,
                fontSize: 9.5,
                color: T.textMuted,
                display: "flex",
                justifyContent: "space-between",
                marginBottom: 3,
              }}
            >
              <span>composing</span>
              <span style={{ color: done ? T.green : T.orange }}>
                {done ? "ready" : `${Math.round(progress * 100)}%`}
              </span>
            </div>
            <div
              style={{
                height: 4,
                borderRadius: 999,
                background: T.borderSoft,
                overflow: "hidden",
              }}
            >
              <div
                style={{
                  height: "100%",
                  width: `${progress * 100}%`,
                  background: done ? T.green : T.orange,
                  boxShadow: `0 0 10px ${done ? T.green : T.orange}88`,
                  transition: "width 200ms ease, background 400ms ease",
                }}
              />
            </div>
          </div>
        </div>
      </div>
    </NodeFrame>
  );
}
function Asset({ label, sub, arrived, tint }) {
  return (
    <div
      style={{
        display: "flex",
        alignItems: "center",
        justifyContent: "space-between",
        fontFamily: FONT_MONO,
        fontSize: 10.5,
        opacity: arrived ? 1 : 0.35,
        transition: "opacity 360ms ease",
      }}
    >
      <span style={{ display: "flex", alignItems: "center", gap: 7 }}>
        <span
          style={{
            width: 7,
            height: 7,
            borderRadius: 1,
            background: arrived ? tint : T.textDim,
            transition: "background 300ms ease",
          }}
        />
        <span style={{ color: T.text }}>{label}</span>
      </span>
      <span style={{ color: T.textMuted }}>{sub}</span>
    </div>
  );
}

// =============================================================
// 05 · STREAM NODE — final mini player
// =============================================================
function StreamNode({ phase }) {
  const ready = phase >= PHASES.STREAM_READY;
  return (
    <NodeFrame
      index={5}
      label="stream"
      active={ready}
      done={false}
      width={260}
      accent={T.green}
    >
      <div style={{ padding: "34px 14px 14px" }}>
        {/* mini player */}
        <div
          style={{
            position: "relative",
            aspectRatio: "16 / 9",
            background: ready
              ? `linear-gradient(135deg, #0d1f17 0%, #14342a 60%, #0d1f17 100%)`
              : T.bgDeep,
            border: `1px solid ${ready ? T.green + "55" : T.borderSoft}`,
            borderRadius: 8,
            overflow: "hidden",
            transition: "all 500ms ease",
            boxShadow: ready ? `0 0 0 4px ${T.green}11, 0 0 28px ${T.green}33` : "none",
          }}
        >
          {/* faint scanlines on ready */}
          {ready && (
            <div
              style={{
                position: "absolute",
                inset: 0,
                backgroundImage:
                  "repeating-linear-gradient(0deg, rgba(63,185,80,0.04) 0 1px, transparent 1px 3px)",
                mixBlendMode: "screen",
              }}
            />
          )}
          {/* play button */}
          <div
            style={{
              position: "absolute",
              inset: 0,
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              transition: "opacity 400ms ease",
              opacity: ready ? 1 : 0.25,
            }}
          >
            <div
              style={{
                width: 44,
                height: 44,
                borderRadius: 999,
                background: ready ? T.green : T.borderSoft,
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                boxShadow: ready ? `0 0 24px ${T.green}88` : "none",
                transition: "all 500ms ease",
              }}
            >
              <span
                style={{
                  width: 0,
                  height: 0,
                  borderLeft: `12px solid ${ready ? T.bg : T.textDim}`,
                  borderTop: "8px solid transparent",
                  borderBottom: "8px solid transparent",
                  marginLeft: 3,
                }}
              />
            </div>
          </div>
          {/* timestamp */}
          <div
            style={{
              position: "absolute",
              left: 8,
              bottom: 6,
              fontFamily: FONT_MONO,
              fontSize: 10,
              color: ready ? T.green : T.textDim,
              letterSpacing: 0.8,
              transition: "color 400ms ease",
            }}
          >
            ◉ 00:00 / 00:14
          </div>
          <div
            style={{
              position: "absolute",
              right: 8,
              bottom: 6,
              fontFamily: FONT_MONO,
              fontSize: 10,
              color: ready ? T.green : T.textDim,
              letterSpacing: 0.8,
            }}
          >
            HD
          </div>
        </div>
        {/* url pill */}
        <div
          style={{
            marginTop: 10,
            background: T.bgDeep,
            border: `1px solid ${ready ? T.green + "44" : T.borderSoft}`,
            borderRadius: 6,
            padding: "6px 10px",
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            fontFamily: FONT_MONO,
            fontSize: 10.5,
            color: ready ? T.text : T.textMuted,
            transition: "all 400ms ease",
          }}
        >
          <span>
            stream.videodb.io/run/<span style={{ color: ready ? T.green : T.textMuted }}>a3f9…</span>
          </span>
          <span
            style={{
              color: ready ? T.green : T.textDim,
              fontSize: 10,
            }}
          >
            ⧉
          </span>
        </div>
        <div
          style={{
            marginTop: 8,
            fontFamily: FONT_MONO,
            fontSize: 10,
            color: T.textMuted,
            display: "flex",
            justifyContent: "space-between",
          }}
        >
          <span>playable evidence</span>
          <span style={{ color: ready ? T.green : T.textMuted }}>
            {ready ? "✓ ready" : "⟂ pending"}
          </span>
        </div>
      </div>
    </NodeFrame>
  );
}

window.ProofExplainer = {
  PHASES,
  useAutoTimeline,
  PromptNode,
  SandboxNode,
  LlmVoiceNode,
  VideoDbNode,
  StreamNode,
  T,
  FONT_MONO,
  FONT_SANS,
};
