// Sidekick Browser Companion — selection content script.
//
// Injected on demand by background.js when the user clicks "Mark Section".
// Draws a hover overlay highlighting the topmost meaningful ancestor of the
// pointer target, and on click captures a structured pin payload plus a
// curated computed-style snapshot, then forwards it to background.js.
//
// Idempotent: re-injection during an already-active session is a no-op.

(() => {
  if (window.__sidekickSelectionInstalled) return;
  window.__sidekickSelectionInstalled = true;

  const PIN_OVERLAY_ID = "__sidekick-pin-overlay";
  const PIN_LABEL_ID = "__sidekick-pin-label";
  const INTERACTIVE_TAGS = new Set(["BUTTON", "A", "INPUT", "SELECT", "TEXTAREA", "LABEL", "FORM"]);
  const SECTION_TAGS = new Set(["SECTION", "ARTICLE", "HEADER", "FOOTER", "NAV", "ASIDE", "MAIN"]);
  const COMPUTED_STYLE_KEYS = [
    "display", "position", "width", "height",
    "padding", "margin", "border", "border-radius",
    "color", "background-color", "background",
    "font-family", "font-size", "font-weight", "line-height", "letter-spacing", "text-align",
    "box-shadow", "opacity", "z-index", "overflow"
  ];

  let active = false;
  let passiveCapture = false;
  let lastTarget = null;

  const overlay = document.createElement("div");
  overlay.id = PIN_OVERLAY_ID;
  Object.assign(overlay.style, {
    position: "fixed",
    pointerEvents: "none",
    border: "2px solid rgb(0, 122, 255)",
    background: "rgba(0, 122, 255, 0.08)",
    borderRadius: "2px",
    zIndex: "2147483646",
    display: "none",
    transition: "all 60ms ease-out",
    boxSizing: "border-box"
  });

  const label = document.createElement("div");
  label.id = PIN_LABEL_ID;
  Object.assign(label.style, {
    position: "fixed",
    pointerEvents: "none",
    background: "rgb(0, 122, 255)",
    color: "white",
    font: "12px -apple-system, BlinkMacSystemFont, sans-serif",
    padding: "2px 6px",
    borderRadius: "3px",
    zIndex: "2147483647",
    display: "none",
    whiteSpace: "nowrap",
    boxShadow: "0 1px 3px rgba(0,0,0,0.2)"
  });

  function ensureMounted() {
    if (!overlay.parentNode) document.documentElement.appendChild(overlay);
    if (!label.parentNode) document.documentElement.appendChild(label);
  }

  function pickAncestor(node) {
    // Walk up from the literal hovered node to the topmost meaningful
    // container — avoid pinning the innermost <span> inside a button or a
    // text fragment inside a card. Prefer landmarks > interactive > the
    // current node itself.
    let current = node;
    let interactive = null;
    let landmark = null;
    let depth = 0;
    while (current && depth < 10) {
      if (current instanceof HTMLElement) {
        if (interactive == null && (INTERACTIVE_TAGS.has(current.tagName) || current.getAttribute("role") === "button")) {
          interactive = current;
        }
        if (landmark == null && (SECTION_TAGS.has(current.tagName) || current.getAttribute("role") === "main")) {
          landmark = current;
        }
      }
      current = current.parentElement;
      depth += 1;
    }
    return interactive || node;
  }

  function buildSelector(el) {
    if (!(el instanceof Element)) return null;
    const testid = el.getAttribute && el.getAttribute("data-testid");
    if (testid) return `[data-testid="${cssEscape(testid)}"]`;
    if (el.id) return `#${cssEscape(el.id)}`;
    const aria = el.getAttribute && el.getAttribute("aria-label");
    if (aria) return `${el.tagName.toLowerCase()}[aria-label="${cssEscape(aria)}"]`;
    // Build a short structural fallback up to depth 6.
    const parts = [];
    let node = el;
    let depth = 0;
    while (node && node.nodeType === 1 && depth < 6) {
      let part = node.tagName.toLowerCase();
      if (node.classList && node.classList.length) {
        const classes = Array.from(node.classList).slice(0, 3).map(cssEscape).join(".");
        if (classes) part += "." + classes;
      }
      const parent = node.parentElement;
      if (parent) {
        const sameTagSiblings = Array.from(parent.children).filter((c) => c.tagName === node.tagName);
        if (sameTagSiblings.length > 1) {
          const idx = sameTagSiblings.indexOf(node) + 1;
          part += `:nth-of-type(${idx})`;
        }
      }
      parts.unshift(part);
      if (parent && parent === document.body) break;
      node = parent;
      depth += 1;
    }
    return parts.join(" > ");
  }

  function cssEscape(value) {
    if (window.CSS && CSS.escape) return CSS.escape(value);
    return String(value).replace(/(["\\\.#\[\]:])/g, "\\$1");
  }

  function curatedComputedStyles(el) {
    const cs = window.getComputedStyle(el);
    const result = {};
    for (const key of COMPUTED_STYLE_KEYS) {
      const v = cs.getPropertyValue(key);
      if (v) result[key] = v.trim();
    }
    return result;
  }

  function safeOuterHTML(el) {
    // Defensively strip <input type="password"> values before serializing.
    try {
      const clone = el.cloneNode(true);
      const inputs = clone.querySelectorAll && clone.querySelectorAll("input[type=password], input[autocomplete*=password]");
      if (inputs) inputs.forEach((n) => { try { n.removeAttribute("value"); } catch (_) {} });
      // Also redact any value on inputs of type=hidden which often carry tokens.
      const hidden = clone.querySelectorAll && clone.querySelectorAll("input[type=hidden]");
      if (hidden) hidden.forEach((n) => { try { n.setAttribute("value", "[REDACTED]"); } catch (_) {} });
      const raw = clone.outerHTML || "";
      return raw.length > 40000 ? raw.slice(0, 40000) : raw;
    } catch (_) {
      return null;
    }
  }

  function visibleTextSnippet(el) {
    try {
      const raw = (el.innerText || el.textContent || "").trim();
      return raw.length > 4000 ? raw.slice(0, 4000) : raw;
    } catch (_) {
      return null;
    }
  }

  function describeRole(el) {
    const role = el.getAttribute && el.getAttribute("role");
    if (role) return role;
    if (INTERACTIVE_TAGS.has(el.tagName)) return el.tagName.toLowerCase();
    return null;
  }

  function describeAriaName(el) {
    const aria = el.getAttribute && el.getAttribute("aria-label");
    if (aria) return aria;
    const labelledBy = el.getAttribute && el.getAttribute("aria-labelledby");
    if (labelledBy) {
      const ids = labelledBy.split(/\s+/);
      const texts = ids.map((id) => {
        const ref = document.getElementById(id);
        return ref ? (ref.innerText || ref.textContent || "").trim() : "";
      });
      return texts.filter(Boolean).join(" ");
    }
    return null;
  }

  function detectColorScheme(el) {
    // Prefer the element's effective `color-scheme`, then the page's, then the
    // OS/user preference. "this blue is too baby blue" only parses with theme.
    try {
      const elScheme = getComputedStyle(el).colorScheme;
      if (elScheme && elScheme !== "normal") return elScheme;
      const rootScheme = getComputedStyle(document.documentElement).colorScheme;
      if (rootScheme && rootScheme !== "normal") return rootScheme;
    } catch (_) {
      /* getComputedStyle can throw on detached nodes */
    }
    if (window.matchMedia) {
      if (window.matchMedia("(prefers-color-scheme: dark)").matches) return "dark";
      if (window.matchMedia("(prefers-color-scheme: light)").matches) return "light";
    }
    return null;
  }

  function buildElementPayload(el) {
    const rect = el.getBoundingClientRect();
    const dpr = window.devicePixelRatio || 1;
    return {
      tagName: el.tagName.toLowerCase(),
      selector: buildSelector(el),
      role: describeRole(el),
      ariaName: describeAriaName(el),
      colorScheme: detectColorScheme(el),
      bounds: {
        x: rect.left,
        y: rect.top,
        width: rect.width,
        height: rect.height,
        devicePixelRatio: dpr
      },
      viewport: {
        width: window.innerWidth,
        height: window.innerHeight,
        devicePixelRatio: dpr,
        scrollX: window.scrollX,
        scrollY: window.scrollY
      },
      computedStyles: curatedComputedStyles(el),
      outerHTML: safeOuterHTML(el),
      visibleText: visibleTextSnippet(el),
      clickedAt: Date.now() / 1000
    };
  }

  function paintOverlay(el) {
    if (!(el instanceof Element)) return;
    const rect = el.getBoundingClientRect();
    overlay.style.left = `${Math.round(rect.left)}px`;
    overlay.style.top = `${Math.round(rect.top)}px`;
    overlay.style.width = `${Math.round(rect.width)}px`;
    overlay.style.height = `${Math.round(rect.height)}px`;
    overlay.style.display = "block";

    const tag = el.tagName.toLowerCase();
    const id = el.id ? `#${el.id}` : "";
    const dims = `${Math.round(rect.width)}×${Math.round(rect.height)}`;
    label.textContent = `${tag}${id} · ${dims}`;
    const labelLeft = Math.max(4, Math.round(rect.left));
    const labelTop = Math.max(4, Math.round(rect.top) - 22);
    label.style.left = `${labelLeft}px`;
    label.style.top = `${labelTop}px`;
    label.style.display = "block";
  }

  function hideOverlay() {
    overlay.style.display = "none";
    label.style.display = "none";
  }

  function onMouseMove(e) {
    if (!active) return;
    const target = document.elementFromPoint(e.clientX, e.clientY);
    if (!target || target === overlay || target === label) return;
    const picked = pickAncestor(target);
    if (picked !== lastTarget) {
      lastTarget = picked;
      paintOverlay(picked);
    }
  }

  function onClick(e) {
    if (!active) return;
    e.preventDefault();
    e.stopPropagation();
    const target = document.elementFromPoint(e.clientX, e.clientY);
    const picked = target ? pickAncestor(target) : null;
    if (!picked) return;
    const payload = buildElementPayload(picked);
    chrome.runtime.sendMessage({ type: "sidekick.pinFromPage", payload }).catch(() => {});
    stop(); // single-shot per arming
  }

  function onPassiveClick(e) {
    if (!passiveCapture || active) return;
    const target = document.elementFromPoint(e.clientX, e.clientY);
    const picked = target ? pickAncestor(target) : null;
    if (!picked) return;
    const payload = buildElementPayload(picked);
    chrome.runtime.sendMessage({ type: "sidekick.browserClickFromPage", payload }).catch(() => {});
  }

  function onKey(e) {
    if (!active) return;
    if (e.key === "Escape") {
      stop();
    }
  }

  function start() {
    if (active) return;
    active = true;
    ensureMounted();
    document.addEventListener("mousemove", onMouseMove, true);
    document.addEventListener("click", onClick, true);
    document.addEventListener("keydown", onKey, true);
    document.documentElement.style.cursor = "crosshair";
  }

  function stop() {
    if (!active) return;
    active = false;
    document.removeEventListener("mousemove", onMouseMove, true);
    document.removeEventListener("click", onClick, true);
    document.removeEventListener("keydown", onKey, true);
    document.documentElement.style.cursor = "";
    hideOverlay();
    lastTarget = null;
  }

  chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
    if (!msg || typeof msg.type !== "string") {
      sendResponse({ ok: false });
      return;
    }
    if (msg.type === "sidekick.startSelection") {
      start();
      sendResponse({ ok: true });
    } else if (msg.type === "sidekick.stopSelection") {
      stop();
      sendResponse({ ok: true });
    } else if (msg.type === "sidekick.setPassiveCapture") {
      passiveCapture = !!msg.enabled;
      sendResponse({ ok: true, enabled: passiveCapture });
    } else {
      sendResponse({ ok: false });
    }
    return false;
  });

  document.addEventListener("click", onPassiveClick, true);
})();
