/* creator-alert.jsx — the RED ALERT that guards the creator role.

   Granting someone Creator hands over every server, every setting, the assistant,
   the usage dashboard — and the power to hand it on again. There is no undo that
   matters: by the time you regret it they can already act. So this is deliberately
   the loudest, slowest, most obstructive thing in the product. Three independent
   gates, each of which needs a DIFFERENT piece of knowledge from you:

     1 · BRIEFING     read what you are giving away; the button arms after 5s
     2 · IDENTITY     type the person's name exactly as it's on file
     3 · AUTHORISE    type the phrase, then press and HOLD for three seconds

   Nothing here is decorative. The countdown exists so a fast double-click can't
   carry you through; the name gate exists so you cannot grant it to the wrong row;
   the hold exists so the final act is continuous and intentional. The API re-checks
   all three (POST /api/users/:id/creator) — this is the ceremony, not the security.

   Rendered by settings.jsx; only ever mounted for a caller who IS a creator. */

const CREATOR_PHRASE = "GRANT CREATOR";
const HOLD_MS = 3000;
const ARM_S = 5;

// What the person actually receives. Written as consequences, not capabilities —
// "every server" lands where "server_scope: ['*']" doesn't.
const CREATOR_POWERS = [
  ["Every server", "All servers on the platform, present and future — including clients they have never been given access to."],
  ["Every setting", "Channels, analysts, branding, scoring basis, recap publishing, live market data, custom domains."],
  ["Every record", "Read, edit and delete any analyst's trades in any server. Reset a server's entire history."],
  ["Your tools", "The assistant, the usage dashboard, ratings, bug reports, the audit log, the whole user list."],
  ["This power", "They can grant Creator to someone else — and you cannot take it back from them without their row."],
];

function CreatorGrantAlarm({ target, onCancel, onGranted }) {
  const [stage, setStage] = useState(1);
  const [arm, setArm] = useState(ARM_S);
  const [typedName, setTypedName] = useState("");
  const [typedPhrase, setTypedPhrase] = useState("");
  const [hold, setHold] = useState(0);
  const [muted, setMuted] = useState(false);
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState("");
  const holdRef = useRef(null);

  const realName = String((target && target.name) || "").trim();
  const nameOk = realName && typedName.trim().toLowerCase() === realName.toLowerCase();
  const phraseOk = typedPhrase.trim().toUpperCase() === CREATOR_PHRASE;

  // ── The klaxon ────────────────────────────────────────────────────────────
  // Two-tone, synthesised — no asset to ship and nothing to fetch (the CSP would
  // block it anyway). Built on the click that opened the dialog, so autoplay
  // policy is satisfied. Fails silently where WebAudio isn't available.
  useEffect(() => {
    if (muted) return;
    let ctx = null, osc = null, gain = null, timer = null, dead = false;
    try {
      const AC = window.AudioContext || window.webkitAudioContext;
      if (!AC) return;
      ctx = new AC();
      osc = ctx.createOscillator();
      gain = ctx.createGain();
      osc.type = "sawtooth";
      osc.frequency.value = 440;
      gain.gain.value = 0.0001;
      osc.connect(gain); gain.connect(ctx.destination);
      osc.start();
      let hi = false;
      const beat = () => {
        if (dead || !ctx) return;
        const t = ctx.currentTime;
        hi = !hi;
        osc.frequency.setValueAtTime(hi ? 622 : 440, t);
        gain.gain.cancelScheduledValues(t);
        gain.gain.setValueAtTime(0.0001, t);
        gain.gain.exponentialRampToValueAtTime(0.055, t + 0.04);
        gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.46);
      };
      beat();
      timer = setInterval(beat, 620);
    } catch (_) { /* no audio → the visual alarm carries it */ }
    return () => {
      dead = true;
      if (timer) clearInterval(timer);
      try { if (osc) osc.stop(); } catch (_) {}
      try { if (ctx) ctx.close(); } catch (_) {}
    };
  }, [muted]);

  // Stage 1 arming countdown — you cannot click through the briefing.
  useEffect(() => {
    if (stage !== 1 || arm <= 0) return;
    const t = setTimeout(() => setArm(a => a - 1), 1000);
    return () => clearTimeout(t);
  }, [stage, arm]);

  // Escape always backs out, at any stage.
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape" && !busy) onCancel(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [busy, onCancel]);

  // ── Hold to authorise ─────────────────────────────────────────────────────
  function startHold() {
    if (!phraseOk || busy || holdRef.current) return;
    const t0 = (window.performance && performance.now()) || Date.now();
    const tick = () => {
      const now = (window.performance && performance.now()) || Date.now();
      const p = Math.min(1, (now - t0) / HOLD_MS);
      setHold(p);
      if (p >= 1) { holdRef.current = null; submit(); return; }
      holdRef.current = requestAnimationFrame(tick);
    };
    holdRef.current = requestAnimationFrame(tick);
  }
  function endHold() {
    if (holdRef.current) { cancelAnimationFrame(holdRef.current); holdRef.current = null; }
    setHold(0);
  }
  useEffect(() => () => { if (holdRef.current) cancelAnimationFrame(holdRef.current); }, []);

  async function submit() {
    setBusy(true); setErr("");
    try {
      // tapeSend throws on a non-2xx, carrying the API's own message — which is
      // exactly what should be shown here ("the name typed does not match…").
      // The content-type is required: express.json() ignores a body without it,
      // and the three confirmations would arrive empty.
      const r = await tapeSend(apiBase() + `/api/users/${target.discord_user_id}/creator`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ acknowledged: true, target_name: realName, confirm: CREATOR_PHRASE }),
      });
      const j = await r.json().catch(() => ({}));
      onGranted(j.user);
    } catch (e) {
      setErr(e.message || "The grant failed."); setBusy(false); setHold(0);
    }
  }

  const stages = ["Briefing", "Identity", "Authorise"];

  return ReactDOM.createPortal(
    <div className="klaxon" role="alertdialog" aria-modal="true" aria-label="Grant the Creator role">
      {/* Sweeping beacons + hazard tape + scanlines. Purely atmospheric, and all
          of it is switched off under prefers-reduced-motion. */}
      <div className="klaxon-beacon left" aria-hidden="true" />
      <div className="klaxon-beacon right" aria-hidden="true" />
      <div className="klaxon-scan" aria-hidden="true" />
      <div className="klaxon-pulse" aria-hidden="true" />
      <div className="klaxon-tape top" aria-hidden="true" />
      <div className="klaxon-tape bottom" aria-hidden="true" />

      <div className="klaxon-panel" onMouseDown={(e) => e.stopPropagation()}>
        <div className="klaxon-head">
          <span className="klaxon-siren">{I("alert", { size: 16 })}</span>
          <span className="klaxon-title">Creator role · escalation</span>
          <button className="klaxon-mute" type="button" onClick={() => setMuted(m => !m)}
            title={muted ? "Sound on" : "Sound off"}>{muted ? "SOUND OFF" : "SOUND ON"}</button>
        </div>

        <div className="klaxon-steps">
          {stages.map((s, i) => (
            <span key={s} className={cx("klaxon-step", stage === i + 1 && "on", stage > i + 1 && "done")}>
              <b>{i + 1}</b>{s}
            </span>
          ))}
        </div>

        <div className="klaxon-target">
          <div className="klaxon-target-l">Granting to</div>
          <div className="klaxon-target-n">{realName || "— unnamed account —"}</div>
          <div className="klaxon-target-id mono">{target.discord_user_id} · currently {target.role || "viewer"}</div>
        </div>

        {stage === 1 && (
          <>
            <p className="klaxon-lede">
              You are about to make this person a <strong>Creator</strong> — the same role you hold.
              This is not an administrator. Read what it means:
            </p>
            <ul className="klaxon-powers">
              {CREATOR_POWERS.map(([h, d]) => (
                <li key={h}><span>{h}</span>{d}</li>
              ))}
            </ul>
            <div className="klaxon-actions">
              <button className="btn" onClick={onCancel}>Stop</button>
              <button className="btn klaxon-go" disabled={arm > 0} onClick={() => setStage(2)}>
                {arm > 0 ? `I understand · ${arm}` : "I understand — continue"}
              </button>
            </div>
          </>
        )}

        {stage === 2 && (
          <>
            <p className="klaxon-lede">
              Confirmation two of three. Type this account's name <strong>exactly</strong> as it appears
              above — so this can never land on the row next to the one you meant.
            </p>
            <input className="input mono klaxon-in" autoFocus value={typedName}
              onChange={(e) => setTypedName(e.target.value)} placeholder={realName ? "Type the name" : "This account has no name on file"}
              disabled={!realName} spellCheck={false} autoComplete="off" />
            {!realName && <div className="klaxon-err">Give this account a name first — the confirmation can't be checked without one.</div>}
            <div className="klaxon-actions">
              <button className="btn" onClick={() => { setStage(1); setTypedName(""); }}>Back</button>
              <button className="btn klaxon-go" disabled={!nameOk} onClick={() => setStage(3)}>
                {nameOk ? "Name matches — continue" : "Type the name to continue"}
              </button>
            </div>
          </>
        )}

        {stage === 3 && (
          <>
            <p className="klaxon-lede">
              Last gate. Type <code className="mono">{CREATOR_PHRASE}</code>, then press and
              <strong> hold</strong> the button for three seconds.
            </p>
            <input className="input mono klaxon-in" autoFocus value={typedPhrase}
              onChange={(e) => setTypedPhrase(e.target.value)} placeholder={CREATOR_PHRASE}
              spellCheck={false} autoComplete="off" />
            {err && <div className="klaxon-err">{err}</div>}
            <div className="klaxon-actions">
              <button className="btn" onClick={() => { setStage(2); setTypedPhrase(""); }} disabled={busy}>Back</button>
              <button
                className={cx("btn klaxon-hold", phraseOk && "live", busy && "busy")}
                disabled={!phraseOk || busy}
                onPointerDown={startHold} onPointerUp={endHold}
                onPointerLeave={endHold} onPointerCancel={endHold}
              >
                <span className="klaxon-hold-fill" style={{ transform: `scaleX(${busy ? 1 : hold})` }} />
                <span className="klaxon-hold-t">
                  {busy ? "Granting…" : !phraseOk ? "Type the phrase" : hold > 0 ? "Keep holding…" : "Hold to grant Creator"}
                </span>
              </button>
            </div>
          </>
        )}
      </div>
    </div>,
    document.body
  );
}

Object.assign(window, { CreatorGrantAlarm, CREATOR_PHRASE });
