/* global React, TelarGlyph, TelarColorVertical, TelarWordmark, LintelWordmarkSmall, useI18n, pickLoadingLine, Ticks */
const { useState: uS, useEffect: uE, useRef: uR, useMemo: uM, useCallback: uC } = React;

// Shared app state & screen machine. Variant-specific layouts import and render these pieces.
// Screens: gate | briefing | world | scenario | play | end | feedback | thankyou | idle | turncap | dailycap | notfound | server

const MAX_TURNS = 30;
const WORLD_ID = "alpha-cohort-1";

// Map an HTTP response (status, parsed body) to a screen/banner action.
// Returns { screen?, banner?, fatal? } — caller applies.
function classifyError(status, body) {
  if (status === 402) return { screen: "dailycap" };
  if (status === 404) return { screen: "notfound" };
  if (status === 409) {
    const err = body && body.error;
    if (err === "turn_cap_reached") return { screen: "turncap" };
    if (err === "session_idle_closed") return { screen: "idle" };
    return { screen: "server" };
  }
  if (status === 429) return { banner: "rate" };
  return { screen: "server" };
}

function useAppState({ lang }) {
  const [screen, setScreen] = uS("gate");
  const [scenario, setScenario] = uS(null); // scenario_id
  const [consent, setConsent] = uS(false);
  const [pwError, setPwError] = uS(null); // null | "invalid_code" | "already_used" | "network"
  const [pwBusy, setPwBusy] = uS(false);
  const [turns, setTurns] = uS([]); // [{kind: 'dm'|'player', text}]
  const [waiting, setWaiting] = uS(false);
  const [loadingLine, setLoadingLine] = uS(0);
  const [loadingLong, setLoadingLong] = uS(false);
  const [turnCount, setTurnCount] = uS(0); // 0..30 (0 = opening only)
  const [edgeBanner, setEdgeBanner] = uS(null); // "rate" | "idle14" | null
  const [testerId, setTesterId] = uS(null);
  const [sessionId, setSessionId] = uS(null);
  const [scenariosList, setScenariosList] = uS(null); // null | array | {error}
  const [openingScenarioTitle, setOpeningScenarioTitle] = uS("");

  const t = useI18n(lang);

  const resetSession = uC(() => {
    setScenario(null); setConsent(false); setTurns([]); setTurnCount(0);
    setWaiting(false); setEdgeBanner(null); setSessionId(null);
    setOpeningScenarioTitle("");
  }, []);

  const submitPassword = uC(async (val) => {
    const code = (val || "").trim();
    if (!code) {
      setPwError("invalid_code");
      return;
    }
    setPwBusy(true);
    setPwError(null);
    try {
      const res = await fetch("/api/gate", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ code }),
      });
      const body = await res.json().catch(() => ({}));
      if (res.status === 200 && body?.tester_id) {
        sessionStorage.setItem("telar.tester_id", body.tester_id);
        setTesterId(body.tester_id);
        setScreen("briefing");
        return;
      }
      if (res.status === 401 && body?.error === "already_used") {
        setPwError("already_used");
        return;
      }
      setPwError("invalid_code");
    } catch {
      setPwError("network");
    } finally {
      setPwBusy(false);
    }
  }, []);

  const loadScenarios = uC(async () => {
    setScenariosList(null);
    try {
      const res = await fetch(`/api/worlds/${encodeURIComponent(WORLD_ID)}/scenarios`);
      const body = await res.json().catch(() => ({}));
      if (!res.ok) {
        setScenariosList({ error: body?.error || "unreachable" });
        return;
      }
      const list = Array.isArray(body?.scenarios) ? body.scenarios : [];
      setScenariosList(list);
    } catch {
      setScenariosList({ error: "network" });
    }
  }, []);

  const beginScenario = uC(async () => {
    if (!scenario || !consent || waiting) return;
    setWaiting(true);
    setLoadingLine((prev) => pickLoadingLine(window.I18N[lang].play.loading, prev));
    setLoadingLong(false);
    const longTimer = setTimeout(() => setLoadingLong(true), 1200);
    try {
      const res = await fetch("/api/sessions", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          tester_id: testerId,
          world_id: WORLD_ID,
          scenario_id: scenario,
        }),
      });
      clearTimeout(longTimer);
      const body = await res.json().catch(() => ({}));
      if (!res.ok) {
        const { screen: next } = classifyError(res.status, body);
        setWaiting(false);
        if (next) setScreen(next);
        return;
      }
      const openingText = body?.opening?.narration || "";
      setSessionId(body?.session_id || null);
      setOpeningScenarioTitle(body?.scenario_title || "");
      setTurns([{ kind: "dm", text: openingText }]);
      setTurnCount(1);
      setWaiting(false);
      setScreen("play");
    } catch {
      clearTimeout(longTimer);
      setWaiting(false);
      setScreen("server");
    }
  }, [scenario, consent, waiting, testerId, lang]);

  const sendTurn = uC(async (input) => {
    const text = (input || "").trim();
    if (!text || waiting || !sessionId) return;
    setTurns((prev) => [...prev, { kind: "player", text }]);
    setWaiting(true);
    setLoadingLine((prev) => pickLoadingLine(window.I18N[lang].play.loading, prev));
    setLoadingLong(false);
    const longTimer = setTimeout(() => setLoadingLong(true), 1200);
    try {
      const res = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/turn`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ player_input: text }),
      });
      clearTimeout(longTimer);
      const body = await res.json().catch(() => ({}));
      if (!res.ok) {
        const { screen: next, banner } = classifyError(res.status, body);
        setWaiting(false);
        if (banner) setEdgeBanner(banner);
        if (next) setScreen(next);
        return;
      }
      const nextText = body?.narration || "";
      setTurns((prev) => [...prev, { kind: "dm", text: nextText }]);
      setTurnCount((n) => n + 1);
      setWaiting(false);
      setEdgeBanner(null);
    } catch {
      clearTimeout(longTimer);
      setWaiting(false);
      setScreen("server");
    }
  }, [waiting, sessionId, lang]);

  return {
    t, screen, setScreen,
    scenario, setScenario, consent, setConsent,
    pwError, pwBusy, submitPassword,
    turns, turnCount, waiting, loadingLine, loadingLong,
    beginScenario, sendTurn, resetSession,
    edgeBanner, setEdgeBanner,
    testerId, sessionId,
    scenariosList, loadScenarios, openingScenarioTitle,
  };
}

// -------- Screen components (shared rendering, variant styling via CSS classes) --------

function GateScreen({ v, state, lang }) {
  const { t, pwError, pwBusy, submitPassword } = state;
  const [val, setVal] = uS("");
  const inputRef = uR(null);
  uE(() => { inputRef.current && inputRef.current.focus(); }, []);
  const errMsg = pwError === "already_used"
    ? t.gate.error_already_used
    : pwError === "network"
    ? t.gate.error_network
    : pwError === "invalid_code"
    ? t.gate.error_bad_password
    : null;
  const disabled = val.length === 0 || pwBusy;
  return (
    <div className={`screen screen-gate v-${v}`}>
      <div className="gate-card">
        {v !== "b" && <Ticks/>}
        <div className="gate-glyph">
          <TelarColorVertical size={v==="c" ? 128 : 108} animate />
        </div>
        <h1 className="gate-heading">{t.gate.heading}</h1>
        <div className="gate-body read-col">
          {t.gate.body.map((p,i)=><p key={i}>{p}</p>)}
        </div>
        <form
          className={`gate-form ${pwError ? "shake" : ""}`}
          onSubmit={(e)=>{e.preventDefault(); if (!disabled) submitPassword(val);}}
        >
          <input
            ref={inputRef}
            type="password"
            placeholder={t.gate.placeholder}
            value={val}
            onChange={(e)=>setVal(e.target.value)}
            aria-label={t.gate.placeholder}
            disabled={pwBusy}
          />
          <button type="submit" className={`btn ${disabled ? "is-disabled":""}`} disabled={disabled}>
            {pwBusy ? t.feedback.sending : t.gate.enter}
          </button>
        </form>
        {errMsg && <div className="gate-error">{errMsg}</div>}
      </div>
    </div>
  );
}

function BriefingScreen({ v, state }) {
  const { t, consent, setConsent, setScreen } = state;
  return (
    <div className={`screen screen-briefing v-${v}`}>
      <div className="briefing-body read-col">
        {v === "a" && <Ticks/>}
        <div className="intro-eyebrow meta">{t.intro.briefing_eyebrow}</div>
        <h2 className="intro-heading">{t.intro.briefing_heading}</h2>
        <p className="briefing-intro narration"><em>{t.intro.briefing_intro}</em></p>
        <div className="expectation-inner">
          <ul>
            {t.intro.before.map((b,i)=><li key={i}><span className="dash">—</span> <span>{b}</span></li>)}
          </ul>
        </div>
        <div className="consent-row">
          <label className="consent-label">
            <input type="checkbox" checked={consent} onChange={(e)=>setConsent(e.target.checked)} />
            <span>{t.intro.consent_label}</span>
          </label>
          <div className="actions-row">
            <button
              className={`btn ${consent ? "" : "is-disabled"}`}
              disabled={!consent}
              onClick={()=>setScreen("world")}
            >
              {t.intro.briefing_continue}
            </button>
            {!consent && (
              <div className="begin-hint meta">{t.intro.begin_hint_consent}</div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

function WorldScreen({ v, state }) {
  const { t, setScreen } = state;
  return (
    <div className={`screen screen-world v-${v}`}>
      <div className="world-body read-col">
        {v === "a" && <Ticks/>}
        <div className="intro-eyebrow meta">{t.intro.title}</div>
        <h2 className="intro-heading">{t.intro.world_heading}</h2>
        <div className="narration">
          {t.intro.body.map((p,i)=><p key={i}>{p}</p>)}
        </div>
        <div className="actions-row">
          <button className="btn btn-back" onClick={()=>setScreen("briefing")}>{t.intro.back}</button>
          <button className="btn" onClick={()=>setScreen("scenario")}>{t.intro.continue}</button>
        </div>
      </div>
    </div>
  );
}

function ScenarioScreen({ v, state }) {
  const { t, scenario, setScenario, consent, beginScenario, setScreen, scenariosList, loadScenarios, waiting } = state;
  uE(() => { loadScenarios(); }, [loadScenarios]);
  const canBegin = !!scenario && consent && !waiting;

  let content;
  if (scenariosList === null) {
    content = <p className="narration"><em>{t.intro.scenarios_loading}</em></p>;
  } else if (scenariosList && !Array.isArray(scenariosList)) {
    content = (
      <p className="narration">
        <em>{t.intro.scenarios_error}</em>{" "}
        <button className="btn btn-back" onClick={loadScenarios} type="button">{t.errors.server_button}</button>
      </p>
    );
  } else if (scenariosList.length === 0) {
    content = <p className="narration"><em>{t.intro.scenarios_empty}</em></p>;
  } else {
    content = (
      <div className="scenario-grid frame-col">
        {scenariosList.map((s, i) => {
          const overlay = (t.intro.scenarios && t.intro.scenarios[s.scenario_id]) || {};
          const title = overlay.title || s.title;
          const hook = overlay.hook || s.hook || "";
          const location = overlay.location || s.location || "";
          return (
            <button
              key={s.scenario_id}
              className={`scenario-card ${scenario===s.scenario_id ? "selected":""}`}
              onClick={()=>setScenario(s.scenario_id)}
              type="button"
            >
              <div className="scenario-num meta">0{i+1}</div>
              <h3 className="scenario-title">{title}</h3>
              <p className="scenario-hook">{hook}</p>
              <div className="scenario-meta meta">
                <span>{location}</span>
              </div>
              <div className="scenario-pick ui">
                {scenario===s.scenario_id ? `— ${t.intro.selected}` : t.intro.pick_this}
              </div>
            </button>
          );
        })}
      </div>
    );
  }

  return (
    <div className={`screen screen-scenario v-${v}`}>
      <div className="scenario-body">
        {v === "a" && <Ticks/>}
        <div className="scenario-header read-col">
          <div className="intro-eyebrow meta">{t.intro.scenario_eyebrow}</div>
          <h2 className="intro-heading">{t.intro.scenario_heading}</h2>
        </div>
        {content}
        <div className="actions-row read-col">
          <button className="btn btn-back" onClick={()=>setScreen("world")}>{t.intro.back}</button>
          <button
            className={`btn ${canBegin ? "" : "is-disabled"}`}
            disabled={!canBegin}
            onClick={beginScenario}
          >
            {waiting ? t.feedback.sending : t.intro.confirm_scenario}
          </button>
          {!canBegin && !waiting && (
            <div className="begin-hint meta">{t.intro.begin_hint_scenario}</div>
          )}
        </div>
      </div>
    </div>
  );
}

function PlayScreen({ v, state }) {
  const { t, scenario, turns, turnCount, waiting, loadingLine, loadingLong, sendTurn, setScreen, edgeBanner, setEdgeBanner, openingScenarioTitle } = state;
  const [draft, setDraft] = uS("");
  const [phIdx, setPhIdx] = uS(0);
  const scrollRef = uR(null);
  uE(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [turns.length, waiting]);
  uE(() => {
    if (!waiting) setPhIdx((i)=>(i+1)%3);
  }, [waiting]);
  const scTitle = uM(() => {
    const overlay = t.intro.scenarios && t.intro.scenarios[scenario];
    if (overlay && overlay.title) return overlay.title;
    return openingScenarioTitle || "";
  }, [t, scenario, openingScenarioTitle]);
  const textareaRef = uR(null);
  const grow = uC(()=>{
    const el = textareaRef.current;
    if (!el) return;
    el.style.height = "auto";
    el.style.height = Math.min(180, el.scrollHeight) + "px";
  }, []);
  return (
    <div className={`screen screen-play v-${v}`}>
      <header className="play-header">
        <div className="play-header-left">
        </div>
        <div className="play-header-right meta">
          <span className="scenario-label">{scTitle}</span>
          <span className="sep">·</span>
          <span className="turn-label">{t.play.turn_of.replace("%n", Math.min(turnCount, MAX_TURNS))}</span>
          <button className="menu-btn" onClick={()=>setScreen("end")} title={t.play.end_session}>≡</button>
        </div>
      </header>

      {edgeBanner === "idle14" && (
        <div className="edge-banner read-col">
          <em>{t.edge.idle_warning}</em>
          <button className="banner-close" onClick={()=>setEdgeBanner(null)}>×</button>
        </div>
      )}
      {edgeBanner === "rate" && (
        <div className="edge-banner rate read-col">
          <em>{t.edge.rate_limit}</em>
        </div>
      )}

      <main className="play-main" ref={scrollRef}>
        <div className="transcript read-col">
          {turns.map((turn, i) => (
            turn.kind === "dm" ? (
              <div className="turn dm narration" key={i}>
                {turn.text.split("\n\n").map((p, j) => <p key={j}>{p}</p>)}
              </div>
            ) : (
              <div className="turn player" key={i}>
                <span className="dash">—</span>
                <em>{turn.text}</em>
              </div>
            )
          ))}
          {waiting && (
            <div className="loading-line">
              <em>{loadingLong ? t.play.loading_long : t.play.loading[loadingLine]}</em>
            </div>
          )}
        </div>
      </main>

      <footer className="play-input-bar">
        <div className="play-input-inner">
          <textarea
            ref={textareaRef}
            placeholder={t.play.input_placeholders[phIdx]}
            value={draft}
            disabled={waiting}
            onChange={(e)=>{setDraft(e.target.value); grow();}}
            onKeyDown={(e)=>{
              if (e.key === "Enter" && !e.shiftKey) {
                e.preventDefault();
                if (!waiting && draft.trim()) { sendTurn(draft); setDraft(""); setTimeout(grow,0); }
              }
            }}
            rows={1}
          />
          <button
            className={`btn ${(waiting || !draft.trim()) ? "is-disabled":""}`}
            disabled={waiting || !draft.trim()}
            onClick={()=>{ if (draft.trim() && !waiting) { sendTurn(draft); setDraft(""); setTimeout(grow,0);} }}
          >{t.play.send}</button>
        </div>
      </footer>
    </div>
  );
}

function EndScreen({ v, state }) {
  const { t, setScreen } = state;
  return (
    <div className={`screen screen-end v-${v}`}>
      <header className="play-header">
        <div className="play-header-left"></div>
      </header>
      <div className="end-body read-col">
        {v === "a" && <Ticks/>}
        <h2 className="heading">{t.end.heading}</h2>
        <p className="narration">{t.end.body}</p>
        <button className="btn" onClick={()=>setScreen("feedback")}>{t.end.button}</button>
        <p className="tail narration">{t.end.tail}</p>
      </div>
    </div>
  );
}

function FeedbackScreen({ v, state }) {
  const { t, setScreen, sessionId, testerId } = state;
  const [step, setStep] = uS(0);
  const [q1, setQ1] = uS("");
  const [q2, setQ2] = uS("");
  const [q3a, setQ3a] = uS(null);
  const [q3b, setQ3b] = uS("");
  const [q4, setQ4] = uS(null);
  const [q4m, setQ4m] = uS("");
  const [q5, setQ5] = uS("");
  const [err, setErr] = uS("");
  const [sending, setSending] = uS(false);

  const TOTAL = 5;
  const validateStep = (i) => {
    if (i === 0 && !q1.trim()) return t.feedback.errors.required;
    if (i === 1 && !q2.trim()) return t.feedback.errors.required;
    if (i === 2 && !q3a) return t.feedback.errors.scale;
    if (i === 3 && !q4) return t.feedback.errors.required;
    if (i === 3 && q4 === "maybe" && !q4m.trim()) return t.feedback.errors.maybe;
    if (i === 4 && !q5.trim()) return t.feedback.errors.required;
    return "";
  };

  const submit = async () => {
    if (!sessionId) return setScreen("thankyou");
    const answers = {
      recap: q1,
      anchor: q2,
      coherence_scale: q3a,
      coherence_follow: q3b,
      return_intent: q4,
      return_note: q4m,
      break: q5,
    };
    try {
      await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/feedback`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ tester_id: testerId, answers }),
      });
    } catch {
      // Feedback failure is non-blocking — thank the tester anyway.
    }
    setScreen("thankyou");
  };

  const next = () => {
    const e = validateStep(step);
    if (e) { setErr(e); return; }
    setErr("");
    if (step < TOTAL - 1) setStep(step + 1);
    else {
      setSending(true);
      submit();
    }
  };
  const prev = () => { setErr(""); if (step > 0) setStep(step - 1); };

  const stepLabel = (t.feedback.step_of || "Question %n of %t")
    .replace("%n", step + 1).replace("%t", TOTAL);

  return (
    <div className={`screen screen-feedback v-${v}`}>
      <header className="feedback-topbar">
        <div className="step-indicator meta">{stepLabel}</div>
      </header>
      <div className="feedback-body read-col">
        {v === "a" && <Ticks/>}
        <div className="step-progress" aria-hidden="true">
          {Array.from({length: TOTAL}).map((_,i)=>(
            <span key={i} className={`progress-tick ${i<=step?"on":""}`}/>
          ))}
        </div>
        {step === 0 && (
          <div className="q-block fade-enter">
            <div className="q-title narration">{t.feedback.q1.title}</div>
            <div className="q-hint"><em>{t.feedback.q1.hint}</em></div>
            <textarea value={q1} onChange={(e)=>setQ1(e.target.value)} placeholder={t.feedback.q1.placeholder} rows={4} autoFocus/>
          </div>
        )}
        {step === 1 && (
          <div className="q-block fade-enter">
            <div className="q-title narration">{t.feedback.q2.title}</div>
            <div className="q-hint"><em>{t.feedback.q2.hint}</em></div>
            <textarea value={q2} onChange={(e)=>setQ2(e.target.value)} placeholder={t.feedback.q2.placeholder} rows={4} autoFocus/>
          </div>
        )}
        {step === 2 && (
          <div className="q-block fade-enter">
            <div className="q-title narration">{t.feedback.q3.title}</div>
            <div className="scale-row">
              {[1,2,3,4,5].map(n=>(
                <button key={n}
                  className={`scale-pill ${q3a===n?"selected":""}`}
                  onClick={()=>setQ3a(n)} type="button">{n}</button>
              ))}
            </div>
            <div className="scale-labels meta">
              <span>{t.feedback.q3.scale[0]}</span>
              <span>{t.feedback.q3.scale[4]}</span>
            </div>
            {q3a !== null && q3a < 5 && (
              <div className="q-follow fade-enter">
                <div className="q-follow-title narration">{t.feedback.q3.follow_title}</div>
                <textarea value={q3b} onChange={(e)=>setQ3b(e.target.value)} placeholder={t.feedback.q3.follow_placeholder} rows={3}/>
              </div>
            )}
          </div>
        )}
        {step === 3 && (
          <div className="q-block fade-enter">
            <div className="q-title narration">{t.feedback.q4.title}</div>
            <div className="radio-list">
              {t.feedback.q4.options.map(opt=>(
                <label key={opt.id} className="radio-row">
                  <input type="radio" name="q4" checked={q4===opt.id} onChange={()=>setQ4(opt.id)}/>
                  <span className="radio-dot"/>
                  <span className="ui">{opt.label}</span>
                </label>
              ))}
            </div>
            {q4 === "maybe" && (
              <div className="q-follow fade-enter">
                <textarea value={q4m} onChange={(e)=>setQ4m(e.target.value)} placeholder={t.feedback.q4.maybe_placeholder} rows={3}/>
              </div>
            )}
          </div>
        )}
        {step === 4 && (
          <div className="q-block fade-enter">
            <div className="q-title narration">{t.feedback.q5.title}</div>
            <div className="q-hint"><em>{t.feedback.q5.hint}</em></div>
            <textarea value={q5} onChange={(e)=>setQ5(e.target.value)} placeholder={t.feedback.q5.placeholder} rows={4} autoFocus/>
          </div>
        )}
        {err && <div className="error"><em>{err}</em></div>}
        <div className="actions-row">
          <button className={`btn btn-back ${step===0?"is-disabled":""}`} onClick={prev} disabled={step===0}>
            {t.feedback.previous || t.intro.back}
          </button>
          <button className={`btn ${sending?"is-disabled":""}`} onClick={next} disabled={sending}>
            {sending ? t.feedback.sending : (step === TOTAL - 1 ? t.feedback.send : (t.feedback.next || t.intro.continue))}
          </button>
        </div>
      </div>
    </div>
  );
}

function ThankyouScreen({ v, state }) {
  const { t } = state;
  return (
    <div className={`screen screen-thankyou v-${v}`}>
      <header className="play-header">
        <div className="play-header-left"></div>
      </header>
      <div className="thankyou-body read-col">
        {v === "a" && <Ticks/>}
        <h2 className="heading">{t.thankyou.heading}</h2>
        <div className="narration">
          {t.thankyou.body.map((p,i)=><p key={i}>{p}</p>)}
        </div>
      </div>
    </div>
  );
}

function EdgeIdleScreen({ v, state }) {
  const { t, setScreen, resetSession } = state;
  return (
    <div className={`screen screen-edge v-${v}`}>
      <div className="edge-full read-col">
        <p className="narration"><em>{t.edge.idle_closed}</em></p>
        <button className="btn" onClick={()=>{ resetSession(); setScreen("briefing"); }}>{t.edge.idle_closed_button}</button>
      </div>
    </div>
  );
}

function EdgeTurnCapScreen({ v, state }) {
  const { t, setScreen } = state;
  return (
    <div className={`screen screen-edge v-${v}`}>
      <div className="edge-full read-col">
        <p className="narration"><em>{t.edge.turn_cap}</em></p>
        <button className="btn" onClick={()=>setScreen("feedback")}>{t.edge.turn_cap_button}</button>
      </div>
    </div>
  );
}

function EdgeDailyCapScreen({ v, state }) {
  const { t } = state;
  return (
    <div className={`screen screen-edge v-${v}`}>
      <div className="edge-full read-col">
        <p className="narration"><em>{t.edge.daily_cap}</em></p>
      </div>
    </div>
  );
}

function NotFoundScreen({ v, state }) {
  const { t, setScreen } = state;
  return (
    <div className={`screen screen-edge v-${v}`}>
      <div className="edge-full read-col">
        <p className="narration"><em>{t.errors.notfound}</em></p>
        <button className="btn" onClick={()=>setScreen("briefing")}>{t.errors.notfound_button}</button>
      </div>
    </div>
  );
}

function ServerErrorScreen({ v, state }) {
  const { t, setScreen } = state;
  return (
    <div className={`screen screen-edge v-${v}`}>
      <div className="edge-full read-col">
        <p className="narration"><em>{t.errors.server}</em></p>
        <button className="btn" onClick={()=>setScreen("play")}>{t.errors.server_button}</button>
      </div>
    </div>
  );
}

// -------- Router --------
function ScreenRouter({ v, state, lang }) {
  const { screen } = state;
  const key = `${v}-${lang}-${screen}`;
  switch (screen) {
    case "gate": return <GateScreen key={key} v={v} state={state} lang={lang}/>;
    case "briefing": return <BriefingScreen key={key} v={v} state={state}/>;
    case "world": return <WorldScreen key={key} v={v} state={state}/>;
    case "scenario": return <ScenarioScreen key={key} v={v} state={state}/>;
    case "intro": return <ScenarioScreen key={key} v={v} state={state}/>;
    case "play": return <PlayScreen key={key} v={v} state={state}/>;
    case "end": return <EndScreen key={key} v={v} state={state}/>;
    case "feedback": return <FeedbackScreen key={key} v={v} state={state}/>;
    case "thankyou": return <ThankyouScreen key={key} v={v} state={state}/>;
    case "idle": return <EdgeIdleScreen key={key} v={v} state={state}/>;
    case "turncap": return <EdgeTurnCapScreen key={key} v={v} state={state}/>;
    case "dailycap": return <EdgeDailyCapScreen key={key} v={v} state={state}/>;
    case "notfound": return <NotFoundScreen key={key} v={v} state={state}/>;
    case "server": return <ServerErrorScreen key={key} v={v} state={state}/>;
    default: return <GateScreen key={key} v={v} state={state} lang={lang}/>;
  }
}

Object.assign(window, { useAppState, ScreenRouter, BriefingScreen, WorldScreen, ScenarioScreen, MAX_TURNS, DEFAULT_PASSWORD });
