// WRAlarm — sounds the bell when a new WR lands on any tracked board.
//
// What it watches:
//   window.RUN_DATA.recentActivity (filled by srapi.js after enrichment)
//   — every leaderboard row, with submission timestamps and place numbers.
//
// What counts as a NEW WR:
//   - place === 1 (someone took #1)
//   - row.runner !== mirrorsedger (we don't alert on our own runs — the
//     site is for visitors, and a self-WR would be self-congratulatory noise)
//   - row.runUrl/timestamp not in our localStorage "seen" set
//
// What happens when one fires:
//   1. A red curtain sweeps across the screen (fixed, top-of-viewport).
//   2. The browser tab title flashes (cycles between the real title and
//      "🔴 NEW WR — <level>") until the tab regains focus.
//   3. The favicon swaps to a red-dot variant (same idea — visible in
//      pinned tabs / tab bars).
//   4. A small audio bell plays via WebAudio (no asset needed).
//   5. A toast card appears in the top-right with the WR details and a
//      link to the run on speedrun.com. The user can click "Mute alerts"
//      to silence future alarms (persisted).
//   6. If the visitor has granted Notifications permission, a system
//      notification fires — useful when the tab is backgrounded.
//
// Bootstrap behaviour:
//   On the very first visit, we don't fire — we just record the current
//   set of WRs as "seen" and wait for the NEXT one. Otherwise every new
//   visitor would get blasted with a stale alarm.
//
// All of this is opt-in to the extent that audio + notifications need
// user interaction. The visual sweep + title flash always works.

const SEEN_LS_KEY = "mirrorsedger:seenWRs";
const MUTED_LS_KEY = "mirrorsedger:wrMuted";
// Bumped from "wrBootstrapped" — earlier versions seeded their seen set from
// a smaller activity feed, so when the feed later grew (back-filled places,
// more recent-runs pages, etc.) every previously-unseen WR fired at once.
// Bumping the key forces a one-time re-seed against the current feed.
const BOOTSTRAPPED_LS_KEY = "mirrorsedger:wrBootstrapped:v2";

// Only fire alarms for WRs that landed within this many days. Anything
// older is treated as historical and silently added to the seen set on
// bootstrap or first sight — it shouldn't notify the user.
const WR_FRESHNESS_DAYS = 7;

function readSeen() {
  try {
    const raw = localStorage.getItem(SEEN_LS_KEY);
    if (!raw) return new Set();
    return new Set(JSON.parse(raw));
  } catch (_) { return new Set(); }
}
function writeSeen(set) {
  try { localStorage.setItem(SEEN_LS_KEY, JSON.stringify([...set])); } catch (_) {}
}

// A stable key for a WR row. We use the run URL when available (it's
// unique per submission on speedrun.com) and fall back to a tuple of
// level + runner + timeMs + ts.
function wrKey(row) {
  if (row.runUrl) return row.runUrl;
  return `${row.level}|${row.runner}|${row.timeMs}|${row.ts}`;
}

// Generate a 32×32 red-dot favicon as a data URL — stamped over the
// existing logo so the user instantly clocks "something happened" in
// the tab bar even when they're not looking at the page.
function buildAlarmFaviconDataURL() {
  const canvas = document.createElement("canvas");
  canvas.width = 32; canvas.height = 32;
  const ctx = canvas.getContext("2d");
  // Background — the brand black square so the dot reads against it.
  ctx.fillStyle = "#0A0A0A";
  ctx.fillRect(0, 0, 32, 32);
  // White slash echoing the brand mark — keeps it recognisable at a glance.
  ctx.strokeStyle = "#FFFFFF";
  ctx.lineWidth = 3;
  ctx.beginPath();
  ctx.moveTo(6, 26); ctx.lineTo(26, 6);
  ctx.stroke();
  // Red dot indicator — center top-right.
  ctx.fillStyle = "#FF0033";
  ctx.beginPath();
  ctx.arc(24, 8, 6, 0, Math.PI * 2);
  ctx.fill();
  return canvas.toDataURL("image/png");
}

// Tiny WebAudio bell — two-note ding, no asset required. We construct
// the audio context lazily because some browsers throw if you create
// one before the user has interacted with the page.
function playBell() {
  try {
    const Ctx = window.AudioContext || window.webkitAudioContext;
    if (!Ctx) return;
    const ctx = new Ctx();
    const now = ctx.currentTime;
    function tone(freq, start, dur, peak = 0.18) {
      const osc = ctx.createOscillator();
      const gain = ctx.createGain();
      osc.type = "sine";
      osc.frequency.value = freq;
      gain.gain.setValueAtTime(0, now + start);
      gain.gain.linearRampToValueAtTime(peak, now + start + 0.02);
      gain.gain.exponentialRampToValueAtTime(0.0001, now + start + dur);
      osc.connect(gain).connect(ctx.destination);
      osc.start(now + start);
      osc.stop(now + start + dur + 0.05);
    }
    // Mirror's Edge-flavoured bell: a clean rising fifth.
    tone(880, 0,    0.35);
    tone(1318, 0.12, 0.50);
    setTimeout(() => ctx.close(), 1200);
  } catch (_) {}
}

function WRAlarm() {
  useDataTick(); // re-eval whenever RUN_DATA mutates
  const [toasts, setToasts] = useState([]);   // [{ id, level, runner, time, url }]
  const [sweep, setSweep] = useState(false);
  const [muted, setMutedState] = useState(() => {
    try { return localStorage.getItem(MUTED_LS_KEY) === "1"; } catch { return false; }
  });
  const seenRef = useRef(null);                 // Set<string> persisted in LS
  const titleFlashRef = useRef({ running: false, original: null, intervalId: null });
  const originalFaviconRef = useRef(null);
  const alarmFaviconRef = useRef(null);
  const dismissTimers = useRef(new Map());

  // Initial setup: read seen, build alarm favicon, capture original favicon.
  useEffect(() => {
    seenRef.current = readSeen();
    alarmFaviconRef.current = buildAlarmFaviconDataURL();
    const link = document.querySelector('link[rel="icon"]:not([sizes])') ||
                 document.querySelector('link[rel="icon"]');
    if (link) originalFaviconRef.current = link.getAttribute("href");
    titleFlashRef.current.original = document.title;

    // Stop the title-flash as soon as the user looks at the tab.
    const onVisible = () => {
      if (document.visibilityState === "visible" && titleFlashRef.current.running) {
        stopTitleFlash();
        restoreFavicon();
      }
    };
    document.addEventListener("visibilitychange", onVisible);
    window.addEventListener("focus", onVisible);
    return () => {
      document.removeEventListener("visibilitychange", onVisible);
      window.removeEventListener("focus", onVisible);
    };
  }, []);

  function setMuted(next) {
    setMutedState(next);
    try { localStorage.setItem(MUTED_LS_KEY, next ? "1" : "0"); } catch {}
  }

  function startTitleFlash(label) {
    const ref = titleFlashRef.current;
    if (ref.running) clearInterval(ref.intervalId);
    ref.running = true;
    let toggle = false;
    ref.intervalId = setInterval(() => {
      document.title = toggle ? ref.original : `🔴 NEW WR — ${label}`;
      toggle = !toggle;
    }, 900);
  }
  function stopTitleFlash() {
    const ref = titleFlashRef.current;
    if (ref.intervalId) clearInterval(ref.intervalId);
    ref.intervalId = null;
    ref.running = false;
    if (ref.original) document.title = ref.original;
  }
  function setAlarmFavicon() {
    const link = document.querySelector('link[rel="icon"]:not([sizes])') ||
                 document.querySelector('link[rel="icon"]');
    if (link && alarmFaviconRef.current) link.setAttribute("href", alarmFaviconRef.current);
  }
  function restoreFavicon() {
    const link = document.querySelector('link[rel="icon"]:not([sizes])') ||
                 document.querySelector('link[rel="icon"]');
    if (link && originalFaviconRef.current) link.setAttribute("href", originalFaviconRef.current);
  }

  function dismissToast(id) {
    setToasts((ts) => ts.filter((t) => t.id !== id));
    if (dismissTimers.current.has(id)) {
      clearTimeout(dismissTimers.current.get(id));
      dismissTimers.current.delete(id);
    }
  }

  // Fire an alarm for one new WR.
  function fireAlarm(row) {
    const id = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
    setToasts((ts) => [...ts, {
      id,
      level: row.level,
      runner: row.runner,
      time: row.time,
      url: row.runUrl || row.runnerUrl,
    }]);
    setSweep(true);
    setTimeout(() => setSweep(false), 1400);

    if (!muted) playBell();
    startTitleFlash(row.level);
    setAlarmFavicon();

    // System notification — only fires if the visitor has actively
    // granted permission. We never re-prompt automatically; the toast
    // has a button for that.
    if ("Notification" in window && Notification.permission === "granted") {
      try {
        new Notification("New WR on Mirror's Edge", {
          body: `${row.runner} took #1 on ${row.level} — ${row.time}`,
          icon: alarmFaviconRef.current || undefined,
          tag: id,
        });
      } catch {}
    }

    // Auto-dismiss the toast after 14s.
    const timer = setTimeout(() => dismissToast(id), 14000);
    dismissTimers.current.set(id, timer);
  }

  // Watch the activity feed; fire on any new top-1 entry by a non-self runner.
  useEffect(() => {
    const data = window.RUN_DATA;
    if (!data || !Array.isArray(data.recentActivity) || !data.recentActivity.length) return;
    if (!seenRef.current) return;

    // Find every WR (place=1, not me) currently in the feed.
    const wrRows = data.recentActivity.filter(r => r.place === 1 && !r.isMe);

    // Compute the freshness cutoff once — anything submitted before this
    // is considered historical and never fires an alarm.
    const cutoffMs = Date.now() - WR_FRESHNESS_DAYS * 24 * 60 * 60 * 1000;
    const isFresh = (r) => typeof r.tsMs === "number" && r.tsMs >= cutoffMs;

    // Bootstrap: on the very first time we have data, seed the seen set
    // with everything we currently know about and don't fire.
    let bootstrapped = false;
    try { bootstrapped = localStorage.getItem(BOOTSTRAPPED_LS_KEY) === "1"; } catch {}
    if (!bootstrapped) {
      for (const row of wrRows) seenRef.current.add(wrKey(row));
      writeSeen(seenRef.current);
      try { localStorage.setItem(BOOTSTRAPPED_LS_KEY, "1"); } catch {}
      return;
    }

    // For any WR older than the freshness window that we haven't seen yet,
    // mark it seen silently. This stops a backlog of historical WRs (e.g.
    // when the API surfaces older runs we hadn't fetched before) from all
    // firing at once.
    let silentlyMarked = false;
    for (const row of wrRows) {
      if (isFresh(row)) continue;
      const k = wrKey(row);
      if (!seenRef.current.has(k)) {
        seenRef.current.add(k);
        silentlyMarked = true;
      }
    }
    if (silentlyMarked) writeSeen(seenRef.current);

    // Anything that's both fresh AND not in the seen set fires an alarm.
    // We sort oldest→newest so toasts stack in chronological order if
    // multiple landed at once. Cap at 3 toasts so a burst doesn't drown
    // the screen — the rest are still marked seen so they don't re-fire.
    const fresh = wrRows
      .filter(isFresh)
      .filter(r => !seenRef.current.has(wrKey(r)))
      .sort((a, b) => (a.tsMs || 0) - (b.tsMs || 0));

    if (!fresh.length) return;

    const MAX_TOASTS = 3;
    let fired = 0;
    for (const row of fresh) {
      seenRef.current.add(wrKey(row));
      if (fired < MAX_TOASTS) {
        fireAlarm(row);
        fired++;
      }
    }
    writeSeen(seenRef.current);
  });

  function requestNotifPermission() {
    if (!("Notification" in window)) return;
    if (Notification.permission === "default") {
      Notification.requestPermission();
    }
  }

  // -----------------------------------------------------------------
  // Manual trigger: ?wr-test=1 in the URL (or the demo button in the
  // toast panel) lets us preview the alarm without waiting for a real
  // run to land. Useful for design review + screenshots.
  // -----------------------------------------------------------------
  useEffect(() => {
    if (!new URLSearchParams(location.search).has("wr-test")) return;
    const t = setTimeout(() => fireAlarm({
      level: "Heat (Chapter 2)",
      runner: "shadowwasp",
      time: "3:42.870",
      runUrl: "https://www.speedrun.com/mirrorsedge",
    }), 1500);
    return () => clearTimeout(t);
    // eslint-disable-next-line
  }, []);

  return (
    <>
      {/* Red curtain that sweeps in from the top of the screen on a fresh WR */}
      <div className={`wralarm-sweep${sweep ? " wralarm-sweep--on" : ""}`} aria-hidden="true" />

      {/* Toast stack — top-right, stacks downward */}
      <div className="wralarm-toasts" role="status" aria-live="polite">
        {toasts.map((t) => (
          <div key={t.id} className="wralarm-toast">
            <div className="wralarm-toast__bar" aria-hidden="true" />
            <div className="wralarm-toast__body">
              <div className="wralarm-toast__kicker">
                <span className="wralarm-toast__pulse" aria-hidden="true" />
                NEW WORLD RECORD
              </div>
              <div className="wralarm-toast__title">{t.level}</div>
              <div className="wralarm-toast__meta">
                <strong>{t.runner}</strong> · {t.time}
              </div>
              <div className="wralarm-toast__actions">
                {t.url && (
                  <a href={t.url} target="_blank" rel="noreferrer" className="wralarm-toast__link">
                    View on speedrun.com ↗
                  </a>
                )}
                <button
                  type="button"
                  className="wralarm-toast__mute"
                  onClick={() => { setMuted(!muted); }}
                  title={muted ? "Sound is muted" : "Mute alarm sound"}
                >
                  {muted ? "🔕 Muted" : "🔔 Mute"}
                </button>
                {"Notification" in window && Notification.permission === "default" && (
                  <button
                    type="button"
                    className="wralarm-toast__mute"
                    onClick={requestNotifPermission}
                    title="Allow notifications to be alerted when the tab isn't visible"
                  >
                    Enable notifications
                  </button>
                )}
              </div>
            </div>
            <button
              type="button"
              className="wralarm-toast__close"
              onClick={() => dismissToast(t.id)}
              aria-label="Dismiss alert"
            >×</button>
          </div>
        ))}
      </div>
    </>
  );
}

window.WRAlarm = WRAlarm;
