// AdBlock-Maxxer - Twitch game panel (ISOLATED world, twitch.tv).
//
// Viewer-retention feature: a small launcher button on a channel page opens a
// DRAGGABLE + RESIZABLE floating panel that runs the bundled "Sprite Cannon"
// game (chrome-extension://<id>/game/spritecannon/index.html) in an iframe, so a
// viewer can play WITHOUT leaving the stream (keeps the page open = retained
// viewer + engagement). Scores post to the streamer's per-stream leaderboard by
// passing ?ch=<channel> (the game's streamChannel() reads it). The "no-wait" path
// while a real Twitch Extension goes through review.
//
// Purely additive and behind the "twitchGame" toggle. Twitch is a React SPA with
// constantly re-rendering markup, so (like twitch-extras.js) we re-assert our
// launcher on a bounded tick and recompute the channel so it follows SPA nav.
// Panel position/size + open state persist via chrome.storage.local.

(function () {
  "use strict";
  if (window.top !== window) return;             // never inside the game iframe / Twitch sub-frames
  if (window.__abmTwitchGame) return;
  window.__abmTwitchGame = true;

  const GAME_PATH = "game/spritecannon/index.html";
  const RESERVED = new Set([
    "directory", "videos", "settings", "u", "search", "subscriptions", "following",
    "wallet", "drops", "prime", "turbo", "friends", "downloads", "store", "jobs",
    "p", "popout", "team", "communities", "products", "bits", "moderator", "dashboard",
    "", "login", "signup",
  ]);

  let enabled = true;     // "twitchGame" toggle (default ON)
  let allowed = true;     // per-site allowlist (paused on twitch.tv?)
  let open = false;       // "twitchGameOpen" - panel visible
  let pos = null;         // {x, y} windowed panel position
  let posFs = null;       // {x, y} fullscreen panel position (kept separate so the two layouts don't clobber each other)
  let size = null;        // {w, h}
  let channel = null;     // current channel login (per-stream leaderboard)
  let panelEl = null;
  let launcherEl = null;
  let launchPos = null;   // {x,y} windowed launcher position (persists)
  let launchPosFs = null; // {x,y} fullscreen launcher position (persists, independent of windowed)
  let minimized = false;  // launcher collapsed to a small icon-only dot (persists)
  let launchAnchored = false; // latched once Play sits at its final default spot, so it stops re-snapping as the page scrolls

  function currentLogin() {
    const seg = location.pathname.split("/").filter(Boolean);
    if (!seg.length) return null;
    if (seg[0] === "popout" && seg[1]) return seg[1].toLowerCase();
    if (RESERVED.has(seg[0])) return null;
    return seg[0].toLowerCase();
  }
  function refreshAllow(list) {
    const h = location.hostname;
    allowed = !((list || []).some((d) => h === d || h.endsWith("." + d)));
  }
  function shouldShow() { return enabled && allowed && !!currentLogin(); }

  function gameUrl() {
    const base = chrome.runtime.getURL(GAME_PATH);
    return channel ? base + "?ch=" + encodeURIComponent(channel) : base;
  }

  // ---- storage -------------------------------------------------------------
  chrome.storage.local.get(
    ["twitchGame", "allowlist", "twitchGameOpen", "twitchGamePos", "twitchGamePosFs", "twitchGameSize", "twitchGameLaunchPos", "twitchGameLaunchPosFs", "twitchGameMinimized"],
    (r) => {
      if (r && r.twitchGame === false) enabled = false;
      open = !!(r && r.twitchGameOpen);
      pos = (r && r.twitchGamePos) || null;
      posFs = (r && r.twitchGamePosFs) || null;
      size = (r && r.twitchGameSize) || null;
      launchPos = (r && r.twitchGameLaunchPos) || null;
      launchPosFs = (r && r.twitchGameLaunchPosFs) || null;
      minimized = !!(r && r.twitchGameMinimized);
      refreshAllow(r && r.allowlist);
      sync();
    }
  );
  chrome.storage.onChanged.addListener((c) => {
    if (c.twitchGame) { enabled = c.twitchGame.newValue !== false; sync(); }
    if (c.allowlist) { refreshAllow(c.allowlist.newValue); sync(); }
    if (c.twitchGameOpen) {
      const v = !!c.twitchGameOpen.newValue;
      if (v !== open) { open = v; sync(); }
    }
  });

  // ---- styles (blended with Twitch dark theme) -----------------------------
  function ensureStyle() {
    if (document.getElementById("abm-scgame-style")) return;
    const s = document.createElement("style");
    s.id = "abm-scgame-style";
    s.textContent = `
      #abm-scg-launch { position:fixed; left:14px; top:auto; bottom:96px; z-index:2147483600; pointer-events:auto;
        display:inline-flex; align-items:center; gap:7px; padding:9px 15px; border-radius:999px; cursor:grab;
        font:700 13px/1 Inter,-apple-system,"Segoe UI",Roboto,sans-serif; color:#fff; letter-spacing:.02em;
        background:linear-gradient(180deg,#9147ff,#7a2ff2); box-shadow:0 6px 20px rgba(145,71,255,.5);
        transition:transform .12s ease, box-shadow .12s ease; user-select:none; }
      #abm-scg-launch:hover { transform:translateY(-2px); box-shadow:0 9px 26px rgba(145,71,255,.62); }
      #abm-scg-launch .abm-scg-emoji { font-size:15px; }
      #abm-scg-launch .abm-scg-min { margin:-2px -6px -2px 3px; padding:2px 6px; border-radius:7px; font-weight:900;
        font-size:15px; line-height:1; opacity:.72; transition:opacity .12s, background .12s; }
      #abm-scg-launch .abm-scg-min:hover { opacity:1; background:rgba(255,255,255,.22); }
      /* minimized = icon-only dot; click it to expand the full pill back */
      #abm-scg-launch.abm-min { padding:0; width:40px; height:40px; gap:0; justify-content:center; }
      #abm-scg-launch.abm-min .abm-scg-lbl, #abm-scg-launch.abm-min .abm-scg-min { display:none; }
      #abm-scg-launch.abm-min .abm-scg-emoji { font-size:18px; }

      #abm-scgame { position:fixed; z-index:2147483601; width:360px; height:540px;
        min-width:240px; min-height:260px; max-width:96vw; max-height:94vh;
        display:flex; flex-direction:column; border-radius:12px; overflow:hidden; user-select:none;
        background:#0e0a1a; box-shadow:0 18px 50px rgba(0,0,0,.6), 0 0 0 1px rgba(145,71,255,.35);
        font:13px/1.45 Inter,-apple-system,"Segoe UI",Roboto,sans-serif; }
      #abm-scgame .abm-scg-hd { flex:0 0 auto; display:flex; align-items:center; gap:8px; cursor:move;
        padding:7px 10px; font-size:12px; font-weight:800; letter-spacing:.04em; text-transform:uppercase;
        color:#fff; background:linear-gradient(180deg,rgba(145,71,255,.32),rgba(145,71,255,.14));
        border-bottom:1px solid rgba(255,255,255,.08); }
      #abm-scgame .abm-scg-hd .abm-scg-ttl { display:flex; align-items:center; gap:7px; }
      #abm-scgame .abm-scg-hd .abm-scg-ch { font-weight:600; text-transform:none; color:#cbb4ff; font-size:11px; opacity:.9; }
      #abm-scgame .abm-scg-hd .abm-scg-sp { flex:1; }
      #abm-scgame .abm-scg-hd .abm-scg-btn { cursor:pointer; color:#d8cdf0; padding:0 6px; border-radius:5px; font-size:16px; line-height:1; }
      #abm-scgame .abm-scg-hd .abm-scg-btn:hover { color:#fff; background:rgba(255,255,255,.16); }
      #abm-scgame .abm-scg-body { flex:1 1 auto; min-height:0; position:relative; background:#05070d; }
      #abm-scgame .abm-scg-body iframe { position:absolute; inset:0; width:100%; height:100%; border:0; display:block; }
      #abm-scgame .abm-rz { position:absolute; z-index:4; }
      #abm-scgame .abm-rz-n { top:-4px; left:10px; right:10px; height:9px; cursor:ns-resize; }
      #abm-scgame .abm-rz-s { bottom:-4px; left:10px; right:10px; height:9px; cursor:ns-resize; }
      #abm-scgame .abm-rz-e { right:-4px; top:10px; bottom:10px; width:9px; cursor:ew-resize; }
      #abm-scgame .abm-rz-w { left:-4px; top:10px; bottom:10px; width:9px; cursor:ew-resize; }
      #abm-scgame .abm-rz-ne { top:-4px; right:-4px; width:15px; height:15px; cursor:nesw-resize; }
      #abm-scgame .abm-rz-nw { top:-4px; left:-4px; width:15px; height:15px; cursor:nwse-resize; }
      #abm-scgame .abm-rz-sw { bottom:-4px; left:-4px; width:15px; height:15px; cursor:nesw-resize; }
      #abm-scgame .abm-rz-se { bottom:-4px; right:-4px; width:15px; height:15px; cursor:nwse-resize; }
      #abm-scgame .abm-rz-se::after { content:""; position:absolute; right:3px; bottom:3px; width:8px; height:8px;
        border-right:2px solid rgba(255,255,255,.45); border-bottom:2px solid rgba(255,255,255,.45); border-radius:0 0 3px 0; }
    `;
    (document.head || document.documentElement).appendChild(s);
  }

  // ---- launcher button (draggable; click = open, drag = move) ---------------
  function fsRoot() { return document.fullscreenElement || document.webkitFullscreenElement || null; }
  function uiRoot() { return fsRoot() || document.body; } // in Twitch fullscreen, only the fullscreen element's subtree renders -> mount there so Play stays reachable
  function isFs() { return !!fsRoot(); }
  // Fullscreen and windowed each remember their OWN spot, so dragging Play in one layout never moves it in the other.
  function curLaunchPos() { return isFs() ? launchPosFs : launchPos; }
  function curPanelPos()  { return isFs() ? posFs : pos; }

  // Twitch's live viewer-count chip (under the player). Anchor the launcher right next to it on first show
  // so it's immediately noticeable; returns null until that element exists (Twitch is a slow-rendering SPA).
  function viewerCountAnchor(w, h) {
    let el = null;
    const sels = [
      '[data-a-target="animated-channel-viewers-count"]',
      'p[data-a-target="animated-channel-viewers-count"]',
      '[data-a-target="channel-viewers-count"]',
    ];
    for (const s of sels) { const c = document.querySelector(s); if (c && c.getClientRects().length) { el = c; break; } }
    if (!el) return null;
    const r = el.getBoundingClientRect();
    if (r.width <= 0 && r.height <= 0) return null;
    return { x: r.right + 12, y: r.top + r.height / 2 - h / 2 }; // just to the right of the count, vertically centered on it
  }
  function positionLauncher() {
    if (!launcherEl) return;
    const w = launcherEl.offsetWidth || 92, h = launcherEl.offsetHeight || 38;
    const W = window.innerWidth, H = window.innerHeight;
    const saved = curLaunchPos();
    let p, settled;
    if (saved) { p = saved; settled = true; }                                  // user-dragged spot for this layout
    else if (!isFs()) {
      const a = viewerCountAnchor(w, h);
      if (a) { p = a; settled = true; }                                        // snapped beside the viewer count
      else { p = { x: 14, y: H - h - 96 }; settled = false; }                  // chip not rendered yet -> keep retrying next tick
    } else { p = { x: 14, y: H - h - 96 }; settled = true; }                    // fullscreen default: bottom-LEFT above the control bar
    if (settled) launchAnchored = true;
    p = { x: Math.min(Math.max(8, p.x), Math.max(8, W - w - 8)), y: Math.min(Math.max(8, p.y), Math.max(8, H - h - 8)) };
    launcherEl.style.left = p.x + "px";
    launcherEl.style.top = p.y + "px";
    launcherEl.style.bottom = "auto";
  }
  function injectLauncher() {
    if (launcherEl && launcherEl.isConnected) { if (launcherEl.parentNode !== uiRoot()) uiRoot().appendChild(launcherEl); return; }
    launcherEl = document.createElement("div");
    launcherEl.id = "abm-scg-launch";
    launcherEl.innerHTML =
      '<span class="abm-scg-emoji">\u{1F3AE}</span>' +
      '<span class="abm-scg-lbl">Play</span>' +
      '<span class="abm-scg-min" title="Minimize">–</span>';
    wireLauncherDrag(launcherEl);
    uiRoot().appendChild(launcherEl);
    renderLauncher();
    positionLauncher();
  }
  // Reflect open/minimized state onto the launcher (label, collapsed-dot class, tooltip).
  function renderLauncher() {
    if (!launcherEl) return;
    launcherEl.classList.toggle("abm-min", minimized);
    const lbl = launcherEl.querySelector(".abm-scg-lbl");
    if (lbl) lbl.textContent = open ? "Close game" : "Play";
    launcherEl.title = minimized
      ? "Sprite Cannon (click to expand)"
      : (open ? "Close Sprite Cannon (drag to move)" : "Play Sprite Cannon while you watch (drag to move)");
  }
  function wireLauncherDrag(el) {
    el.addEventListener("mousedown", (e) => {
      if (e.button !== 0) return;
      e.preventDefault();
      const onMin = !!(e.target && e.target.closest && e.target.closest(".abm-scg-min")); // clicked the minimize handle
      const rect = el.getBoundingClientRect();
      const offX = e.clientX - rect.left, offY = e.clientY - rect.top;
      const startX = e.clientX, startY = e.clientY;
      let moved = false;
      el.style.cursor = "grabbing";
      function move(ev) {
        if (!moved && Math.abs(ev.clientX - startX) + Math.abs(ev.clientY - startY) > 5) moved = true;
        if (moved) {
          const np = { x: ev.clientX - offX, y: ev.clientY - offY };
          if (isFs()) launchPosFs = np; else launchPos = np; // write to the current layout only
          positionLauncher();
        }
      }
      function up() {
        document.removeEventListener("mousemove", move, true);
        document.removeEventListener("mouseup", up, true);
        el.style.cursor = "grab";
        if (moved) {
          if (isFs()) { if (launchPosFs) chrome.storage.local.set({ twitchGameLaunchPosFs: launchPosFs }); }
          else { if (launchPos) chrome.storage.local.set({ twitchGameLaunchPos: launchPos }); }
        } else if (minimized) { setMinimized(false); }   // clean click on the dot -> expand the full pill
        else if (onMin) { setMinimized(true); }           // clean click on the "-" handle -> collapse to a dot
        else { setOpen(!open); }                          // clean click on the pill body -> toggle the game
      }
      document.addEventListener("mousemove", move, true);
      document.addEventListener("mouseup", up, true);
    });
  }
  function removeLauncher() {
    if (launcherEl && launcherEl.parentNode) launcherEl.parentNode.removeChild(launcherEl);
    launcherEl = null;
  }

  // ---- floating panel ------------------------------------------------------
  function clampPos(p) {
    const w = panelEl ? panelEl.offsetWidth || 360 : 360;
    const h = 44; // keep at least the title bar on-screen
    const W = window.innerWidth, H = window.innerHeight;
    return { x: Math.min(Math.max(8, p.x), Math.max(8, W - w - 8)), y: Math.min(Math.max(8, p.y), Math.max(8, H - h - 8)) };
  }
  function defaultPos() {
    const w = (size && size.w) || 360, h = (size && size.h) || 540;
    return { x: Math.max(8, window.innerWidth - w - 24), y: Math.max(8, window.innerHeight - h - 80) };
  }
  function placePanel() {
    if (!panelEl) return;
    const p = clampPos(curPanelPos() || defaultPos());
    panelEl.style.left = p.x + "px";
    panelEl.style.top = p.y + "px";
  }
  function setIframeChannel() {
    if (!panelEl) return;
    const fr = panelEl.querySelector("iframe");
    if (!fr) return;
    const want = gameUrl();
    // Only (re)load when the channel target actually changes - avoids reloading mid-play.
    if (fr.getAttribute("data-abm-url") !== want) {
      fr.setAttribute("data-abm-url", want);
      fr.dataset.abmLoaded = "";
      fr.addEventListener("load", () => { fr.dataset.abmLoaded = "1"; }, { once: true });
      fr.src = want;
      // Safety net: if the embed never fires `load` (rare transient that leaves a blank/black panel), reload it once.
      setTimeout(() => { if (fr.isConnected && fr.dataset.abmLoaded !== "1" && fr.getAttribute("data-abm-url") === want) fr.src = want; }, 2500);
    }
    const chEl = panelEl.querySelector(".abm-scg-ch");
    if (chEl) chEl.textContent = channel ? "@" + channel : "";
  }

  function buildPanel() {
    if (panelEl) {
      if (!panelEl.isConnected || panelEl.parentNode !== uiRoot()) uiRoot().appendChild(panelEl);
      placePanel();
      setIframeChannel();
      return;
    }
    panelEl = document.createElement("div");
    panelEl.id = "abm-scgame";
    if (size && size.w && size.h) {
      panelEl.style.width = size.w + "px";
      panelEl.style.height = size.h + "px";
    }
    panelEl.innerHTML =
      '<div class="abm-scg-hd">' +
        '<span class="abm-scg-ttl"><span>\u{1F3AE} Sprite Cannon</span><span class="abm-scg-ch"></span></span>' +
        '<span class="abm-scg-sp"></span>' +
        '<span class="abm-scg-btn abm-scg-close" title="Close">×</span>' +
      '</div>' +
      '<div class="abm-scg-body"><iframe allow="autoplay; fullscreen" referrerpolicy="no-referrer"></iframe></div>' +
      '<div class="abm-rz abm-rz-n" data-d="n"></div><div class="abm-rz abm-rz-s" data-d="s"></div>' +
      '<div class="abm-rz abm-rz-e" data-d="e"></div><div class="abm-rz abm-rz-w" data-d="w"></div>' +
      '<div class="abm-rz abm-rz-ne" data-d="ne"></div><div class="abm-rz abm-rz-nw" data-d="nw"></div>' +
      '<div class="abm-rz abm-rz-sw" data-d="sw"></div><div class="abm-rz abm-rz-se" data-d="se"></div>';
    uiRoot().appendChild(panelEl);

    panelEl.querySelector(".abm-scg-close").addEventListener("click", () => setOpen(false));
    wireDrag(panelEl.querySelector(".abm-scg-hd"));
    wireResize(Array.prototype.slice.call(panelEl.querySelectorAll(".abm-rz")));

    placePanel();
    setIframeChannel();
  }
  function destroyPanel() {
    if (panelEl && panelEl.parentNode) panelEl.parentNode.removeChild(panelEl);
    panelEl = null;
  }

  // While dragging/resizing, the iframe would swallow mouse events - disable its
  // pointer events for the duration so the gesture tracks smoothly.
  function lockIframe(lock) {
    if (!panelEl) return;
    const fr = panelEl.querySelector("iframe");
    if (fr) fr.style.pointerEvents = lock ? "none" : "";
  }

  function wireDrag(handle) {
    handle.addEventListener("mousedown", (e) => {
      if (e.button !== 0 || e.target.classList.contains("abm-scg-btn")) return;
      e.preventDefault();
      const rect = panelEl.getBoundingClientRect();
      const offX = e.clientX - rect.left, offY = e.clientY - rect.top;
      lockIframe(true);
      function move(ev) {
        const np = clampPos({ x: ev.clientX - offX, y: ev.clientY - offY });
        if (isFs()) posFs = np; else pos = np;
        panelEl.style.left = np.x + "px";
        panelEl.style.top = np.y + "px";
      }
      function up() {
        document.removeEventListener("mousemove", move, true);
        document.removeEventListener("mouseup", up, true);
        lockIframe(false);
        const np = curPanelPos();
        if (np) chrome.storage.local.set(isFs() ? { twitchGamePosFs: np } : { twitchGamePos: np });
      }
      document.addEventListener("mousemove", move, true);
      document.addEventListener("mouseup", up, true);
    });
  }

  // resize from ANY edge or corner; w/n edges move left/top as they shrink
  function wireResize(handles) {
    handles.forEach((handle) => {
      const dir = handle.getAttribute("data-d") || "se";
      handle.addEventListener("mousedown", (e) => {
        if (e.button !== 0) return;
        e.preventDefault(); e.stopPropagation();
        const rect = panelEl.getBoundingClientRect();
        const sx = e.clientX, sy = e.clientY, w0 = rect.width, h0 = rect.height, l0 = rect.left, t0 = rect.top;
        const minW = 200, minH = 180;
        const maxW = window.innerWidth * 0.96, maxH = window.innerHeight * 0.94;
        const clamp = (v, lo, hi) => Math.min(Math.max(v, lo), hi);
        lockIframe(true);
        function move(ev) {
          const dx = ev.clientX - sx, dy = ev.clientY - sy;
          let w = w0, h = h0, l = l0, t = t0;
          if (dir.indexOf("e") >= 0) w = clamp(w0 + dx, minW, maxW);
          if (dir.indexOf("s") >= 0) h = clamp(h0 + dy, minH, maxH);
          if (dir.indexOf("w") >= 0) { w = clamp(w0 - dx, minW, maxW); l = l0 + (w0 - w); }
          if (dir.indexOf("n") >= 0) { h = clamp(h0 - dy, minH, maxH); t = t0 + (h0 - h); }
          panelEl.style.width = w + "px"; panelEl.style.height = h + "px";
          panelEl.style.left = l + "px"; panelEl.style.top = t + "px";
        }
        function up() {
          document.removeEventListener("mousemove", move, true);
          document.removeEventListener("mouseup", up, true);
          lockIframe(false);
          size = { w: panelEl.offsetWidth, h: panelEl.offsetHeight };
          const np = clampPos({ x: panelEl.offsetLeft, y: panelEl.offsetTop });
          if (isFs()) posFs = np; else pos = np;
          panelEl.style.left = np.x + "px"; panelEl.style.top = np.y + "px";
          chrome.storage.local.set(isFs() ? { twitchGameSize: size, twitchGamePosFs: np } : { twitchGameSize: size, twitchGamePos: np });
        }
        document.addEventListener("mousemove", move, true);
        document.addEventListener("mouseup", up, true);
      });
    });
  }

  function setOpen(v) {
    if (open === v) return;
    open = v;
    chrome.storage.local.set({ twitchGameOpen: v });
    sync();
  }
  function setMinimized(v) {
    if (minimized === v) return;
    minimized = v;
    chrome.storage.local.set({ twitchGameMinimized: v });
    renderLauncher();
    positionLauncher(); // pill<->dot changes the footprint -> re-clamp so it stays on-screen
  }

  // ---- run -----------------------------------------------------------------
  function sync() {
    channel = currentLogin();
    if (!shouldShow()) { removeLauncher(); destroyPanel(); return; }
    ensureStyle();
    injectLauncher();
    // Until Play has settled (user-dragged, or snapped to the now-rendered viewer-count chip), keep retrying -
    // Twitch renders that element late, so the anchor only becomes available a few ticks after the page loads.
    if (!curLaunchPos() && !launchAnchored) positionLauncher();
    renderLauncher();
    if (open) buildPanel();
    else destroyPanel();
  }

  // Re-assert on a bounded tick: Twitch SPA re-renders wipe injected nodes, and
  // switching channels changes currentLogin() -> re-point the leaderboard channel.
  setInterval(() => {
    const prevCh = channel;
    sync();
    if (open && panelEl && channel !== prevCh) setIframeChannel();
  }, 1500);

  // Keep launcher + panel on-screen if the window shrinks.
  window.addEventListener("resize", () => { positionLauncher(); if (panelEl) placePanel(); });

  // Entering/leaving Twitch fullscreen swaps which subtree renders -> re-mount our
  // launcher (and any open panel) into the fullscreen element so Play stays usable.
  function onFsChange() {
    if (!shouldShow()) return;
    launchAnchored = false; // the layout changed -> re-evaluate this mode's own saved spot / anchor / default
    if (launcherEl) { uiRoot().appendChild(launcherEl); positionLauncher(); }
    if (open && panelEl) { uiRoot().appendChild(panelEl); placePanel(); }
  }
  document.addEventListener("fullscreenchange", onFsChange);
  document.addEventListener("webkitfullscreenchange", onFsChange);
})();
