const DEFAULT_ENDPOINT = "http://127.0.0.1:8765/screenshot";
const HISTORY_KEY = "capture_history_v1";
const HISTORY_LIMIT = 25;
const EXTRA_INSTRUCTIONS_KEY = "extra_instructions_v1";

function $(id) {
  return document.getElementById(id);
}

function setStatus(msg, kind) {
  const el = $("status");
  el.textContent = msg;
  el.className = kind || "";
}

async function storageGet(key) {
  return new Promise((resolve) => {
    chrome.storage.local.get([key], (res) => resolve(res[key]));
  });
}

async function storageSet(obj) {
  return new Promise((resolve) => {
    chrome.storage.local.set(obj, () => resolve());
  });
}

function clampString(s, maxLen) {
  const t = String(s || "");
  if (t.length <= maxLen) return t;
  return t.slice(0, Math.max(0, maxLen - 1)) + "…";
}

function renderResult(entryOrResp) {
  const resp = entryOrResp && entryOrResp.png_path ? entryOrResp : entryOrResp && entryOrResp.resp ? entryOrResp.resp : entryOrResp;
  const meta = entryOrResp && entryOrResp.title ? entryOrResp : null;

  const lines = [];
  lines.push("Saved:");
  lines.push(`  PNG:  ${resp.png_path || "(unknown)"}`);
  lines.push(`  META: ${resp.meta_path || "(unknown)"}`);
  if (resp.content_path) lines.push(`  CONTENT: ${resp.content_path}`);
  if (meta && meta.url) lines.push(`  URL:  ${meta.url}`);
  if (meta && meta.extra_instructions) lines.push(`  EXTRA: ${clampString(meta.extra_instructions, 220)}`);

  if (resp.ai_result) {
    if (resp.ai_result.ok && resp.ai_result.ai && Array.isArray(resp.ai_result.ai.posts)) {
      lines.push("");
      {
        const ms = resp.ai_result.took_ms || "?";
        const model = resp.ai_result.model ? ` model=${resp.ai_result.model}` : "";
        const rid = resp.ai_result.response_id ? ` id=${resp.ai_result.response_id}` : "";
        const status = resp.ai_result.status && resp.ai_result.status !== "completed" ? ` status=${resp.ai_result.status}` : "";
        const inc = resp.ai_result.incomplete_reason ? ` incomplete=${resp.ai_result.incomplete_reason}` : "";
        lines.push(`AI (${ms}ms${model}${rid}${status}${inc}):`);
      }
      for (const p of resp.ai_result.ai.posts) {
        const idx = typeof p.index === "number" ? p.index : "?";
        const postText = (p.post_text || "").replace(/\s+/g, " ").trim();

        // Back-compat with older saved results.
        const hasNew =
          "improved_short" in p ||
          "improved_medium" in p ||
          "critical_short" in p ||
          "critical_medium" in p ||
          "suggested_short" in p ||
          "suggested_medium" in p;

        const improvedShort = (p.improved_short || "").replace(/\s+/g, " ").trim();
        const improvedMedium = (p.improved_medium || "").replace(/\s+/g, " ").trim();
        const criticalShort = (p.critical_short || "").replace(/\s+/g, " ").trim();
        const criticalMedium = (p.critical_medium || "").replace(/\s+/g, " ").trim();
        const suggestedShort = (p.suggested_short || "").replace(/\s+/g, " ").trim();
        const suggestedMedium = (p.suggested_medium || "").replace(/\s+/g, " ").trim();

        const shortLegacy = (p.short_response || "").replace(/\s+/g, " ").trim();
        const mediumLegacy = (p.medium_response || "").replace(/\s+/g, " ").trim();
        lines.push("");
        lines.push(`#${idx} Post: ${clampString(postText, 180)}`);
        if (hasNew) {
          lines.push(`  improved (short):   ${improvedShort}`);
          lines.push(`  improved (medium):  ${improvedMedium}`);
          lines.push(`  Critical (short): ${criticalShort}`);
          lines.push(`  Critical (medium): ${criticalMedium}`);
          lines.push(`  Suggested (short): ${suggestedShort}`);
          lines.push(`  Suggested (medium): ${suggestedMedium}`);
        } else {
          lines.push(`  Short:  ${shortLegacy}`);
          lines.push(`  Medium: ${mediumLegacy}`);
        }
      }
      if (resp.ai_result.ai_path) lines.push(`\nAI file: ${resp.ai_result.ai_path}`);
      if (resp.ai_result.usage && typeof resp.ai_result.usage === "object") {
        const u = resp.ai_result.usage;
        const ins = typeof u.input_tokens === "number" ? u.input_tokens : null;
        const outs = typeof u.output_tokens === "number" ? u.output_tokens : null;
        const tots = typeof u.total_tokens === "number" ? u.total_tokens : null;
        const parts = [];
        if (ins != null) parts.push(`in=${ins}`);
        if (outs != null) parts.push(`out=${outs}`);
        if (tots != null) parts.push(`total=${tots}`);
        if (parts.length) lines.push(`AI usage: ${parts.join(" ")}`);
      }
    } else {
      lines.push("");
      const ms = resp.ai_result.took_ms || "?";
      const model = resp.ai_result.model ? ` model=${resp.ai_result.model}` : "";
      const rid = resp.ai_result.response_id ? ` id=${resp.ai_result.response_id}` : "";
      const status = resp.ai_result.status ? ` status=${resp.ai_result.status}` : "";
      const inc = resp.ai_result.incomplete_reason ? ` incomplete=${resp.ai_result.incomplete_reason}` : "";
      const err = resp.ai_result.error || (resp.ai_result.ai && resp.ai_result.ai.error) || "unknown";
      lines.push(`AI error (${ms}ms${model}${rid}${status}${inc}): ${err}`);
      if (resp.ai_result.detail) lines.push(`Detail: ${clampString(resp.ai_result.detail, 600)}`);
      if (resp.ai_result.raw_preview) lines.push(`Raw: ${clampString(resp.ai_result.raw_preview, 600)}`);
      if (resp.ai_result.usage && typeof resp.ai_result.usage === "object") {
        const u = resp.ai_result.usage;
        const ins = typeof u.input_tokens === "number" ? u.input_tokens : null;
        const outs = typeof u.output_tokens === "number" ? u.output_tokens : null;
        const tots = typeof u.total_tokens === "number" ? u.total_tokens : null;
        const parts = [];
        if (ins != null) parts.push(`in=${ins}`);
        if (outs != null) parts.push(`out=${outs}`);
        if (tots != null) parts.push(`total=${tots}`);
        if (parts.length) lines.push(`AI usage: ${parts.join(" ")}`);
      }
      if (resp.ai_result.ai_path) lines.push(`AI file: ${resp.ai_result.ai_path}`);
    }
  }

  if (resp.ran) {
    lines.push("Ran:");
    if (resp.ran.error) {
      lines.push(`  error: ${resp.ran.error}`);
    } else {
      lines.push(`  exit: ${resp.ran.exit_code}`);
      if (resp.ran.stdout) lines.push(`  stdout: ${resp.ran.stdout.trim()}`);
      if (resp.ran.stderr) lines.push(`  stderr: ${resp.ran.stderr.trim()}`);
    }
  }

  return lines.join("\n");
}

async function loadHistory() {
  const h = (await storageGet(HISTORY_KEY)) || [];
  return Array.isArray(h) ? h : [];
}

async function saveHistory(history) {
  await storageSet({ [HISTORY_KEY]: history });
}

function historyLabel(item) {
  const when = item.saved_at ? item.saved_at.replace("T", " ").replace("Z", "Z") : "";
  const t = item.title ? clampString(item.title, 36) : "";
  const u = item.url ? clampString(item.url, 46) : "";
  const base = t || u || item.id || "";
  return when ? `${when} | ${base}` : base;
}

async function refreshHistoryUI() {
  const sel = $("history");
  const history = await loadHistory();
  sel.textContent = "";

  if (!history.length) {
    const opt = document.createElement("option");
    opt.value = "";
    opt.textContent = "(empty)";
    sel.appendChild(opt);
    sel.disabled = true;
    return;
  }

  sel.disabled = false;
  for (const item of history) {
    const opt = document.createElement("option");
    opt.value = item.id || item.png_path || item.meta_path || "";
    opt.textContent = historyLabel(item);
    sel.appendChild(opt);
  }
}

async function getActiveTab() {
  const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
  return tabs[0] || null;
}

async function captureVisibleTab() {
  // Defaults to current window when windowId is null.
  return await chrome.tabs.captureVisibleTab(null, { format: "png" });
}

async function extractPageContent(tabId) {
  const [{ result }] = await chrome.scripting.executeScript({
    target: { tabId },
    func: () => {
      const SKIP_TAGS = new Set(["script", "style", "noscript", "template", "head", "meta", "link", "svg", "canvas"]);

      const MAX_DEPTH = 14;
      const MAX_NODES = 1800;
      const MAX_TEXT_NODE_LEN = 500;

      let nodeCount = 0;
      let truncated = false;

      function cleanText(s) {
        return String(s || "")
          .replace(/\s+/g, " ")
          .trim();
      }

      function rectIntersectsViewport(r) {
        const vw = window.innerWidth || document.documentElement.clientWidth || 0;
        const vh = window.innerHeight || document.documentElement.clientHeight || 0;
        return r.bottom > 0 && r.right > 0 && r.top < vh && r.left < vw;
      }

      function isVisibleElement(el) {
        try {
          const cs = window.getComputedStyle(el);
          if (!cs) return false;
          if (cs.display === "none" || cs.visibility === "hidden") return false;
          const op = Number(cs.opacity || "1");
          if (!Number.isNaN(op) && op <= 0.02) return false;
          const r = el.getBoundingClientRect();
          if ((r.width || 0) < 1 || (r.height || 0) < 1) return false;
          if (!rectIntersectsViewport(r)) return false;
          return true;
        } catch {
          return false;
        }
      }

      function hasMeaningfulAttrs(el) {
        const role = (el.getAttribute("role") || "").trim();
        if (role) return true;
        const aria = (el.getAttribute("aria-label") || "").trim();
        if (aria) return true;
        const dt = (el.getAttribute("data-testid") || "").trim();
        if (dt) return true;
        const tag = el.tagName.toLowerCase();
        if (tag === "a" && (el.getAttribute("href") || el.href)) return true;
        if (tag === "button" || tag === "input" || tag === "textarea" || tag === "select") return true;
        return false;
      }

      function directVisibleText(el) {
        // Only include immediate text nodes and form values. This avoids duplicating text up the tree.
        const parts = [];

        for (const n of Array.from(el.childNodes || [])) {
          if (n.nodeType === Node.TEXT_NODE) {
            const t = cleanText(n.nodeValue || "");
            if (t) parts.push(t.length > MAX_TEXT_NODE_LEN ? t.slice(0, MAX_TEXT_NODE_LEN - 1) + "…" : t);
          }
        }

        const tag = el.tagName.toLowerCase();
        if (tag === "input") {
          const type = (el.getAttribute("type") || "text").toLowerCase();
          if (!["hidden", "submit", "button"].includes(type)) {
            const v = cleanText(el.value || el.getAttribute("value") || el.getAttribute("placeholder") || "");
            if (v) parts.push(v);
          }
        } else if (tag === "textarea") {
          const v = cleanText(el.value || el.getAttribute("placeholder") || "");
          if (v) parts.push(v);
        }

        return cleanText(parts.join(" "));
      }

      function simplifyTag(tag) {
        // Keep some semantics; treat most wrapper tags as "div".
        if (["main", "article", "section", "ul", "ol", "li", "p"].includes(tag)) return tag;
        if (tag.match(/^h[1-6]$/)) return tag;
        if (["a", "button", "label"].includes(tag)) return tag;
        if (["header", "footer", "nav", "aside"].includes(tag)) return tag;
        return "div";
      }

      function shouldSkip(el, tag) {
        if (SKIP_TAGS.has(tag)) return true;
        // Skip common overlay noise.
        const id = (el.id || "").toLowerCase();
        if (id.includes("cookie") || id.includes("consent")) return true;
        return false;
      }

      function collapseWrappers(node) {
        // Collapse div/span wrappers: div > div > div ... with a single child and no text/attrs.
        // We only collapse when the wrapper has exactly one child.
        while (
          node &&
          node.tag === "div" &&
          !node.text &&
          !node.attrs &&
          Array.isArray(node.children) &&
          node.children.length === 1 &&
          node.children[0] &&
          node.children[0].tag
        ) {
          node = node.children[0];
        }
        return node;
      }

      function maybeHoistInlineText(node) {
        // Common pattern: <a><span>Text</span></a> or <button><div><span>...</span></div></button>
        if (!node || node.text) return node;
        if (!["a", "button", "label", "p", "li"].includes(node.tag)) return node;
        if (!Array.isArray(node.children) || node.children.length !== 1) return node;
        const ch = node.children[0];
        if (ch && ch.text && (!ch.children || ch.children.length === 0) && (!ch.attrs || Object.keys(ch.attrs).length === 0)) {
          node.text = ch.text;
          delete node.children;
        }
        return node;
      }

      function build(el, depth) {
        if (truncated) return null;
        if (!el || depth > MAX_DEPTH) return null;
        if (!(el instanceof Element)) return null;

        const rawTag = el.tagName.toLowerCase();
        if (shouldSkip(el, rawTag)) return null;
        if (!isVisibleElement(el)) return null;

        nodeCount += 1;
        if (nodeCount > MAX_NODES) {
          truncated = true;
          return null;
        }

        const tag = simplifyTag(rawTag);
        const text = directVisibleText(el);

        const children = [];
        for (const ch of Array.from(el.children || [])) {
          const n = build(ch, depth + 1);
          if (n) children.push(n);
          if (truncated) break;
        }

        if (!text && children.length === 0) return null;

        const node = { tag };
        if (text) node.text = text;

        // Only keep attrs when they help identify purpose/action.
        if (hasMeaningfulAttrs(el)) {
          const attrs = {};
          const role = (el.getAttribute("role") || "").trim();
          const aria = (el.getAttribute("aria-label") || "").trim();
          const dt = (el.getAttribute("data-testid") || "").trim();
          if (role) attrs.role = role;
          if (aria) attrs.aria_label = aria;
          if (dt) attrs.data_testid = dt;

          if (rawTag === "a") {
            const href = (el.href || el.getAttribute("href") || "").trim();
            if (href) attrs.href = href;
          }

          if (Object.keys(attrs).length) node.attrs = attrs;
        }

        if (children.length) node.children = children;
        return maybeHoistInlineText(collapseWrappers(node));
      }

      const extractedAt = new Date().toISOString();
      const url = String(location.href || "");
      const title = String(document.title || "");

      const root = document.body ? build(document.body, 0) : null;
      return {
        extracted_at: extractedAt,
        url,
        title,
        hostname: location.hostname,
        visible_tree: root,
        truncated,
        stats: { nodes: nodeCount },
      };
    },
  });
  return result;
}

async function postScreenshot(endpoint, payload) {
  const r = await fetch(endpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
  const text = await r.text();
  let data = null;
  try {
    data = JSON.parse(text);
  } catch {
    // ignore
  }
  if (!r.ok) {
    throw new Error(`HTTP ${r.status}: ${text}`);
  }
  return data;
}

async function ping(endpoint) {
  const base = endpoint.replace(/\/screenshot\s*$/, "");
  const r = await fetch(`${base}/health`, { method: "GET" });
  if (!r.ok) return `HTTP ${r.status}`;
  const j = await r.json();
  return j && j.ok ? "ok" : "unexpected_response";
}

async function main() {
  const endpointEl = $("endpoint");
  const extraEl = $("extra_instructions");
  const captureBtn = $("capture");
  const pingBtn = $("ping");
  const historySel = $("history");
  const showHistoryBtn = $("show_history");
  const clearHistoryBtn = $("clear_history");

  endpointEl.value = (await storageGet("endpoint")) || DEFAULT_ENDPOINT;
  extraEl.value = (await storageGet(EXTRA_INSTRUCTIONS_KEY)) || "";
  await refreshHistoryUI();

  endpointEl.addEventListener("change", async () => {
    await storageSet({ endpoint: endpointEl.value.trim() });
  });
  extraEl.addEventListener("change", async () => {
    await storageSet({ [EXTRA_INSTRUCTIONS_KEY]: extraEl.value });
  });

  pingBtn.addEventListener("click", async () => {
    const endpoint = endpointEl.value.trim() || DEFAULT_ENDPOINT;
    setStatus("Pinging...", "");
    const msg = await ping(endpoint);
    setStatus(`Ping result: ${msg}`, msg === "ok" ? "ok" : "err");
  });

  showHistoryBtn.addEventListener("click", async () => {
    const history = await loadHistory();
    if (!history.length) {
      setStatus("History is empty.", "err");
      return;
    }
    const id = historySel.value;
    const item = history.find((x) => (x.id || x.png_path || x.meta_path) === id) || history[0];
    if (!item) {
      setStatus("No history item selected.", "err");
      return;
    }
    setStatus(renderResult(item), "ok");
  });

  clearHistoryBtn.addEventListener("click", async () => {
    await saveHistory([]);
    await refreshHistoryUI();
    setStatus("History cleared.", "ok");
  });

  captureBtn.addEventListener("click", async () => {
    const endpoint = endpointEl.value.trim() || DEFAULT_ENDPOINT;
    const extraInstructions = (extraEl.value || "").trim();
    captureBtn.disabled = true;
    setStatus("Extracting page content...", "");

    try {
      const tab = await getActiveTab();
      if (!tab) throw new Error("No active tab found");

      let content = null;
      try {
        content = await extractPageContent(tab.id);
      } catch (e) {
        // Some URLs (chrome://*, Web Store, PDF viewer) cannot be scripted.
        content = { error: String(e && e.message ? e.message : e) };
      }

      setStatus("Capturing visible tab...", "");
      const dataUrl = await captureVisibleTab();
      setStatus("Uploading to local server...", "");

      const resp = await postScreenshot(endpoint, {
        data_url: dataUrl,
        title: tab.title || "",
        url: tab.url || "",
        ts: new Date().toISOString(),
        content,
        extra_instructions: extraInstructions,
      });

      // Persist for later viewing in the popup.
      const history = await loadHistory();
      const entry = {
        id: resp.png_path || resp.meta_path || String(Date.now()),
        saved_at: new Date().toISOString(),
        title: tab.title || "",
        url: tab.url || "",
        extra_instructions: extraInstructions,
        resp: {
          png_path: resp.png_path || "",
          meta_path: resp.meta_path || "",
          content_path: resp.content_path || "",
          ai_result: resp.ai_result || null,
        },
      };
      const deduped = [entry, ...history.filter((x) => (x.id || x.png_path || x.meta_path) !== entry.id)].slice(0, HISTORY_LIMIT);
      await saveHistory(deduped);
      await refreshHistoryUI();

      setStatus(renderResult(entry), "ok");
    } catch (e) {
      setStatus(String(e && e.message ? e.message : e), "err");
    } finally {
      captureBtn.disabled = false;
    }
  });
}

main();
