// Hover-to-learn: highlight known orchestra/singer names on any page and show an
// info card (bio, key facts, library count) on hover. Non-destructive-ish: wraps
// only matched text spans, runs in the content-script isolated world.
import { buildMatcher, norm, surname } from "./terms.js";

const KEY_ENABLED = "highlightEnabled";
const SKIP_TAGS = new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEXTAREA", "CODE", "KBD", "SAMP",
  "SELECT", "OPTION", "INPUT", "TITLE"]);
const MAX_WRAPS = 600;

let matcher = null;
const entryByKey = new Map();   // norm(phrase) -> learning entry
const seenEntries = new Set();  // entries already highlighted (first-mention-only)
let enabled = true;
let wrapCount = 0;
let mutating = false;
let scanTimer = null;
let mo = null;
let showTimer = null;
let pendingTerm = null;
const SHOW_DELAY = 300;         // hover-intent delay before the card appears
const send = (m) => chrome.runtime.sendMessage(m).catch(() => null);

init();

async function init() {
  try {
    const st = await chrome.storage.local.get(KEY_ENABLED);
    if (st && st[KEY_ENABLED] === false) enabled = false;
  } catch (e) { /* */ }

  const resp = await send({ type: "learningTerms" });
  const entries = (resp && resp.entries) || [];
  if (!entries.length) return;

  const phrases = [];
  const addPhrase = (p, e) => {
    const k = norm(p);
    if (k && !entryByKey.has(k)) { entryByKey.set(k, e); phrases.push(p); }
  };
  for (const e of entries) {
    addPhrase(e.name, e);
    if (e.kind === "orchestra") { const sn = surname(e.name); if (sn) addPhrase(sn, e); }
  }
  matcher = buildMatcher(phrases);
  if (!matcher) return;

  injectStyle();
  ensureCard();
  document.addEventListener("mouseover", onOver, true);
  document.addEventListener("mouseout", onOut, true);
  document.addEventListener("click", onClick, true);
  document.addEventListener("keydown", onKey, true);
  if (enabled && document.body) scan(document.body);
  observe();

  try {
    chrome.storage.onChanged.addListener((ch, area) => {
      if (area === "local" && KEY_ENABLED in ch) {
        enabled = ch[KEY_ENABLED].newValue !== false;
        if (enabled) scan(document.body); else unwrapAll();
      }
    });
  } catch (e) { /* */ }
}

// ---- highlight scanning ---------------------------------------------------
function scan(root) {
  if (!root || !matcher || !enabled || wrapCount >= MAX_WRAPS) return;
  mutating = true;
  try {
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
      acceptNode(node) {
        const v = node.nodeValue;
        if (!v || v.length < 4 || !/\S/.test(v)) return NodeFilter.FILTER_REJECT;
        const p = node.parentNode;
        if (!p || p.nodeType !== 1) return NodeFilter.FILTER_REJECT;
        if (SKIP_TAGS.has(p.nodeName) || p.isContentEditable) return NodeFilter.FILTER_REJECT;
        if (p.closest("#eleje-hover-host, .eleje-term")) return NodeFilter.FILTER_REJECT;
        return NodeFilter.FILTER_ACCEPT;
      },
    });
    const nodes = [];
    let n;
    while ((n = walker.nextNode())) nodes.push(n);
    for (const node of nodes) {
      if (wrapCount >= MAX_WRAPS) break;
      wrapNode(node);
    }
  } catch (e) { /* */ } finally { mutating = false; }
}

function wrapNode(node) {
  const text = node.nodeValue;
  matcher.lastIndex = 0;
  let m, last = 0, frag = null;
  while ((m = matcher.exec(text))) {
    const key = norm(m[1]);
    const entry = entryByKey.get(key);
    if (!entry || seenEntries.has(entry)) continue;   // first-mention-only, per name
    if (!frag) frag = document.createDocumentFragment();
    if (m.index > last) frag.appendChild(document.createTextNode(text.slice(last, m.index)));
    const span = document.createElement("span");
    span.className = "eleje-term";
    span.dataset.key = key;
    span.textContent = m[0];
    frag.appendChild(span);
    seenEntries.add(entry);
    last = m.index + m[0].length;
    if (++wrapCount >= MAX_WRAPS) break;
  }
  if (frag) {
    if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last)));
    if (node.parentNode) node.parentNode.replaceChild(frag, node);
  }
}

function unwrapAll() {
  document.querySelectorAll(".eleje-term").forEach(s => {
    s.replaceWith(document.createTextNode(s.textContent));
  });
  wrapCount = 0;
  seenEntries.clear();
  hideCard();
}

function observe() {
  mo = new MutationObserver(muts => {
    if (!enabled || mutating) return;
    clearTimeout(scanTimer);
    const added = [];
    for (const mu of muts) for (const node of mu.addedNodes) {
      if (node.nodeType === 1 && node.id !== "eleje-hover-host" &&
          !(node.classList && node.classList.contains("eleje-term"))) added.push(node);
    }
    if (!added.length) return;
    scanTimer = setTimeout(() => added.forEach(scan), 400);
  });
  if (document.body) mo.observe(document.body, { childList: true, subtree: true });
}

// ---- card -----------------------------------------------------------------
let host, shadow, cardEl, hideTimer, overCard = false;

function injectStyle() {
  const s = document.createElement("style");
  s.id = "eleje-page-style";
  s.textContent = ".eleje-term{border-bottom:1px dotted rgba(242,66,87,.85);cursor:help;}";
  (document.head || document.documentElement).appendChild(s);
}

const CARD_CSS = `
  .card{position:fixed;z-index:2147483600;width:300px;max-width:calc(100vw - 24px);
    background:#141414;color:#f5f5f5;border:1px solid #262626;border-radius:11px;
    box-shadow:0 10px 30px rgba(0,0,0,.5);font:13px/1.45 -apple-system,BlinkMacSystemFont,
    "Segoe UI",Helvetica,Arial,sans-serif;padding:11px 13px;}
  /* Transparent BRIDGES that extend the card's hover area into the gap above and below
     it, so moving the pointer from the highlighted term onto the card never crosses a
     dead zone (the card's mouseenter keeps firing). Absolutely positioned → no effect on
     the card's measured height. */
  .card::before,.card::after{content:"";position:absolute;left:0;right:0;height:14px;}
  .card::before{top:-14px;}
  .card::after{bottom:-14px;}
  .kind{font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:#f24257;font-weight:700;}
  .nm{font-size:16px;font-weight:700;margin:1px 0 1px;}
  .sub{color:#cfcfcf;font-size:12px;margin-bottom:6px;}
  .fields{color:#cfcfcf;font-size:11.5px;margin:5px 0;}
  .fields b{color:#f5f5f5;font-weight:600;}
  .bio{font-size:12.5px;margin:6px 0;}
  .lib{font-size:12px;color:#f24257;border-top:1px solid #262626;padding-top:7px;margin-top:7px;}
  .lib .smp{color:#cfcfcf;}
  .foot{display:flex;gap:12px;border-top:1px solid #262626;padding-top:8px;margin-top:8px;}
  .foot a{color:#f24257;text-decoration:none;cursor:pointer;font-size:12px;}
  .foot a.off{color:#8a8a8a;margin-left:auto;}
  .foot a:hover{text-decoration:underline;}
`;

function ensureCard() {
  if (host) return;
  host = document.createElement("div");
  host.id = "eleje-hover-host";
  shadow = host.attachShadow({ mode: "open" });
  const style = document.createElement("style");
  style.textContent = CARD_CSS;
  cardEl = document.createElement("div");
  cardEl.className = "card";
  cardEl.style.display = "none";
  shadow.append(style, cardEl);
  document.documentElement.appendChild(host);
  // Track whether the pointer is on the card (incl. its bridges) so a scheduled hide
  // never closes it out from under the cursor.
  cardEl.addEventListener("mouseenter", () => { overCard = true; clearTimeout(hideTimer); });
  cardEl.addEventListener("mouseleave", () => { overCard = false; scheduleHide(); });
}

function esc(s) {
  return (s == null ? "" : String(s)).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function onOver(e) {
  const t = e.target;
  if (!(t instanceof HTMLElement) || !t.classList.contains("eleje-term")) return;
  clearTimeout(hideTimer);
  clearTimeout(showTimer);
  pendingTerm = t;
  showTimer = setTimeout(() => { if (pendingTerm) showCard(pendingTerm); }, SHOW_DELAY);
}
function onOut(e) {
  const t = e.target;
  if (!(t instanceof HTMLElement) || !t.classList.contains("eleje-term")) return;
  clearTimeout(showTimer);
  pendingTerm = null;
  scheduleHide();                 // closes unless the pointer reaches the card
}
function onClick(e) {
  const t = e.target;
  if (t instanceof HTMLElement && t.classList.contains("eleje-term")) {
    clearTimeout(showTimer); clearTimeout(hideTimer); pendingTerm = null; showCard(t);
  }
}
function onKey(e) { if (e.key === "Escape") { clearTimeout(showTimer); hideCard(); } }
// A SCHEDULED hide only closes the card when the pointer is NOT on it (the user really
// moved away). Direct hideCard() calls (Escape, unwrap, toggle-off) still force-close.
function scheduleHide() { clearTimeout(hideTimer); hideTimer = setTimeout(() => { if (!overCard) hideCard(); }, 320); }
function hideCard() { overCard = false; if (cardEl) cardEl.style.display = "none"; }

async function showCard(termEl) {
  const entry = entryByKey.get(termEl.dataset.key);
  if (!entry) return;
  const fields = (entry.fields || []).slice(0, 5)
    .map(f => `<b>${esc(f.label)}:</b> ${esc(f.value)}`).join(" &nbsp;·&nbsp; ");
  const bio = entry.bio ? esc(entry.bio.length > 260 ? entry.bio.slice(0, 257) + "…" : entry.bio) : "";
  cardEl.innerHTML = `
    <div class="kind">${entry.kind === "orchestra" ? "Orchestra" : "Singer"}</div>
    <div class="nm">${esc(entry.name)}</div>
    ${entry.subtitle ? `<div class="sub">${esc(entry.subtitle)}</div>` : ""}
    ${fields ? `<div class="fields">${fields}</div>` : ""}
    ${bio ? `<div class="bio">${bio}</div>` : ""}
    <div class="lib" id="eleje-lib">…</div>
    <div class="foot"><a class="open">Open in El Eje ▸</a><a class="off">Hide highlights</a></div>`;
  cardEl.querySelector(".open").addEventListener("click", () => send({ type: "openPanel", query: entry.name }));
  cardEl.querySelector(".off").addEventListener("click", async () => {
    try { await chrome.storage.local.set({ [KEY_ENABLED]: false }); } catch (e) { unwrapAll(); }
  });

  position(termEl);
  cardEl.style.display = "block";

  const info = await send({ type: "artistInfo", name: entry.name });
  const lib = shadow.getElementById("eleje-lib");
  if (!lib) return;
  if (info && info.count) {
    const smp = (info.samples || []).map(s => `${esc(s.t)}${s.y ? " (" + esc(s.y) + ")" : ""}`).join(" · ");
    lib.innerHTML = `${info.count} recording${info.count === 1 ? "" : "s"} in your library` +
      (smp ? ` <span class="smp">— ${smp}</span>` : "");
  } else {
    lib.textContent = "Not in your library yet.";
  }
}

function position(termEl) {
  const r = termEl.getBoundingClientRect();
  const w = 300, vw = window.innerWidth, vh = window.innerHeight;
  let left = Math.min(r.left, vw - w - 12);
  left = Math.max(12, left);
  cardEl.style.left = left + "px";
  cardEl.style.visibility = "hidden";
  cardEl.style.display = "block";
  const h = cardEl.offsetHeight || 180;
  cardEl.style.visibility = "";
  let top = r.bottom + 8;
  if (top + h > vh - 8) top = Math.max(8, r.top - h - 8);
  cardEl.style.top = top + "px";
}
