/* global React, ReactDOM, window,
   CORPORA, MODELS, QUESTIONS, resolveRace,
   Header, QuestionBar, QuestionBank, AnswerPanel,
   EvidenceSection, Scoreboard, UploadModal,
   useTweaks, TweaksPanel, TweakSection, TweakToggle, TweakRadio, TweakSlider */
const { useState: uS, useEffect: uE, useRef: uR, useCallback: uC } = React;

const PACE = { Calm: 1.5, Normal: 1.0, Snappy: 0.55 };

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "blindDefault": false,
  "theme": "Light",
  "pace": "Normal",
  "ourDelay": 0,
  "showScoreboard": true
}/*EDITMODE-END*/;

function shuffled(arr) {
  const a = arr.slice();
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

function blankState() {
  const o = {};
  MODELS.forEach((m) => { o[m.key] = { running: false, done: false, text: "", elapsed: null, verdict: null }; });
  return o;
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  const [corpus, setCorpus] = uS(20);
  const [text, setText] = uS(QUESTIONS[0].text);
  const [activeQ, setActiveQ] = uS(QUESTIONS[0].id);
  const [blind, setBlind] = uS(!!TWEAK_DEFAULTS.blindDefault);
  const [revealed, setRevealed] = uS(false);
  const [running, setRunning] = uS(false);
  const [race, setRace] = uS(blankState);
  const [order, setOrder] = uS(MODELS.map((m) => m.key));
  const [evidenceOpen, setEvidenceOpen] = uS(false);
  const [evQ, setEvQ] = uS(activeQ);
  const [bankOpen, setBankOpen] = uS(false);
  const [uploadOpen, setUploadOpen] = uS(false);
  const [uploadedCount, setUploadedCount] = uS(0);
  const [lastQuery, setLastQuery] = uS(QUESTIONS[0].text);
  // which competitors are in the race (ours is always in)
  const [selected, setSelected] = uS(() => {
    const o = {}; MODELS.forEach((m) => { if (!m.ours) o[m.key] = true; }); return o;
  });
  const [tally, setTally] = uS(() => {
    const o = {}; MODELS.forEach((m) => { o[m.key] = { correct: 0, total: 0, latSum: 0, latN: 0 }; }); return o;
  });

  const rafRef = uR(null);

  // keep blind synced to tweak default when user flips it in panel (only if not mid-session)
  uE(() => { setBlind(!!t.blindDefault); /* eslint-disable-next-line */ }, [t.blindDefault]);

  const corpusObj = CORPORA.find((c) => c.id === corpus) || CORPORA[0];
  const oursModel = MODELS.find((m) => m.ours);
  const competitors = MODELS.filter((m) => !m.ours);
  const activeModels = MODELS.filter((m) => m.ours || selected[m.key]);
  const corpusDetail = corpusObj.detail + (uploadedCount > 0
    ? "  ·  +" + uploadedCount + " of your drawing" + (uploadedCount > 1 ? "s" : "")
    : "");

  function toggleCompetitor(key, on) {
    if (running) return;
    let ns;
    if (key === "__all") { ns = {}; competitors.forEach((c) => { ns[c.key] = on; }); }
    else { ns = { ...selected, [key]: on }; }
    setSelected(ns);
    // rebuild the board so panels match the new selection right away
    const keys = MODELS.filter((m) => m.ours || ns[m.key]).map((m) => m.key);
    setOrder(keys);
    const blank = {};
    keys.forEach((k) => { blank[k] = { running: false, done: false, text: "", elapsed: null, verdict: null }; });
    setRace(blank);
    setEvidenceOpen(false);
  }

  const run = uC((opts) => {
    if (running) return;
    const q = ((opts && opts.text != null ? opts.text : text) || "").trim();
    if (!q) return;
    // resolve which scripted question (if any)
    let qid = (opts && opts.qid !== undefined) ? opts.qid : activeQ;
    if (qid) {
      const match = QUESTIONS.find((x) => x.id === qid);
      if (!match || match.text.trim() !== q) qid = null;
    }
    if (!qid) {
      const byText = QUESTIONS.find((x) => x.text.trim() === q);
      qid = byText ? byText.id : null;
    }
    const { results, verified } = resolveRace(corpus, qid, q);
    setLastQuery(q);
    const activeKeys = MODELS.filter((m) => m.ours || selected[m.key]).map((m) => m.key);

    // build per-model configs
    const cfg = {};
    MODELS.forEach((m) => {
      const r = results[m.key];
      const paceF = PACE[t.pace] || 1;
      let ms = r.ms * paceF;
      let latency = r.latency;
      if (m.ours) { ms += (t.ourDelay || 0) * 550 * paceF; latency += (t.ourDelay || 0); }
      cfg[m.key] = { ...r, ms: Math.max(700, ms), latency, words: r.answer.split(" ") };
    });

    setBankOpen(false);
    setEvidenceOpen(false);
    setRevealed(false);
    setEvQ(qid || "default");
    setOrder(blind ? shuffled(activeKeys) : activeKeys);
    setRunning(true);

    const init = {};
    activeKeys.forEach((k) => { init[k] = { running: true, done: false, text: "", elapsed: 0, verdict: null }; });
    setRace(init);

    const start = performance.now();
    if (rafRef.current) cancelAnimationFrame(rafRef.current);

    function frame(now) {
      const elapsedMs = now - start;
      let allDone = true;
      const next = {};
      activeKeys.forEach((key) => {
        const c = cfg[key];
        const p = Math.min(elapsedMs / c.ms, 1);
        if (p < 1) allDone = false;
        const n = Math.ceil(c.words.length * p);
        const txt = c.words.slice(0, n).join(" ");
        if (p >= 1) {
          next[key] = { running: false, done: true, text: c.answer, elapsed: c.latency,
            verdict: c.verdict, note: c.note, failMsg: c.failMsg, evidence: !!c.evidence };
        } else {
          next[key] = { running: true, done: false, text: txt, elapsed: c.latency * p, evidence: !!c.evidence };
        }
      });
      setRace(next);
      if (!allDone) { rafRef.current = requestAnimationFrame(frame); }
      else {
        setRunning(false);
        if (verified) {
          setTally((prev) => {
            const nt = { ...prev };
            activeKeys.forEach((key) => {
              const c = cfg[key];
              const cur = { ...(prev[key] || { correct: 0, total: 0, latSum: 0, latN: 0 }) };
              cur.total += 1;
              if (c.verdict === "correct") cur.correct += 1;
              cur.latSum += c.latency; cur.latN += 1;
              nt[key] = cur;
            });
            return nt;
          });
        }
      }
    }
    rafRef.current = requestAnimationFrame(frame);
  }, [running, text, activeQ, corpus, blind, selected, t.pace, t.ourDelay]);

  uE(() => () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); }, []);

  function pickQuestion(q) {
    setText(q.text); setActiveQ(q.id); setBankOpen(false);
    run({ text: q.text, qid: q.id });
  }

  function onChangeCorpus(id) {
    setCorpus(id); setEvidenceOpen(false);
  }

  const oursDone = race[oursModel.key] && race[oursModel.key].done && race[oursModel.key].evidence;
  const canShowEvidence = oursDone && (!blind || revealed);

  return (
    <div className={t.theme === "Dark" ? "theme-dark" : ""} style={{
      minHeight: "100vh", background: "var(--bg)", color: "var(--fg)",
      display: "flex", flexDirection: "column", fontFamily: "var(--font-sans)" }}>

      <Header corpus={corpus} setCorpus={onChangeCorpus} corpora={CORPORA}
        blind={blind} setBlind={(b) => { setBlind(b); if (!b) setRevealed(false); }}
        revealed={revealed} onReveal={() => setRevealed(true)}
        onUpload={() => setUploadOpen(true)} running={running} uploadedCount={uploadedCount} />

      <QuestionBar value={text} setValue={(v) => { setText(v); setActiveQ(null); }}
        onRun={run} onToggleBank={() => setBankOpen((o) => !o)} bankOpen={bankOpen}
        running={running} corpusDetail={corpusDetail}
        competitors={competitors} selected={selected} onToggleCompetitor={toggleCompetitor} />

      <QuestionBank open={bankOpen} questions={QUESTIONS} onPick={pickQuestion} onClose={() => setBankOpen(false)} />

      {/* the race */}
      <div style={{ padding: "4px 22px 8px" }}>
        <div className="ev-race" data-count={order.length} style={{ display: "grid", gap: 14, alignItems: "start",
          justifyContent: order.length === 1 ? "center" : "stretch" }}>
          {order.map((key, i) => {
            const m = MODELS.find((x) => x.key === key);
            const label = (blind && !revealed) ? "Model " + String.fromCharCode(65 + i) : m.name;
            const accent = m.ours && (!blind || revealed);
            return (
              <AnswerPanel key={key} model={m} label={label} version={m.version}
                accent={accent} blind={blind && !revealed} st={race[key]}
                anyEvidence={canShowEvidence} queryText={lastQuery}
                evidenceOpen={evidenceOpen}
                onShowEvidence={() => { setEvQ(evQ); setEvidenceOpen((o) => !o); }} />
            );
          })}
        </div>
      </div>

      {/* evidence overlay */}
      <EvidenceSection open={evidenceOpen && canShowEvidence} questionId={evQ} modelName={oursModel.name} />

      {/* scoreboard */}
      {t.showScoreboard && <Scoreboard models={activeModels} tally={tally} blind={blind} revealed={revealed} />}

      {/* footer */}
      <footer style={{ marginTop: "auto", padding: "14px 22px 18px", borderTop: "1px solid var(--border-subtle)",
        display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
        <span style={{ font: "400 12px var(--font-sans)", color: "var(--fg-3)" }}>
          IntuigenceAI P&amp;ID Agent · GPT-5 · Claude Opus 4.6 · Gemini 2.5 Pro
        </span>
        <span style={{ font: "400 12px var(--font-sans)", color: "var(--fg-4)" }}>·</span>
        <span style={{ font: "400 12px var(--font-sans)", color: "var(--fg-3)" }}>June 2026</span>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 7, marginLeft: "auto",
          font: "500 12px var(--font-sans)", color: "var(--fg-2)" }}>
          <span style={{ width: 6, height: 6, borderRadius: 999, background: "var(--success)" }} />
          Same files provided to all models
        </span>
      </footer>

      <UploadModal open={uploadOpen} onClose={() => setUploadOpen(false)}
        onComplete={(n) => setUploadedCount((c) => c + n)} />

      {/* Tweaks */}
      <TweaksPanel>
        <TweakSection label="Demo behavior" />
        <TweakToggle label="Blind mode by default" value={t.blindDefault} onChange={(v) => setTweak("blindDefault", v)} />
        <TweakToggle label="Show scoreboard" value={t.showScoreboard} onChange={(v) => setTweak("showScoreboard", v)} />
        <TweakSection label="Pacing" />
        <TweakRadio label="Stream speed" value={t.pace} options={["Calm", "Normal", "Snappy"]} onChange={(v) => setTweak("pace", v)} />
        <TweakSlider label="Our latency (honesty delay)" value={t.ourDelay} min={0} max={8} step={1} unit="s" onChange={(v) => setTweak("ourDelay", v)} />
        <TweakSection label="Appearance" />
        <TweakRadio label="Theme" value={t.theme} options={["Light", "Dark"]} onChange={(v) => setTweak("theme", v)} />
      </TweaksPanel>
    </div>
  );
}

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