/* app.jsx — Card 12 (1:1 design replacement, wired to the real eval-target API)
 *
 * Three stages of the experience (unchanged from the design):
 *   idle      → only the HeroIntro is shown (form + pitch)
 *   running   → dashboard mounted (header + telemetry deck + command deck)
 *   complete  → same dashboard, comparison shown inside command deck
 *
 * Transitions (unchanged): idle→running door-open clip-path; running→complete
 * command-body wipe. The ONLY change vs the prototype is the data source:
 * `runSimulation` + static RELAY_RUN are replaced by window.byokicuBridge,
 * which drives runState from live `/api/eval-target/runs` polling and supplies
 * the real relay + server-baseline runs for the comparison view.
 */

const { useState: useStateApp, useEffect: useEffectApp, useRef: useRefApp } = React;
const tr = window.byokicuTr;

/* Card 92-10: language + theme toggle. lang and theme are COUPLED — English·dark ⟷
   中文·浅色 — so this is a single two-state control. The label shows the language you
   switch TO, in its own script (the conventional affordance), with a sun/moon hinting
   the paired theme. Rendered outside the mode/hero conditionals so it's always reachable. */
function LangThemeToggle({ lang, onFlip, inline }) {
  const toZh = lang === "en";
  return (
    <button
      type="button"
      className={inline ? "locale-toggle inline" : "locale-toggle"}
      onClick={onFlip}
      aria-label={toZh ? "切换到中文浅色界面" : "Switch to English dark interface"}
    >
      <span className="lt-ico">{toZh ? "☀" : "☾"}</span>
      <span>{toZh ? "中文 · 浅色" : "EN · Dark"}</span>
    </button>
  );
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  collisionIntensity: "default",
  showRoadGrid: true,
  showStations: true,
  runSpeed: 1.0,
  gaugeAccent: "phosphor"
}/*EDITMODE-END*/;

const HERO_FADE_MS = 620;

function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [mode, setMode] = useStateApp("idle");
  // Card 92-10: locale state mirrors window.byokicuLocale. Toggling it forces a tree-wide
  // re-render so every byokicuTr() call (components + mock-data pure helpers) re-reads lang.
  const [locale, setLocale] = useStateApp(() => ({
    lang: (window.byokicuLocale && window.byokicuLocale.lang) || "en",
    theme: (window.byokicuLocale && window.byokicuLocale.theme) || "dark"
  }));
  const [heroMounted, setHeroMounted] = useStateApp(true);
  const [doorState, setDoorState] = useStateApp("closed"); // closed | opening | open
  const [error, setError] = useStateApp("");
  const [form, setForm] = useStateApp(() => loadInitialForm());
  const [runState, setRunState] = useStateApp(() => initialRunState());
  const runHandleRef = useRefApp(null);
  const doorTimerRef = useRefApp(0);
  const scrollLeaderboardRef = useRefApp(false);

  useEffectApp(() => {
    const root = document.documentElement;
    const dark = {
      phosphor: { accent: "#52c7d4", accent2: "#7fdbe5", glow: "rgba(82, 199, 212, 0.16)" },
      cyan:     { accent: "#6dd5ff", accent2: "#a3e6ff", glow: "rgba(109, 213, 255, 0.16)" },
      amber:    { accent: "#ffae3a", accent2: "#ffc468", glow: "rgba(255, 174, 58, 0.16)" }
    };
    // Card 92-10: light variants are darkened — --accent is used as text/needle, not just
    // glow, so the bright dark-mode teal/cyan/amber would wash out on white. This effect
    // OWNS --accent/--accent-2/--gauge-needle/--accent-dim as inline props on <html>
    // (inline beats the [data-theme=light] stylesheet rule), so the light palette is themed
    // here, not in CSS. Re-runs on theme change.
    const light = {
      phosphor: { accent: "#0e7d8c", accent2: "#0b6a77", glow: "rgba(14, 125, 140, 0.14)" },
      cyan:     { accent: "#1574b3", accent2: "#0f5e95", glow: "rgba(21, 116, 179, 0.14)" },
      amber:    { accent: "#a86a00", accent2: "#8a5600", glow: "rgba(168, 106, 0, 0.14)" }
    };
    const map = locale.theme === "light" ? light : dark;
    const c = map[tweaks.gaugeAccent] || map.phosphor;
    root.style.setProperty("--accent", c.accent);
    root.style.setProperty("--accent-2", c.accent2);
    root.style.setProperty("--gauge-needle", c.accent);
    root.style.setProperty("--accent-dim", c.glow);
  }, [tweaks.gaugeAccent, locale.theme]);

  const canStart =
    form.endpoint.trim() && form.provider.trim() &&
    form.model.trim() && form.apiKey.trim();

  const clearDoorTimer = () => {
    if (doorTimerRef.current) { clearTimeout(doorTimerRef.current); doorTimerRef.current = 0; }
  };

  const onStart = () => {
    if (!canStart || mode === "running") return;
    const target = {
      providerId: form.provider,
      model: form.model,
      apiEndpointHost: hostOf(form.endpoint),
      protocol: form.protocol,
      runId: "",
      kind: "byok-relay",
      credentialOrigin: "ephemeral-key"
    };
    // Mount the dashboard, kick off door-open animation, dispatch the real run.
    setError("");
    setMode("running");
    setRunState({ ...initialRunState(), target });
    setDoorState("opening");

    runHandleRef.current = window.byokicuBridge.startRun(form, {
      onProgress: (next) => setRunState((prev) => ({ ...prev, ...next })),
      onComplete: ({ relayRun, baselineRun, finalRunState, fatalError }) => {
        setRunState({ ...finalRunState, relayRun, baselineRun, fatalError: fatalError || "" });
        setMode("complete");
        if (fatalError) {
          // Card 16: non-continuable error (503/400/408) — stop the flow, land on
          // the results page (above), and alert the user.
          window.alert("Evaluation stopped — " + fatalError);
        }
      },
      onError: (message) => {
        if (runHandleRef.current) { runHandleRef.current.cancel(); runHandleRef.current = null; }
        clearDoorTimer();
        setError(message || "Run failed");
        setMode("idle");
        setRunState(initialRunState());
        setHeroMounted(true);
        setDoorState("closed");
      }
    });

    // Honor the UI's "key cleared after dispatch" promise: the bridge already
    // cleared its payload copy and never persists the key; drop it from form
    // state too so it doesn't linger in memory after the run is dispatched.
    setForm((f) => ({ ...f, apiKey: "" }));

    doorTimerRef.current = setTimeout(() => {
      setDoorState("open");
      setHeroMounted(false);
    }, HERO_FADE_MS);
  };

  // Card 92-10: flip English·dark ⟷ 中文·浅色. Mutate the global + localStorage + <html>
  // first (byokicuSetLocale), THEN setState — the re-render runs after this handler returns,
  // by which point byokicuTr() reads the new lang.
  const flipLocale = () => {
    const next = locale.lang === "en"
      ? { lang: "zh", theme: "light" }
      : { lang: "en", theme: "dark" };
    window.byokicuSetLocale(next.lang, next.theme);
    setLocale(next);
  };

  const onReset = () => {
    if (runHandleRef.current) { runHandleRef.current.cancel(); runHandleRef.current = null; }
    clearDoorTimer();
    setError("");
    setMode("idle");
    setRunState(initialRunState());
    setHeroMounted(true);
    setDoorState("closed");
  };

  // Card 21: results-page "Leader Board" nav → return home, then scroll to the
  // leaderboard once the home view has re-mounted (effect below, not a timer).
  const onShowLeaderboard = () => {
    scrollLeaderboardRef.current = true;
    onReset();
  };

  useEffectApp(() => {
    if (heroMounted && scrollLeaderboardRef.current) {
      scrollLeaderboardRef.current = false;
      const el = document.getElementById("leaderboard");
      if (el && el.scrollIntoView) el.scrollIntoView({ behavior: "smooth" });
    }
  }, [heroMounted]);

  useEffectApp(() => {
    return () => {
      if (runHandleRef.current) runHandleRef.current.cancel();
      clearDoorTimer();
    };
  }, []);

  const elapsed = runState.elapsedMs;
  const modeLabel = tr(mode === "idle" ? "header.mode.idle" : mode === "running" ? "header.mode.running" : "header.mode.complete");

  const dashboardClass = [
    "shell",
    "dashboard-doors",
    doorState === "closed"  ? "door-closed" : "",
    doorState === "opening" ? "door-opening" : ""
  ].filter(Boolean).join(" ");

  return (
    <>
      {/* byok.icu: the fixed top-right toggle shows on the LANDING only; the dashboard
          (mode !== idle) carries its own inline toggle inside the header nav. */}
      {mode === "idle" && <LangThemeToggle lang={locale.lang} onFlip={flipLocale} />}
      {heroMounted && (
        <>
          {/* Card 92-07/92-08: the BYOK landing form — endpoint + key, discover the
              relay's models (protocol auto-detected), pick one, dispatch. Replaces the
              old HeroIntro form; HeroIntro stays loaded only for its LeaderBoard export. */}
          <ByokLanding
            form={form}
            setForm={setForm}
            onStart={onStart}
            canStart={canStart}
            fading={doorState !== "closed"}
          />
          <LeaderBoard />
        </>
      )}

      {mode !== "idle" && (
        <div className={dashboardClass}>
          {/* byok.icu: results/dashboard header is a functional nav (home / login / lang
              toggle) — the run telemetry (status / run id / endpoint / elapsed) was removed. */}
          <header className="head-strip">
            <div className="head-brand">
              <span className="bug">b</span>
              <h1>byok.icu<small>{tr("header.brandSub")}</small></h1>
            </div>
            {/* byok.icu: centered "what's under test" — the model + endpoint on the grill. */}
            <div className="head-grill" aria-label="Model under test">
              <span className="grill-label">{tr("header.grill")}</span>
              <span className="grill-target">
                {runState.target.model || "—"}
                {(runState.target.apiEndpointHost || hostOf(form.endpoint))
                  ? <span className="grill-ep"> @ {runState.target.apiEndpointHost || hostOf(form.endpoint)}</span>
                  : null}
              </span>
            </div>
            <nav className="head-nav" aria-label="Site navigation">
              <button type="button" className="head-nav-link" onClick={onReset}>{tr("nav.home")}</button>
              <button type="button" className="head-nav-link soon" onClick={() => {}} title="coming soon">{tr("nav.login")}</button>
              <LangThemeToggle lang={locale.lang} onFlip={flipLocale} inline />
            </nav>
          </header>

          <TelemetryDeck mode={mode} runState={runState} tweaks={tweaks} />

          <CommandDeck
            mode={mode}
            runState={runState}
            form={form}
            setForm={setForm}
            onStart={onStart}
            onReset={onReset}
            onShowLeaderboard={onShowLeaderboard}
            canStart={canStart}
            tweaks={tweaks}
          />
        </div>
      )}

      {error && (
        <div
          role="alert"
          onClick={() => setError("")}
          style={{
            position: "fixed", left: "50%", bottom: 24, transform: "translateX(-50%)",
            zIndex: 2147483645, maxWidth: "min(560px, 92vw)", cursor: "pointer",
            padding: "12px 16px", borderRadius: 10,
            background: "rgba(20,8,10,.92)", color: "#ffd7dd",
            border: "1px solid rgba(255,90,110,.5)", font: "13px/1.4 var(--font-mono, ui-monospace, monospace)",
            boxShadow: "0 12px 40px rgba(0,0,0,.5)"
          }}
        >
          {error}
        </div>
      )}

      <TweaksPanel>
        <TweakSection label="Telemetry" />
        <TweakRadio label="Gauge accent" value={tweaks.gaugeAccent}
          options={["phosphor", "cyan", "amber"]}
          onChange={(v) => setTweak("gaugeAccent", v)} />
        <TweakToggle label="Scope grid sweep" value={tweaks.showRoadGrid}
          onChange={(v) => setTweak("showRoadGrid", v)} />
        <TweakToggle label="Case stations on dial" value={tweaks.showStations}
          onChange={(v) => setTweak("showStations", v)} />
        <TweakSection label="Impact" />
        <TweakRadio label="Row collision" value={tweaks.collisionIntensity}
          options={["subtle", "default", "brutal"]}
          onChange={(v) => setTweak("collisionIntensity", v)} />
        <TweakButton label="Replay impact"
          onClick={() => { window.__byokicu_replayImpact?.(); }} />
        <TweakSection label="Run" />
        <TweakSlider label="Sim speed" value={tweaks.runSpeed}
          min={0.5} max={2.0} step={0.1} unit="×"
          onChange={(v) => setTweak("runSpeed", v)} />
        <TweakButton label="Reset" onClick={onReset} />
      </TweaksPanel>
    </>
  );
}

function loadInitialForm() {
  const saved = (window.byokicuBridge && window.byokicuBridge.loadSavedForm && window.byokicuBridge.loadSavedForm()) || {};
  return {
    endpoint: saved.endpoint || "",
    protocol: saved.protocol || "openai",
    provider: saved.provider || "",
    model: saved.model || "",
    apiKey: ""
  };
}

function initialRunState() {
  return {
    tokenRate: 0,
    inputTokens: 0,
    outputTokens: 0,
    elapsedMs: 0,
    cases: CASES.map((c) => ({
      id: c.id, label: c.label, short: c.short,
      status: "pending", latencyMs: undefined
    })),
    currentCaseIdx: -1,
    target: { providerId: "", model: "", apiEndpointHost: "", protocol: "openai", runId: "" },
    relayRun: null,
    baselineRun: null
  };
}

function hostOf(url) {
  try { return new URL(url).host; }
  catch {
    const m = String(url || "").match(/^https?:\/\/([^/]+)/i);
    return m ? m[1] : url;
  }
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
