// AdBlock-Maxxer - THE SYSTEM: a blank-slate gamified Daily Quest panel (ISOLATED, all sites).
//
// Self-contained: every user fills it with their OWN quests. State lives in
// chrome.storage.local (key "sysQuest") on THIS device. Tap a quest to clear it, earn XP,
// level E -> S with titles, build a streak. Quests can have a timer (stopwatch + auto-
// complete) or be Twitch-verified "go live" (link your username, it auto-checks via the
// public Ledger endpoint). Miss the day -> the Penalty Zone. Toggle via Alt+Shift+Q or the
// popup checkbox. Shadow-root isolated so host-page CSS can't touch it.

(function () {
  "use strict";
  if (window.top !== window) return;          // top frame only
  if (window.__abmSystem) return;             // guard re-injection
  window.__abmSystem = true;

  const TC_URL = "https://the-ledger.maxxtopia.workers.dev/twitch-check?login=";
  let host = null, root = null, open = false, pos = null, minimized = false, autoArrival = false;
  let st = null;
  let twStatus = "", lastPoll = 0, loopIv = null;

  const byId = (id) => (root ? root.querySelector("#" + id) : null);
  const qsel = (s) => (root ? root.querySelector(s) : null);
  const uid = () => "q" + Math.random().toString(36).slice(2, 9);
  const RANKS = [[50, "S"], [35, "A"], [20, "B"], [10, "C"], [5, "D"], [0, "E"]];
  const TITLES = { E: "Awakened", D: "Hunter", C: "Elite", B: "Ace", A: "National-Level", S: "Monarch" };
  const lvlOf = (xp) => Math.floor(xp / 100) + 1;
  const rankOf = (lvl) => { for (const [t, r] of RANKS) if (lvl >= t) return r; return "E"; };
  const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
  const dstr = (d) => d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
  const today = () => dstr(new Date());
  const yday = () => { const d = new Date(); d.setDate(d.getDate() - 1); return dstr(d); };
  const fmt = (ms) => { const s = Math.floor(ms / 1000), m = Math.floor(s / 60); return m + ":" + String(s % 60).padStart(2, "0"); };
  const fmtHMS = (ms) => { const s = Math.max(0, Math.floor(ms / 1000)); return String(Math.floor(s / 3600)).padStart(2, "0") + ":" + String(Math.floor(s % 3600 / 60)).padStart(2, "0") + ":" + String(s % 60).padStart(2, "0"); };
  const msToMidnight = () => { const n = new Date(); return new Date(n.getFullYear(), n.getMonth(), n.getDate() + 1).getTime() - n.getTime(); };

  const DEFAULT = { items: [], doneDate: "", doneIds: [], xp: 0, streak: 0, lastClear: "", penalty: "", timers: {}, twitch: "", penaltyFor: "", penaltyAck: true, penalizedRun: false, counter: { label: "Tally", count: 0 } };

  function load(cb) {
    chrome.storage.local.get("sysQuest", (r) => { st = Object.assign({}, DEFAULT, (r && r.sysQuest) || {}); rollover(); cb && cb(); });
  }
  function save() { chrome.storage.local.set({ sysQuest: st }); }

  const allDone = () => st.items.length > 0 && st.items.every((q) => st.doneIds.includes(q.id));

  // New day: if the prior day had quests and wasn't fully cleared -> Penalty Zone + streak break.
  // Penalty fires ONCE when the chain breaks (penalizedRun gate), never re-arms on consecutive
  // missed days -> no daily shame wall. Re-arms only after a full clear restores the chain.
  function rollover() {
    const t = today();
    if (st.doneDate === t) return;
    if (st.doneDate) {
      const cleared = st.items.length > 0 && st.items.every((q) => st.doneIds.includes(q.id));
      if (st.items.length > 0 && !cleared && !st.penalizedRun) { st.penaltyFor = st.doneDate; st.penaltyAck = false; st.penalizedRun = true; }
      if (st.lastClear && st.lastClear !== yday() && st.lastClear !== t) st.streak = 0;
    }
    st.doneDate = t; st.doneIds = []; st.timers = {};
    if (st.counter) st.counter.count = 0; // daily counter resets too
  }

  function bump(delta) {
    if (!st.counter) st.counter = { label: "Tally", count: 0 };
    st.counter.count = Math.max(0, (st.counter.count || 0) + delta);
    save();
    const el = byId("cval"); if (el) el.textContent = st.counter.count;
  }

  // --- effects ---
  let ac = null;
  function ping(kind) {
    try {
      ac = ac || new (window.AudioContext || window.webkitAudioContext)();
      const now = ac.currentTime;
      const notes = kind === "lvl" ? [660, 880, 1320] : kind === "pen" ? [330, 247] : [990];
      notes.forEach((f, k) => {
        const o = ac.createOscillator(), g = ac.createGain();
        o.type = "sine"; o.frequency.value = f;
        const t = now + k * 0.09;
        g.gain.setValueAtTime(0, t); g.gain.linearRampToValueAtTime(0.06, t + 0.01); g.gain.exponentialRampToValueAtTime(0.0001, t + 0.18);
        o.connect(g); g.connect(ac.destination); o.start(t); o.stop(t + 0.2);
      });
    } catch (e) {}
  }
  function levelUpFx(n) {
    const w = byId("win"); if (!w) return;
    const o = document.createElement("div"); o.className = "lvlup";
    o.innerHTML = "<big>LEVEL UP</big><small>LEVEL " + n + " &middot; RANK " + rankOf(n) + " &middot; " + TITLES[rankOf(n)] + "</small>";
    w.appendChild(o);
    setTimeout(() => { if (o.parentNode) o.parentNode.removeChild(o); }, 1500);
  }

  function toggle(id) {
    const i = st.doneIds.indexOf(id);
    const wasAll = allDone();
    const prevLvl = lvlOf(st.xp);
    const turningOn = i < 0;
    if (i >= 0) { st.doneIds.splice(i, 1); st.xp = Math.max(0, st.xp - 10); }
    else { st.doneIds.push(id); st.xp += 10; }
    const isAll = allDone();
    if (isAll && !wasAll) {
      st.xp += 20;
      st.penalizedRun = false; // chain restored -> a future break can penalize once again
      if (st.lastClear !== today()) { st.streak = st.lastClear === yday() ? st.streak + 1 : 1; st.lastClear = today(); }
    } else if (!isAll && wasAll) {
      st.xp = Math.max(0, st.xp - 20);
      if (st.lastClear === today()) { st.streak = Math.max(0, st.streak - 1); st.lastClear = st.streak ? yday() : ""; }
    }
    save(); render();
    if (turningOn) {
      const row = root && root.querySelector('.q[data-id="' + id + '"]');
      if (row) row.classList.add("flash");
      ping("done");
    }
    if (lvlOf(st.xp) > prevLvl) { levelUpFx(lvlOf(st.xp)); ping("lvl"); }
  }
  function addItem(label, mins) {
    label = (label || "").trim(); if (!label) return;
    st.items.push({ id: uid(), label: label.slice(0, 80), mins: Math.max(0, Math.min(600, parseInt(mins, 10) || 0)) });
    save(); render();
  }
  function delItem(id) {
    if (id === "__golive") { setTwitch(""); return; }
    st.items = st.items.filter((q) => q.id !== id); st.doneIds = st.doneIds.filter((x) => x !== id); if (st.timers) delete st.timers[id]; save(); render();
  }

  // --- timers ---
  function tState(id) { if (!st.timers) st.timers = {}; if (!st.timers[id]) st.timers[id] = { running: false, startedAt: null, elapsed: 0 }; return st.timers[id]; }
  function totalMs(id) { const t = tState(id); return (t.elapsed || 0) + (t.running && t.startedAt ? Date.now() - t.startedAt : 0); }
  function startTimer(id) { if (st.doneIds.includes(id)) return; const t = tState(id); t.running = true; t.startedAt = Date.now(); save(); render(); }
  function pauseTimer(id) { const t = tState(id); if (t.running) { t.elapsed = (t.elapsed || 0) + (t.startedAt ? Date.now() - t.startedAt : 0); t.running = false; t.startedAt = null; save(); render(); } }

  // --- Twitch-verified go-live ---
  function setTwitch(name) {
    name = (name || "").trim().toLowerCase().replace(/[^a-z0-9_]/g, "").slice(0, 30);
    st.twitch = name;
    const has = st.items.some((q) => q.id === "__golive");
    if (name && !has) st.items.unshift({ id: "__golive", label: "Go live 60 min", twitch: true });
    if (!name && has) { st.items = st.items.filter((q) => q.id !== "__golive"); st.doneIds = st.doneIds.filter((x) => x !== "__golive"); }
    twStatus = name ? "checking..." : "";
    save(); render(); pollTwitch(true);
  }
  function pollTwitch(force) {
    if (!st.twitch) return;
    if (!force && Date.now() - lastPoll < 110000) return;
    lastPoll = Date.now();
    fetch(TC_URL + encodeURIComponent(st.twitch)).then((r) => r.json()).then((d) => {
      if (!d || !d.ok) { twStatus = (d && d.reason) || "channel?"; updTw(); return; }
      twStatus = d.live60 ? ("LIVE 60+ ✓") : (d.isLiveNow ? ("LIVE " + d.minutes + "/60 min") : "offline");
      if (d.live60 && st.items.some((q) => q.id === "__golive") && !st.doneIds.includes("__golive")) toggle("__golive");
      else updTw();
    }).catch(() => { twStatus = "check failed"; updTw(); });
  }
  function updTw() { const el = byId("twstat"); if (el) el.textContent = twStatus; }

  // --- one loop while the panel is open: deadline, timer bars, twitch poll, midnight roll ---
  function startLoop() { if (!loopIv) loopIv = setInterval(loop, 1000); }
  function stopLoop() { if (loopIv) { clearInterval(loopIv); loopIv = null; } }
  function loop() {
    if (!root) { stopLoop(); return; }
    const dl = byId("deadline"); if (dl) dl.textContent = "QUEST RESETS IN " + fmtHMS(msToMidnight());
    for (const q of st.items) {
      if (!q.mins) continue;
      const t = st.timers && st.timers[q.id];
      if (!t || !t.running) continue;
      const total = q.mins * 60000, el = totalMs(q.id);
      if (el >= total && !st.doneIds.includes(q.id)) { t.running = false; t.elapsed = total; t.startedAt = null; save(); toggle(q.id); return; }
      const bar = byId("t-" + q.id); if (bar) bar.style.width = Math.min(100, el / total * 100) + "%";
      const tt = byId("tt-" + q.id); if (tt) tt.textContent = fmt(el) + " / " + fmt(total);
    }
    pollTwitch(false);
    if (st.doneDate !== today()) { rollover(); save(); render(); } // day rolled over while open
  }

  const CSS = `
    :host { all: initial; }
    @keyframes sysIn { 0%{opacity:0; transform:translateY(8px) scale(.92);} 60%{opacity:1;} 100%{opacity:1; transform:translateY(0) scale(1);} }
    @keyframes sysGlow { 0%,100%{box-shadow:0 0 18px rgba(45,155,224,.40), inset 0 0 18px rgba(45,155,224,.08);} 50%{box-shadow:0 0 30px rgba(95,200,255,.65), inset 0 0 26px rgba(45,155,224,.16);} }
    @keyframes flash { 0%{background:#0a1626; box-shadow:none;} 30%{background:#0e2f44; box-shadow:0 0 16px rgba(57,217,138,.85);} 100%{background:#0a1626; box-shadow:none;} }
    @keyframes arisePulse { 0%,100%{opacity:.7; text-shadow:0 0 8px rgba(57,217,138,.6);} 50%{opacity:1; text-shadow:0 0 16px rgba(57,217,138,1);} }
    @keyframes lvlIn { 0%{opacity:0; transform:scale(1.45);} 22%{opacity:1;} 80%{opacity:1; transform:scale(1);} 100%{opacity:0;} }
    @keyframes penIn { 0%{opacity:0;} 100%{opacity:1;} }
    .win { position: fixed; z-index: 2147483645; width: 264px; opacity:0; display:flex; flex-direction:column; max-height:calc(100vh - 24px);
      background: linear-gradient(180deg,#0a1422,#070d18); color: #cfe8ff; border: 1px solid #2b9be0; border-radius: 12px;
      font: 13px/1.4 "Segoe UI",system-ui,sans-serif; overflow: hidden;
      animation: sysIn .45s cubic-bezier(.2,.9,.25,1) forwards, sysGlow 3.4s ease-in-out infinite .45s; }
    .win::before { content:""; position:absolute; inset:0; border-radius:12px; pointer-events:none; background: linear-gradient(180deg, rgba(127,210,255,.06), transparent 42%); }
    .hdr { position:relative; display:flex; align-items:center; gap:8px; padding:9px 11px; cursor:move; user-select:none; background: linear-gradient(180deg,#0d1c30,#0a1422); border-bottom:1px solid #163049; }
    .hdr::after { content:""; position:absolute; left:0; right:0; bottom:-1px; height:1px; background:linear-gradient(90deg,transparent,#7fd2ff,transparent); }
    .hdr b { flex:1; font-size:12px; letter-spacing:2px; color:#9fe0ff; text-shadow:0 0 8px rgba(127,210,255,.6); }
    .x { cursor:pointer; color:#5f86a8; font-size:15px; padding:2px 5px; border-radius:5px; }
    .x:hover { color:#fff; background:#163049; }
    #body { display:flex; flex-direction:column; min-height:0; overflow:hidden; }
    .bar { display:flex; justify-content:space-between; font-size:12px; color:#9fc6e6; padding:9px 12px 3px; }
    .bar b { color:#9fe0ff; }
    .ttl { color:#a970ff; }
    .xp { position:relative; height:7px; margin:0 12px 6px; border:1px solid #2b9be0; border-radius:5px; background:#0a1422; overflow:hidden; }
    .xp > i { display:block; height:100%; background:linear-gradient(90deg,#2b9be0,#7fd2ff); transition:width .55s cubic-bezier(.2,.9,.25,1); box-shadow:0 0 10px rgba(127,210,255,.85); }
    .deadline { text-align:center; font-size:10px; letter-spacing:2px; color:#c98b4a; padding:0 0 8px; }
    .list { flex:1 1 auto; min-height:0; padding: 2px 10px 4px; overflow:auto; }
    .q { display:flex; align-items:center; gap:9px; padding:9px 10px; margin:6px 0; cursor:pointer; border:1px solid #1d3a55; border-radius:8px; background:#0a1626; font-size:13px; transition:border-color .15s, transform .08s; }
    .q:hover { border-color:#2b9be0; }
    .q:active { transform:scale(.985); }
    .q.flash { animation: flash .6s ease-out; }
    .q .bx { font-size:17px; color:#7fd2ff; }
    .q.done { color:#5f86a8; border-color:#1c5a3a; }
    .q.done .lbl { text-decoration: line-through; }
    .q.done .bx { color:#39d98a; text-shadow:0 0 8px rgba(57,217,138,.85); }
    .q .lbl { flex:1; }
    .q .rew { font-size:10px; color:#39a0c2; opacity:.75; white-space:nowrap; }
    .q .del { color:#436; opacity:.6; font-size:13px; padding:0 3px; }
    .q .del:hover { color:#e2536a; opacity:1; }
    .q.timed { align-items:center; }
    .tcol { flex:1; display:flex; flex-direction:column; gap:4px; min-width:0; }
    .tlabel { display:flex; justify-content:space-between; align-items:baseline; gap:8px; }
    .ttime { font-size:10px; color:#5f86a8; white-space:nowrap; }
    .tbar { height:5px; border-radius:4px; background:#0a1422; border:1px solid #163049; overflow:hidden; }
    .tbar > i { display:block; height:100%; width:0; background:linear-gradient(90deg,#2b9be0,#7fd2ff); box-shadow:0 0 8px rgba(127,210,255,.7); transition:width 1s linear; }
    .tbtn { cursor:pointer; color:#7fd2ff; font-size:13px; padding:0 5px; }
    .tbtn:hover { color:#fff; }
    .q.golive .tw { color:#a970ff; font-size:11px; }
    .empty { text-align:center; color:#5f86a8; font-size:12px; padding:14px 8px; }
    .add { display:flex; gap:6px; padding:6px 12px 8px; }
    .add input { flex:1; background:#0a1626; border:1px solid #1d3a55; border-radius:7px; color:#cfe8ff; padding:8px 9px; font-size:13px; outline:none; }
    .add input:focus { border-color:#2b9be0; }
    .add #min { flex:0 0 44px; width:44px; padding:8px 6px; -moz-appearance:textfield; }
    .add #min::-webkit-outer-spin-button, .add #min::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }
    .add button { background:#123a5c; border:1px solid #2b9be0; color:#9fe0ff; border-radius:7px; padding:0 12px; cursor:pointer; font-size:16px; }
    .link { display:flex; align-items:center; gap:7px; padding:0 12px 10px; }
    .link .lk { color:#a970ff; }
    .link input { flex:1; background:#0a1626; border:1px solid #2a2350; border-radius:7px; color:#cfe8ff; padding:7px 9px; font-size:12px; outline:none; }
    .link input:focus { border-color:#a970ff; }
    .cnt { display:flex; align-items:center; gap:7px; padding:0 12px 10px; }
    .cnt input { flex:1; background:#0a1626; border:1px solid #1d3a55; border-radius:7px; color:#cfe8ff; padding:7px 9px; font-size:12px; outline:none; }
    .cnt input:focus { border-color:#2b9be0; }
    .cnt .cval { color:#9fe0ff; font-weight:700; font-size:15px; min-width:22px; text-align:center; }
    .cnt .cbtn { cursor:pointer; width:24px; height:24px; line-height:22px; text-align:center; border:1px solid #2b9be0; border-radius:6px; color:#7fd2ff; font-size:14px; }
    .cnt .cbtn:hover { color:#fff; background:#163049; }
    .arise { text-align:center; color:#39d98a; font-size:12px; letter-spacing:3px; padding:0 0 8px; animation: arisePulse 1.6s ease-in-out infinite; }
    .pen { padding:8px 12px 12px; border-top:1px solid #122438; }
    .pen input { width:100%; box-sizing:border-box; background:transparent; border:0; border-bottom:1px dashed #3a4a5e; color:#c25b6a; font-size:11px; padding:4px 0; outline:none; letter-spacing:.5px; }
    .pen input::placeholder { color:#5a3a44; }
    .win.min #body { display:none; }
    .lvlup { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; flex-direction:column; background:radial-gradient(circle, rgba(10,30,50,.92), rgba(7,13,24,.97)); color:#9fe0ff; pointer-events:none; letter-spacing:4px; font-weight:700; text-shadow:0 0 16px rgba(127,210,255,1); animation: lvlIn 1.5s ease-out forwards; z-index:5; }
    .lvlup big { font-size:23px; } .lvlup small { font-size:11px; letter-spacing:2px; color:#7fd2ff; margin-top:7px; text-align:center; }
    .penzone { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; flex-direction:column; z-index:6; padding:24px; box-sizing:border-box; text-align:center; animation: penIn .3s ease-out; background:radial-gradient(circle, rgba(60,10,16,.95), rgba(20,4,8,.98)); border-radius:12px; }
    .penzone .pz-big { color:#ff5566; font-size:18px; font-weight:800; letter-spacing:3px; text-shadow:0 0 14px rgba(255,60,80,.9); }
    .penzone .pz-sub { color:#e2a0a8; font-size:12px; margin-top:8px; }
    .penzone .pz-pen { color:#ffd0d6; font-size:12px; font-style:italic; margin-top:14px; line-height:1.5; }
    .penzone .pz-btn { margin-top:20px; cursor:pointer; border:1px solid #ff5566; color:#ff97a4; border-radius:8px; padding:8px 22px; letter-spacing:3px; font-size:12px; }
    .penzone .pz-btn:hover { background:rgba(255,85,102,.16); color:#fff; }
  `;

  function rowHtml(q) {
    const done = st.doneIds.includes(q.id);
    if (q.twitch) {
      return `<div class="q golive${done ? " done" : ""}" data-id="${q.id}">`
        + `<span class="bx" data-toggle="${q.id}">${done ? "&#9745;" : "&#9744;"}</span>`
        + `<div class="tcol"><div class="tlabel"><span class="lbl">${esc(q.label)} <span class="tw">@${esc(st.twitch)}</span></span><span class="ttime" id="twstat">${esc(twStatus)}</span></div></div>`
        + `<span class="rew">+10</span><span class="del" data-del="${q.id}">&#10005;</span></div>`;
    }
    if (q.mins) {
      const total = q.mins * 60000, el = done ? total : totalMs(q.id), pct = Math.min(100, el / total * 100);
      const t = st.timers && st.timers[q.id], running = t && t.running;
      const btn = done ? "" : `<span class="tbtn" data-timer="${q.id}">${running ? "&#10074;&#10074;" : "&#9654;"}</span>`;
      return `<div class="q timed${done ? " done" : ""}" data-id="${q.id}">`
        + `<span class="bx" data-toggle="${q.id}">${done ? "&#9745;" : "&#9744;"}</span>`
        + `<div class="tcol"><div class="tlabel"><span class="lbl">${esc(q.label)}</span><span class="ttime" id="tt-${q.id}">${fmt(el)} / ${fmt(total)}</span></div>`
        + `<div class="tbar"><i id="t-${q.id}" style="width:${pct}%"></i></div></div>`
        + btn + `<span class="rew">+10</span><span class="del" data-del="${q.id}">&#10005;</span></div>`;
    }
    return `<div class="q${done ? " done" : ""}" data-id="${q.id}"><span class="bx">${done ? "&#9745;" : "&#9744;"}</span><span class="lbl">${esc(q.label)}</span><span class="rew">+10</span><span class="del" data-del="${q.id}">&#10005;</span></div>`;
  }

  function bodyHtml() {
    const lvl = lvlOf(st.xp), rank = rankOf(lvl), xpIn = st.xp % 100;
    const rows = st.items.length ? st.items.map(rowHtml).join("") : `<div class="empty">Add your first quest below to begin. ARISE.</div>`;
    return `
      <div class="bar"><span>LEVEL ${lvl}</span><span>RANK ${rank} <span class="ttl">${TITLES[rank]}</span></span><span>&#128293; ${st.streak}</span></div>
      <div class="xp"><i style="width:${xpIn}%"></i></div>
      <div class="deadline" id="deadline">QUEST RESETS IN ${fmtHMS(msToMidnight())}</div>
      <div class="list">${rows}</div>
      ${allDone() ? '<div class="arise">QUEST CLEARED &mdash; ARISE</div>' : ""}
      <div class="add"><input id="in" type="text" placeholder="New quest..." maxlength="80" /><input id="min" type="number" min="0" max="600" placeholder="min" title="optional timer (minutes)" /><button id="addb" title="Add quest">+</button></div>
      <div class="link"><span class="lk">&#128279;</span><input id="tw" type="text" placeholder="Twitch username = auto go-live quest" value="${esc(st.twitch)}" maxlength="30" /></div>
      <div class="cnt"><input id="cntlbl" type="text" value="${esc((st.counter && st.counter.label) || "Tally")}" maxlength="24" title="rename this counter" /><span class="cval" id="cval">${(st.counter && st.counter.count) || 0}</span><span class="cbtn" data-cnt="-1" title="minus">&#8722;</span><span class="cbtn" data-cnt="1" title="plus">+</span></div>
      <div class="pen"><input id="pen" type="text" placeholder="Set your own penalty for breaking the chain..." value="${esc(st.penalty)}" maxlength="120" /></div>`;
  }

  function render() {
    if (!root) return;
    const b = byId("body");
    if (!b) return;
    b.innerHTML = bodyHtml();
    b.querySelectorAll(".q").forEach((el) => el.addEventListener("click", (e) => {
      const t = e.target;
      if (t.getAttribute("data-del")) { delItem(t.getAttribute("data-del")); return; }
      if (t.getAttribute("data-timer")) { const id = t.getAttribute("data-timer"); const ts = st.timers && st.timers[id]; if (ts && ts.running) pauseTimer(id); else startTimer(id); return; }
      const id = el.getAttribute("data-id");
      if (el.classList.contains("timed") || el.classList.contains("golive")) { if (t.getAttribute("data-toggle")) toggle(id); return; }
      toggle(id);
    }));
    const inp = byId("in"), mn = byId("min"), pen = byId("pen"), tw = byId("tw");
    const add = () => { addItem(inp.value, mn.value); };
    byId("addb").addEventListener("click", add);
    inp.addEventListener("keydown", (e) => { if (e.key === "Enter") add(); });
    mn.addEventListener("keydown", (e) => { if (e.key === "Enter") add(); });
    tw.addEventListener("change", () => setTwitch(tw.value));
    b.querySelectorAll("[data-cnt]").forEach((el) => el.addEventListener("click", () => bump(parseInt(el.getAttribute("data-cnt"), 10))));
    const cl = byId("cntlbl"); if (cl) cl.addEventListener("change", () => { if (!st.counter) st.counter = { count: 0 }; st.counter.label = cl.value.trim().slice(0, 24) || "Tally"; save(); });
    pen.addEventListener("change", () => { st.penalty = pen.value.trim().slice(0, 120); save(); });
    if (st.penaltyFor && !st.penaltyAck) showPenalty();
  }

  function showPenalty() {
    const w = byId("win"); if (!w || qsel(".penzone")) return;
    const o = document.createElement("div"); o.className = "penzone";
    o.innerHTML = `<div class="pz-big">&#9888; PENALTY ZONE</div><div class="pz-sub">You failed the daily quest. Don't fail twice.</div>`
      + (st.penalty ? `<div class="pz-pen">"${esc(st.penalty)}"</div>` : "")
      + `<div class="pz-btn" id="pzack">ACCEPT</div>`;
    w.appendChild(o);
    applyMin(); // force-expand: a minimized window would clip the overlay + ACCEPT button out of view
    qsel("#pzack").addEventListener("click", () => { st.penaltyAck = true; st.penaltyFor = ""; save(); o.remove(); applyMin(); ping("pen"); });
    ping("pen");
  }

  function place() {
    const w = byId("win"); if (!w) return;
    const p = pos || { x: window.innerWidth - 284, y: 80 };
    p.x = Math.min(Math.max(8, p.x), window.innerWidth - 272);
    p.y = Math.min(Math.max(8, p.y), window.innerHeight - 80);
    w.style.left = p.x + "px"; w.style.top = p.y + "px"; pos = p;
  }
  function startDrag(e) {
    const w = byId("win"); const sx = e.clientX, sy = e.clientY; const ox = pos.x, oy = pos.y;
    const mv = (ev) => { pos = { x: ox + ev.clientX - sx, y: oy + ev.clientY - sy }; w.style.left = pos.x + "px"; w.style.top = pos.y + "px"; };
    const up = () => { document.removeEventListener("mousemove", mv); document.removeEventListener("mouseup", up); chrome.storage.local.set({ sysPos: pos }); };
    document.addEventListener("mousemove", mv); document.addEventListener("mouseup", up);
  }

  function build() {
    if (host) return;
    host = document.createElement("div");
    host.id = "abm-system-host";
    root = host.attachShadow ? host.attachShadow({ mode: "open" }) : host;
    const style = document.createElement("style"); style.textContent = CSS;
    const wrap = document.createElement("div");
    wrap.innerHTML = `<div class="win" id="win"><div class="hdr" id="hdr"><b>&#10214; SYSTEM &#10215;  DAILY QUEST</b><div class="x" id="min" title="Minimize / expand">&#8212;</div><div class="x" id="close" title="Close">&#10005;</div></div><div id="body"></div></div>`;
    root.appendChild(style); root.appendChild(wrap.firstElementChild);
    document.documentElement.appendChild(host);
    byId("hdr").addEventListener("mousedown", startDrag);
    byId("min").addEventListener("click", toggleMin);
    byId("close").addEventListener("click", () => setOpen(false));
    chrome.storage.local.get("sysMin", (r) => { minimized = !!(r && r.sysMin); applyMin(); });
    place(); render(); pollTwitch(true); startLoop();
    if (autoArrival) { autoArrival = false; setTimeout(arrivalFx, 220); }
  }
  function arrivalFx() {
    const w = byId("win"); if (!w) return;
    const o = document.createElement("div"); o.className = "lvlup";
    o.innerHTML = "<big>&#10214; NOTICE &#10215;</big><small>DAILY QUEST HAS ARRIVED</small>";
    w.appendChild(o);
    setTimeout(() => { if (o.parentNode) o.remove(); }, 1900);
    ping("lvl");
  }
  function applyMin() { const w = byId("win"), b = byId("min"); if (!w) return; const m = minimized && !qsel(".penzone"); w.classList.toggle("min", m); if (b) b.innerHTML = minimized ? "&#9633;" : "&#8212;"; }
  function toggleMin() { minimized = !minimized; chrome.storage.local.set({ sysMin: minimized }); applyMin(); }
  function teardown() { stopLoop(); if (host && host.parentNode) host.parentNode.removeChild(host); host = null; root = null; }
  function setOpen(v) { open = v; chrome.storage.local.set({ sysOpen: v }); if (v) { load(build); } else teardown(); }

  chrome.storage.local.get(["sysOpen", "sysPos", "sysAuto", "sysAutoShown"], (r) => {
    r = r || {};
    pos = r.sysPos || null;
    if (r.sysOpen) { setOpen(true); return; }
    // the System summons you once a day (first page load), unless you've turned it off
    if (r.sysAuto !== false && r.sysAutoShown !== today()) {
      chrome.storage.local.set({ sysAutoShown: today() });
      autoArrival = true; setOpen(true);
    }
  });
  chrome.storage.onChanged.addListener((c) => {
    if (c.sysOpen && !!c.sysOpen.newValue !== open) { open = !!c.sysOpen.newValue; if (open) load(build); else teardown(); }
  });
  chrome.runtime.onMessage.addListener((msg, _s, reply) => {
    if (msg && msg.type === "cmd" && msg.cmd === "toggle-system") { setOpen(!open); try { reply({ ok: true }); } catch (e) {} }
  });
  window.addEventListener("resize", () => { if (root) place(); });
})();
