/* State 376 — the brain. No meters on screen. The room keeps the count. */
const { useState, useEffect, useRef } = React;

function clamp(v, lo = 0, hi = 100) { return Math.max(lo, Math.min(hi, v)); }

function App() {
  const D = window.S376.DOSSIERS;
  const N = D.length;

  const [phase, setPhase] = useState("archive"); // archive|dossier|stamping|result|inter|recog|end|flash
  const [index, setIndex] = useState(0);
  const [verdict, setVerdict] = useState(null);
  const [resultText, setResultText] = useState("");
  const [inter, setInter] = useState(null);
  const [timeLeft, setTimeLeft] = useState(null);
  const [muted, setMuted] = useState(() => localStorage.getItem("s376_muted") === "1");
  const [flash, setFlash] = useState(false);
  const [shake, setShake] = useState(false);
  const [recommend, setRecommend] = useState(null);
  const [mobile, setMobile] = useState(() => /[?&]m=1/.test(location.search) || window.matchMedia("(max-width:860px)").matches);
  useEffect(() => {
    if (/[?&]m=1/.test(location.search)) return;
    const mq = window.matchMedia("(max-width:860px)");
    const h = (e) => setMobile(e.matches);
    mq.addEventListener ? mq.addEventListener("change", h) : mq.addListener(h);
    return () => { mq.removeEventListener ? mq.removeEventListener("change", h) : mq.removeListener(h); };
  }, []);

  const [res, setRes] = useState({ loyalty: 50, sanity: 78, suspicion: 12, network: 0 });
  const decisions = useRef([]);                 // {d, verdict, dt}
  const shownAt = useRef(0);
  const seen = useRef({});                       // interstitials already shown
  const colleagueVerdict = useRef(null);
  const examined = useRef(0);                    // how often the operator looked closer

  // push audio state on every resource change
  useEffect(() => { window.S376Audio.setState(res); }, [res]);
  useEffect(() => { window.S376Audio.setMuted(muted); localStorage.setItem("s376_muted", muted ? "1" : "0"); }, [muted]);

  // ----- derived diegetic numbers -----
  const processed = decisions.current.length;
  const arrested = decisions.current.filter(x => x.verdict === "ARREST").length;
  const cleared = decisions.current.filter(x => x.verdict === "CLEAR").length;
  const monitored = decisions.current.filter(x => x.verdict === "MONITOR").length;
  const avgSec = processed ? decisions.current.reduce((a, x) => a + x.dt, 0) / processed : 0;
  const efficiency = processed ? clamp(Math.round(102 - avgSec * 0.5 + (arrested - cleared) * 3), 35, 100) : 100;
  const clockMin = 8 * 60 + processed * 50;
  const clock = String(Math.floor(clockMin / 60)).padStart(2, "0") + ":" + String(clockMin % 60).padStart(2, "0");

  // ----- begin -----
  function begin() {
    window.S376Audio.unlock();
    window.S376Audio.setMuted(muted);
    goToDossier(0);
  }

  function goToDossier(i) {
    setVerdict(null); setResultText("");
    if (i === 8) { // the colleague — cut the sound, recognition beat
      window.S376Audio.silenceCut();
      setPhase("recog");
      return;
    }
    setIndex(i);
    setPhase("dossier");
    shownAt.current = Date.now();
    if (D[i].timed) setTimeLeft(D[i].timed); else setTimeLeft(null);
  }

  function openColleague() {
    setIndex(8);
    setPhase("dossier");
    shownAt.current = Date.now();
    setTimeLeft(D[8].timed);
  }

  // ----- timed pressure on dossier 9 -----
  useEffect(() => {
    if (phase !== "dossier" || timeLeft == null) return;
    if (timeLeft <= 0) { resolve("MONITOR", true); return; }
    const t = setTimeout(() => setTimeLeft(tl => (tl == null ? null : tl - 1)), 1000);
    return () => clearTimeout(t);
  }, [phase, timeLeft]);

  // ----- the stamp -----
  function resolve(v, timedOut = false) {
    const d = D[index];
    const tone = v === "ARREST" ? "red" : v === "MONITOR" ? "amber" : "blue";
    setVerdict(v);
    setTimeLeft(null);
    setPhase("stamping");
    window.S376Audio.stamp(tone);
    setShake(true); setTimeout(() => setShake(false), 340);
    if (v === "ARREST" && d.deviance < 0.5 && !d.colleague) setTimeout(() => window.S376Audio.scream(), 850);

    const dt = Math.max(1, (Date.now() - shownAt.current) / 1000);
    decisions.current.push({ d, verdict: v, dt });
    if (d.colleague) colleagueVerdict.current = v;

    // resource deltas
    let { loyalty, sanity, suspicion, network } = res;
    const innocent = d.deviance < 0.5;
    if (v === "ARREST") {
      loyalty += d.colleague ? 14 : 7;
      sanity -= d.colleague ? 22 : (innocent ? 12 : 6);
      suspicion -= d.colleague ? 6 : 3;
      if (d.network) network -= 16;
    } else if (v === "MONITOR") {
      loyalty += 2; sanity -= d.colleague ? 8 : 3; suspicion += d.colleague ? 4 : 1;
    } else { // CLEAR
      loyalty -= d.colleague ? 8 : 5;
      sanity += d.colleague ? 6 : 5;
      suspicion += d.colleague ? 16 : 5;
      network += d.network ? 22 : (d.colleague ? 20 : 3);
      if (d.test) { suspicion += 18; loyalty -= 6; }
      if (!d.test && !d.network && !d.colleague && d.deviance > 0.85) { suspicion += 8; loyalty -= 7; }
    }
    const next = { loyalty: clamp(loyalty), sanity: clamp(sanity), suspicion: clamp(suspicion), network: clamp(network) };

    // pattern-detected red flash on a suspicion spike
    if (next.suspicion - res.suspicion >= 12) {
      setTimeout(() => { setFlash(true); setTimeout(() => setFlash(false), 520); }, 700);
    }

    setResultText(timedOut
      ? "The decision window closed. " + d.result["MONITOR"]
      : d.result[v]);

    setTimeout(() => { setRes(next); setPhase("result"); }, 1350);
  }

  // ----- advance after a result -----
  function next() {
    const nextIndex = index + 1;
    if (nextIndex >= N) { setPhase("end"); return; }
    const item = interstitialBefore(nextIndex);
    if (item) { setInter(item); setPhase("inter"); return; }
    window.S376Audio.paper();
    goToDossier(nextIndex);
  }

  function afterInter() {
    setInter(null);
    window.S376Audio.paper();
    goToDossier(index + 1);
  }

  // ----- scheduled slips (warmth + the reactive memo) -----
  function interstitialBefore(i) {
    const I = window.S376.INTERSTITIALS;
    if (i === 2 && !seen.current.cw) { seen.current.cw = 1; return I.coworkerNote; }
    if (i === 4 && !seen.current.dr) { seen.current.dr = 1; return I.daughterDrawing; }
    if (i === 5 && efficiency < 80 && !seen.current.qp) { seen.current.qp = 1; return I.quotaPush; }
    if (i === 7 && !seen.current.memo) { seen.current.memo = 1; return buildMemo(); }
    return null;
  }

  // reactive Internal Affairs memo — specific to what the player actually did
  function buildMemo() {
    const clearedTest = decisions.current.some(x => x.d.test && x.verdict === "CLEAR");
    const approvalRate = Math.round((arrested / Math.max(1, processed)) * 100);
    let body;
    if (examined.current >= 5) {
      body = "You cross-referenced " + examined.current + " files this morning against a section mean of one. Thoroughness is admirable in a clerk and dangerous in a believer. We are still deciding which you are. — I.A.";
    } else if (clearedTest) {
      body = "Re: File 00-0000. That file was not a citizen; it was an instrument. You released it. "
        + "Internal Affairs thanks you for the clarity. Your designation has been moved to a shorter list. — I.A.";
    } else if (cleared >= 3) {
      body = "A review of your shift shows " + cleared + " RELEASE verdicts against a section mean of 2. "
        + "Mercy is a pattern, and patterns are our work, not yours. Your latitude is hereby narrowed. — I.A.";
    } else if (arrested >= 5) {
      body = "Throughput exemplary: " + approvalRate + "% DETAIN, decisions averaging "
        + Math.round(avgSec) + " seconds. Your rhythm has been recommended as a training model for incoming operators. — I.A.";
    } else {
      body = "Your decision latency runs long against the section mean. Hesitation is not neutral; it is a reading we can take. "
        + "Proceed with the confidence the State has placed in you. — I.A.";
    }
    return { type: "memo", from: "INTERNAL AFFAIRS · floor 4 · do not reply", body };
  }

  // ----- ending classification -----
  function ending() {
    const cv = colleagueVerdict.current;
    if (cv === "CLEAR") return {
      tag: "THE EVIDENCE",
      line: "You cleared her. Somewhere your own file gained a page. You did the human thing, and the State filed it under the same heading it always does.",
    };
    if (cv === "ARREST") return {
      tag: "THE EXEMPLARY OPERATOR",
      line: "You stamped her DETAIN and your hand did not shake, because by then it knew the motion. That ease is the most expensive thing you will ever own.",
    };
    return {
      tag: "THE DEFERRAL",
      line: "You could not do it and could not not. The system does not defer. She was taken anyway; you only kept your hands clean of the verb.",
    };
  }

  function dreamLine() {
    const s = res.sanity;
    if (s > 60) return "That night you dream in colour — a classroom, a question on a wall, a lullaby in a language no one made you fear. You wake still able to tell the difference. That is not nothing. Hold it.";
    if (s > 32) return "That night the dream is grey and procedural. Faces arrive pre-redacted. You file them anyway, in your sleep, efficiently. You wake a little harder to read.";
    return "That night there are no dreams. Only the hum, and the count, and the clean surface of a desk with nothing personal left on it. You wake on time. You are, by every measure the State keeps, well.";
  }

  function replay() {
    decisions.current = []; seen.current = {}; colleagueVerdict.current = null; examined.current = 0;
    setRes({ loyalty: 50, sanity: 78, suspicion: 12, network: 0 });
    setVerdict(null); setResultText(""); setInter(null); setTimeLeft(null);
    setIndex(0); setPhase("archive");
  }

  // ----- render -----
  const sceneFilter = {
    filter: `saturate(${28 + (res.sanity / 100) * 86}%) brightness(${0.82 + (res.sanity / 100) * 0.2}) contrast(${1 + (1 - res.sanity / 100) * 0.06})`,
  };

  if (phase === "archive") {
    return <div className="stage-inner archive-stage"><ArchiveIntro onBegin={begin} /></div>;
  }
  if (phase === "end") {
    const e = ending();
    return <div className="stage-inner"><EndCard stats={{ processed, arrested, monitored, cleared, efficiency, res }} ending={e} dream={dreamLine()} onReplay={replay} /></div>;
  }

  const d = D[index];

  const dossierEl = phase === "recog"
    ? <Recognition onContinue={openColleague} />
    : <Dossier key={index} d={d} index={index} phase={phase} verdict={verdict} sanity={res.sanity}
        resultText={resultText} onVerdict={resolve} onNext={next} timeLeft={timeLeft}
        digs={window.S376.DIGS[d.id] || {}} onExamine={() => { examined.current++; }} />;
  const stampsEl = phase === "dossier"
    ? <Stamps onPick={resolve} disabled={false} recommend={d.aiRec} hint={setRecommend} />
    : null;
  const overlays = (
    <>
      <div className="surv-tint" style={{ opacity: clamp(res.suspicion - 25, 0, 70) / 100 }} />
      <div className="vignette" />
      <div className={"red-flash" + (flash ? " on" : "")} />
      {phase === "inter" && <Interstitial item={inter} onContinue={afterInter} />}
      <button className="mute-btn" onClick={() => setMuted(m => !m)} title="sound">{muted ? "\u266a\u0338" : "\u266a"}</button>
    </>
  );

  if (mobile) {
    return (
      <div className="stage-inner m">
        <div className="m-room">
          <div className="m-scene" style={sceneFilter}>
            <MobileChrome clock={clock} quota={N} processed={processed} efficiency={efficiency}
              suspicion={res.suspicion} ticker={window.S376.TICKER} />
            <div className="m-main">{dossierEl}{phase !== "recog" && <MobileDesk sanity={res.sanity} />}</div>
            {stampsEl && <div className="m-stampbar">{stampsEl}</div>}
          </div>
          {overlays}
        </div>
      </div>
    );
  }

  return (
    <div className="stage-inner">
      <div className={"room" + (shake ? " shaking" : "")}>
        <div className="scene" style={sceneFilter}>
          <ChromeBar clock={clock} quota={N} processed={processed} efficiency={efficiency}
            loyalty={res.loyalty} ticker={window.S376.TICKER} suspicion={res.suspicion} />
          <Wall sanity={res.sanity} tod={Math.min(1, processed / 8)} />
          <div className="desk">
            <div className="lamp-pool" />
            <Tray side="in" count={N - processed} label="PENDING" />
            <Tray side="out" count={processed} label="FILED" />
            <DeskItems sanity={res.sanity} loyalty={res.loyalty} />
            <div className="nameplate"><span>OFFICER&nbsp;1187</span></div>
            {dossierEl}
            {stampsEl}
          </div>
        </div>
        {overlays}
      </div>
    </div>
  );
}

/* ---------- compact mobile desk: the human layer, decaying ---------- */
function MobileDesk({ sanity }) {
  const h = sanity / 100;
  const decay = { filter: `grayscale(${(1 - h) * 100}%) saturate(${40 + h * 80}%)`, opacity: Math.max(0.18, h) };
  return (
    <div className="m-desk">
      <div className="m-lamp" />
      <div className="m-photo" style={decay}>
        {sanity > 38
          ? <div className="m-photo-face"><span /><span className="sm" /></div>
          : <div className="m-photo-sealed">RECORD<br/>SEALED</div>}
      </div>
      <div className="m-plant" style={{ filter: `grayscale(${(1 - h) * 100}%)`, opacity: Math.max(0.18, h) }}>
        <i className="l1" /><i className="l2" /><i className="l3" /><b />
      </div>
      <div className="m-nameplate">OFFICER 1187 · DESK 7</div>
    </div>
  );
}

/* ---------- compact mobile chrome ---------- */
function MobileChrome({ clock, quota, processed, efficiency, suspicion, ticker }) {
  return (
    <>
      <div className="m-chrome">
        <Emblem size={18} color="#b9c4bb" />
        <div className="mc-id">DCC · 376<span>SECTION 2 · DESK 7</span></div>
        <div className="mc-spacer" />
        <div className="mc-read"><b>{processed}/{quota}</b><span>QUOTA</span></div>
        <div className="mc-read"><b style={{ color: efficiency >= 90 ? "#9bd29b" : efficiency >= 70 ? "#cdbf9b" : "#c98b6b" }}>{efficiency}%</b><span>EFFIC.</span></div>
        <div className="mc-read"><b>{clock}</b><span>SHIFT</span></div>
        <div className={"eye" + (suspicion > 45 ? " eye-watch" : "")}><Emblem size={20} color={suspicion > 45 ? "#7fc6e0" : "#9aa39a"} /></div>
      </div>
      <div className="m-ticker"><div className="ticker-track">{[...ticker, ...ticker].map((t, i) => <span key={i}>{t}</span>)}</div></div>
    </>
  );
}

/* ---------- end-of-shift card: the archivist returns, past tense ---------- */
function EndCard({ stats, ending, dream, onReplay }) {
  const { processed, arrested, monitored, cleared, efficiency, res } = stats;
  return (
    <div className="endcard">
      <div className="archive-scan" />
      <div className="endcard-inner">
        <div className="end-kicker">END OF SHIFT · TERMINAL LOG CLOSED</div>
        <h2 className="end-tag">{ending.tag}</h2>

        <div className="end-tally">
          <div className="t"><b>{processed}</b><span>CITIZENS PROCESSED</span></div>
          <div className="t"><b className="rec-red">{arrested}</b><span>DETAINED</span></div>
          <div className="t"><b className="rec-amber">{monitored}</b><span>OBSERVED</span></div>
          <div className="t"><b className="rec-blue">{cleared}</b><span>RELEASED</span></div>
          <div className="t"><b>{efficiency}%</b><span>EFFICIENCY</span></div>
        </div>

        <p className="end-line">{ending.line}</p>
        <p className="end-dream">{dream}</p>

        <div className="end-rule" />

        <p className="archivist">
          <b>Archivist's note.</b> The terminal showed this operator no score. It kept the count anyway,
          as terminals do — the numbers above were never on their screen. The Directorate of Civic Compliance
          fell four years after this shift. It fell the way these things fall: slowly, and then between one
          Tuesday and the next, when enough hands stopped stamping at once. The threshold, the historians tell us,
          was roughly three and a half in every hundred. Never more. Never less.
        </p>
        <p className="archivist past">
          This record survives because the regime did not. The appendix is written in the past tense.
          You are holding the proof.
        </p>

        <div className="end-actions">
          <button className="begin-btn" onClick={onReplay}>REVIEW THE SHIFT AGAIN</button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { App, EndCard });
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
