/* adom-schematic-viewer core — KiCad-native schematic SVG + a component hotspot
 * overlay. Hover a symbol → what it is (ref/value/footprint) + live stock & price.
 * Consumes window.__SCH__ = { svg, meta } (embed) OR fetches /state (live). */
(function () {
  "use strict";
  const $ = (s, r) => (r || document).querySelector(s);
  const state = { meta: null, pinned: null, enrichCache: {} };

  function mount(svgText, meta) {
    state.meta = meta || { components: [] };
    const box = $("#svgbox");
    box.innerHTML = svgText;
    const svg = box.querySelector("svg");
    if (!svg) return;
    svg.removeAttribute("width"); svg.removeAttribute("height");
    svg.style.width = "100%"; svg.style.height = "100%";
    state.svg = svg;
    state.vb = (svg.getAttribute("viewBox") || "0 0 100 100").split(/\s+/).map(Number);
    state.vb0 = state.vb.slice();
    wireHover(svg);
    wirePanZoom(svg);
    buildCompList();
    setStat();
    updateChrome();
  }
  // Show the Components panel + stat only when there's room (standalone tab or a
  // fullscreened pane); hide it in a small embedded thumbnail (APM board view).
  function updateChrome() {
    const big = innerWidth > 620;
    ["#side", "#stat"].forEach((s) => { const e = $(s); if (e) e.style.display = big ? "" : "none"; });
  }
  addEventListener("resize", updateChrome);

  // ── component highlight: screen-blend teal over the symbol (turns the symbol
  // strokes teal, leaves the white sheet white — no box) + list sync ──────────
  function clearSym() {
    state.svg.classList.remove("dim");
    state.svg.querySelectorAll(".symhl").forEach((el) => el.classList.remove("symhl"));
    document.querySelectorAll("#complist .crow.sel").forEach((r) => r.classList.remove("sel"));
  }
  function compHl(ref) {
    clearSym();
    // recolour the hovered symbol's real KiCad lines teal + dim the rest
    state.svg.classList.add("dim");
    state.svg.querySelectorAll('.adom-sheet [data-cref="' + cssEsc(ref) + '"]').forEach((el) => el.classList.add("symhl"));
    document.querySelectorAll("#complist .crow").forEach((r) => r.classList.toggle("sel", r.getAttribute("data-ref") === ref));
  }
  function clearHl() { if (!state.pinned) clearSym(); }
  function wireHover(svg) {
    svg.addEventListener("mousemove", (e) => {
      state.mouse = e; // remembered so a pin lands where the card already is
      if (state.pinned) return; // keep the pinned card + highlight stable
      const c = e.target.closest(".adom-comp");
      if (c) { const ref = c.getAttribute("data-ref"); compHl(ref); showCard(ref, e); return; }
      clearHl(); hideCard();
    });
    svg.addEventListener("mouseleave", () => { if (!state.pinned) { clearHl(); hideCard(); } });
    svg.addEventListener("click", (e) => {
      const c = e.target.closest(".adom-comp");
      if (!c) { state.pinned = null; clearSym(); hideCard(); return; }
      const ref = c.getAttribute("data-ref");
      state.pinned = state.pinned === ref ? null : ref;
      // pin: keep the card exactly where the hover card already sits, and expand
      // it to the full detail view so the links become reachable.
      if (state.pinned) { compHl(ref); showCard(ref, e); } else { clearSym(); hideCard(); }
    });
  }

  function comp(ref) { return (state.meta.components || []).find((k) => k.reference === ref); }
  // Abridged while merely hovering on a small/embedded screen (APM's board pane):
  // reference · MPN · stock, nothing else. Pinning always shows the full card.
  function isBrief(ref) { return state.pinned !== ref && innerWidth <= 620; }
  function showCard(ref, e) {
    const c = comp(ref); if (!c) return;
    const card = $("#card");
    const pinned = state.pinned === ref, brief = isBrief(ref);
    const rows = [];
    if (brief) {
      rows.push('<div class="ci-h"><span class="ci-ref">' + escapeHtml(ref) + "</span></div>");
      // real MPN arrives with enrichment; show the schematic value meanwhile
      rows.push('<div class="ci-mfr" id="ci-mpn">' + escapeHtml(c.value || "") + "</div>");
      rows.push('<div class="ci-enrich" id="ci-enrich">' + (c.lcsc || c.value ? '<span class="muted">stock…</span>' : "") + "</div>");
    } else {
      rows.push('<div class="ci-h"><span class="ci-ref">' + escapeHtml(ref) + '</span><span class="ci-val">' + escapeHtml(c.value || "") + "</span></div>");
      if (c.description) rows.push('<div class="ci-desc">' + escapeHtml(c.description) + "</div>");
      if (c.footprint) rows.push(row("footprint", c.footprint.split(":").pop()));
      if (c.lcsc) rows.push(row("lcsc", c.lcsc));
      rows.push('<div class="ci-enrich" id="ci-enrich">' + (c.lcsc || c.value ? '<span class="muted">loading stock · price…</span>' : "") + "</div>");
      if (c.datasheet && c.datasheet !== "~") rows.push('<a class="ci-ds" href="' + escapeHtml(c.datasheet) + '" target="_blank" rel="noopener">datasheet ↗</a>');
    }
    card.innerHTML = rows.join("");
    card.classList.toggle("brief", brief);
    card.style.display = "block";
    // clickable only when pinned, so hovering never eats the pointer
    card.classList.toggle("pinned", pinned);
    // follow the cursor while hovering; once pinned it stays put where it is —
    // but the pinned card is bigger than the brief one it replaced, so pull it
    // back inside the viewport if that growth pushed it off an edge.
    if (!pinned) placeCard(card, e || state.mouse); else clampCard(card);
    state.briefMode = brief;
    if (c.lcsc || c.value) enrich(c.value, c.lcsc);
  }
  // place near the cursor, flipping to the other side at the viewport edges
  function placeCard(card, e) {
    if (!e) { card.style.left = "12px"; card.style.top = "12px"; return; }
    const pad = 14, w = card.offsetWidth, h = card.offsetHeight;
    let x = e.clientX + pad, y = e.clientY + pad;
    if (x + w > innerWidth) x = e.clientX - w - pad;
    if (y + h > innerHeight) y = e.clientY - h - pad;
    card.style.left = Math.max(4, x) + "px";
    card.style.top = Math.max(4, y) + "px";
  }
  // keep an already-placed card fully inside the viewport
  function clampCard(card) {
    const w = card.offsetWidth, h = card.offsetHeight;
    const x = parseFloat(card.style.left) || 0, y = parseFloat(card.style.top) || 0;
    card.style.left = Math.max(4, Math.min(x, innerWidth - w - 4)) + "px";
    card.style.top = Math.max(4, Math.min(y, innerHeight - h - 4)) + "px";
  }
  function row(k, v) { return '<div class="ci-row"><span class="ci-k">' + k + '</span><span class="ci-v">' + escapeHtml(String(v)) + "</span></div>"; }
  function hideCard() { const c = $("#card"); if (c && !state.pinned) { c.style.display = "none"; c.classList.remove("pinned", "brief"); } }

  // ── enrichment (stock/price via LCSC PN, wiki page + popularity) ──────────
  function enrich(value, lcsc) {
    const key = (value || "") + "|" + (lcsc || "");
    const render = (d) => {
      const el = $("#ci-enrich"); if (!el || state.enrichKey !== key) return;
      // abridged card: the real MPN + stock, nothing else
      if (state.briefMode) {
        const m = $("#ci-mpn");
        if (m && d && d.mpn) m.textContent = d.mpn;
        if (!d || d.error || d.stock == null) { el.innerHTML = '<span class="muted">no stock data</span>'; return; }
        el.innerHTML = '<div class="pills"><span class="pill ' + (d.stock > 0 ? "ok" : "no") + '">' + (d.stock > 0 ? d.stock + " in stock" : "no stock") + "</span></div>";
        return;
      }
      if (!d || d.error) { el.innerHTML = '<span class="muted">no stock/price found</span>'; return; }
      const parts = [];
      if (d.stock != null) parts.push('<span class="pill ' + (d.stock > 0 ? "ok" : "no") + '">' + (d.stock > 0 ? d.stock + " in stock" : "no stock") + "</span>");
      if (d.price) parts.push('<span class="pill">' + escapeHtml(d.price) + "</span>");
      if (d.tier) parts.push('<span class="pill tier">' + escapeHtml(d.tier) + "</span>");
      if (d.popularity != null) parts.push('<span class="pill pop">★ ' + d.popularity + "</span>");
      let html = "";
      if (d.mpn) html += '<div class="ci-mfr">' + escapeHtml(d.mpn) + "</div>";
      if (d.mfr) html += '<div class="ci-mfr muted">' + escapeHtml(d.mfr) + "</div>";
      html += '<div class="pills">' + parts.join("") + "</div>";
      const links = [];
      if (d.jlc_url) links.push('<a class="ci-lnk" href="' + escapeHtml(d.jlc_url) + '" target="_blank" rel="noopener">JLCPCB ↗</a>');
      if (d.wiki_url) links.push('<a class="ci-lnk" href="' + escapeHtml(d.wiki_url) + '" target="_blank" rel="noopener">wiki page ↗</a>');
      if (links.length) html += '<div class="ci-links">' + links.join("") + "</div>";
      el.innerHTML = html || '<span class="muted">no data</span>';
    };
    state.enrichKey = key;
    if (state.enrichCache[key]) return render(state.enrichCache[key]);
    // NOTE: relative "enrich" resolves against whatever serves the page — our own
    // /enrich when running standalone, or the HOST app's (APM's
    // /board-view/<slug>/enrich) when embedded. So embeds get live stock too.
    if (typeof fetch !== "function") return render(null);
    const url = "enrich?mpn=" + encodeURIComponent(value || "") + (lcsc ? "&lcsc=" + encodeURIComponent(lcsc) : "");
    let tries = 0;
    const go = () => fetch(url).then((r) => r.json()).then((d) => {
      if (d && d.pending && tries++ < 25) { setTimeout(go, 1500); return; }
      state.enrichCache[key] = d; render(d);
    }).catch(() => render(null));
    go();
  }

  // ── pan / zoom ────────────────────────────────────────────────────────────
  function wirePanZoom(svg) {
    svg.addEventListener("wheel", (e) => {
      e.preventDefault();
      const [x, y, w, h] = state.vb;
      const f = e.deltaY > 0 ? 1.12 : 0.893;
      const r = svg.getBoundingClientRect();
      const mx = x + ((e.clientX - r.left) / r.width) * w, my = y + ((e.clientY - r.top) / r.height) * h;
      const nw = Math.min(state.vb0[2] * 8, Math.max(state.vb0[2] / 200, w * f)), nh = nw * (h / w);
      state.vb = [mx - (mx - x) * (nw / w), my - (my - y) * (nh / h), nw, nh];
      svg.setAttribute("viewBox", state.vb.join(" "));
    }, { passive: false });
    let drag = null;
    svg.addEventListener("mousedown", (e) => { drag = { x: e.clientX, y: e.clientY, vb: state.vb.slice() }; });
    addEventListener("mouseup", () => (drag = null));
    addEventListener("mousemove", (e) => {
      if (!drag) return;
      const r = svg.getBoundingClientRect();
      state.vb = [drag.vb[0] - ((e.clientX - drag.x) / r.width) * drag.vb[2], drag.vb[1] - ((e.clientY - drag.y) / r.height) * drag.vb[3], drag.vb[2], drag.vb[3]];
      svg.setAttribute("viewBox", state.vb.join(" "));
    });
  }
  function fit() { if (state.svg) { state.vb = state.vb0.slice(); state.svg.setAttribute("viewBox", state.vb.join(" ")); } }

  // ── component list ────────────────────────────────────────────────────────
  function buildCompList() {
    const el = $("#complist"); if (!el) return;
    const cs = (state.meta.components || []).slice().sort((a, b) => a.reference.localeCompare(b.reference, undefined, { numeric: true }));
    el.innerHTML = cs.map((c) => '<div class="crow" data-ref="' + escapeHtml(c.reference) + '"><b>' + escapeHtml(c.reference) + "</b> " + escapeHtml(c.value || "") + "</div>").join("") || '<div class="muted">no components</div>';
    el.querySelectorAll(".crow").forEach((r) => {
      const ref = r.getAttribute("data-ref");
      r.addEventListener("mouseenter", () => { if (!state.pinned) compHl(ref); });
      r.addEventListener("mouseleave", () => { if (!state.pinned) clearHl(); });
      r.addEventListener("click", () => { state.pinned = state.pinned === ref ? null : ref; if (state.pinned) { compHl(ref); showCard(ref); } else { clearSym(); hideCard(); } });
    });
  }
  function setStat() { const el = $("#stat"); if (el) el.textContent = (state.meta.components ? state.meta.components.length : 0) + " components"; }

  // ── helpers ───────────────────────────────────────────────────────────────
  function escapeHtml(s) { return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c])); }
  function cssEsc(s) { return String(s).replace(/["\\]/g, "\\$&"); }

  window.AdomSch = { mount, fit, compHl };
  function boot() {
    $("#fitbtn") && $("#fitbtn").addEventListener("click", fit);
    if (window.__SCH__) { window.__EMBED__ = true; mount(window.__SCH__.svg, window.__SCH__.meta); return; }
    fetch("state").then((r) => r.json()).then((s) => {
      if (s && s.svg) mount(s.svg, s.meta || {});
      else $("#svgbox").innerHTML = '<div class="empty">No schematic loaded. POST a .kicad_sch to /load.</div>';
    }).catch(() => { $("#svgbox").innerHTML = '<div class="empty">server unreachable</div>'; });
  }
  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot); else boot();
})();