---
import AdminLayout from "../../layouts/AdminLayout.astro";
---

<AdminLayout title="QA">
  <div style="display:flex;align-items:baseline;gap:1.25rem">
    <h1>QA</h1>
    <button type="button" class="btn" id="runBtn" style="margin-left:auto">Run QA now</button>
  </div>
  <p class="lead">Automated checks against the live site — links, images, the contact form, the API, and SEO. Turn any finding into a Web Designer fix with one click.</p>

  <p class="msg" id="msg" role="status" aria-live="polite"></p>

  <section id="runWrap" class="run" hidden>
    <div class="run-head">
      <span class="run-when" id="runWhen"></span>
      <span class="pills" id="runPills"></span>
    </div>
    <p class="run-summary" id="runSummary"></p>
  </section>

  <div id="findings" class="findings">Loading…</div>

  <details class="history">
    <summary>Previous runs</summary>
    <div id="history"></div>
  </details>

  <style>
    .run { border:1px solid var(--rule); border-radius:2px; padding:1.25rem 1.4rem; margin:1.5rem 0; }
    .run-head { display:flex; align-items:center; gap:1rem; margin-bottom:.6rem; }
    .run-when { font-family:var(--mono); font-size:.65rem; letter-spacing:.08em; text-transform:uppercase; color:var(--ink4); }
    .pills { margin-left:auto; display:flex; gap:.5rem; }
    .pill { font-family:var(--mono); font-size:.6rem; letter-spacing:.08em; text-transform:uppercase; padding:.2rem .55rem; border-radius:2px; border:1px solid var(--rule); color:var(--ink3); }
    .pill.error { color:var(--seal); border-color:var(--seal); }
    .pill.ok { color:var(--ink); }
    .run-summary { margin:0; font-size:.95rem; line-height:1.6; color:var(--ink2); }

    .findings { display:flex; flex-direction:column; }
    .grp { margin-top:1.75rem; }
    .grp h2 { font-family:var(--mono); font-size:.62rem; letter-spacing:.14em; text-transform:uppercase; color:var(--ink4); margin:0 0 .5rem; }
    .fnd { display:flex; align-items:flex-start; gap:.9rem; padding:.85rem 0; border-top:1px solid var(--rule); }
    .fnd:last-child { border-bottom:1px solid var(--rule); }
    .sev { flex:0 0 auto; margin-top:.15rem; width:.5rem; height:.5rem; border-radius:50%; background:var(--ink4); }
    .sev.error { background:var(--seal); } .sev.warning { background:var(--ink3); }
    .fnd .body { flex:1; min-width:0; }
    .fnd .top { display:flex; gap:.6rem; align-items:baseline; flex-wrap:wrap; }
    .fnd .ck { font-family:var(--mono); font-size:.6rem; letter-spacing:.08em; text-transform:uppercase; color:var(--ink4); }
    .fnd .url { font-family:var(--mono); font-size:.78rem; color:var(--ink2); word-break:break-all; }
    .fnd .detail { margin:.25rem 0 0; font-size:.88rem; color:var(--ink3); }
    .fnd .acts { flex:0 0 auto; display:flex; gap:.5rem; align-items:center; }
    .fnd .acts button { font-family:var(--mono); font-size:.58rem; letter-spacing:.06em; text-transform:uppercase;
      background:none; border:1px solid var(--rule); border-radius:2px; padding:.35rem .6rem; color:var(--ink3); cursor:pointer; }
    .fnd .acts button.fix:hover { color:var(--paper); background:var(--ink); border-color:var(--ink); }
    .fnd .acts button.ignore:hover { color:var(--seal); }
    .fnd .state { font-family:var(--mono); font-size:.58rem; letter-spacing:.06em; text-transform:uppercase; color:var(--ink4); }
    .clean { padding:2rem 0; color:var(--ink3); }
    .history { margin-top:2.5rem; }
    .history summary { font-family:var(--mono); font-size:.62rem; letter-spacing:.12em; text-transform:uppercase; color:var(--ink4); cursor:pointer; }
    .history .hrow { display:flex; gap:1rem; padding:.6rem 0; border-top:1px solid var(--rule); font-size:.85rem; }
    .history .hrow .hw { font-family:var(--mono); font-size:.62rem; color:var(--ink4); }
  </style>

  <script>
    const $ = (id) => document.getElementById(id);
    const msg = $("msg");
    const setMsg = (t, cls = "") => { msg.textContent = t; msg.className = "msg " + cls; };
    let polling = null;

    const SEV_ORDER = ["error", "warning", "info"];
    const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));

    $("runBtn").addEventListener("click", async () => {
      const btn = $("runBtn"); btn.disabled = true;
      setMsg("Running QA against the live site… this takes a moment.");
      try {
        const r = await fetch("/devconsole/qa/run", { method: "POST", credentials: "same-origin" });
        if (r.status === 429) { setMsg("A QA run is already in progress.", "error"); btn.disabled = false; return; }
        // poll for a fresh completed run
        pollRuns(true);
      } catch { setMsg("Couldn't start the QA run.", "error"); btn.disabled = false; }
    });

    async function pollRuns(waitForNew = false) {
      if (polling) clearInterval(polling);
      let seen = waitForNew ? await latestRunId() : null;
      const tick = async () => {
        const runs = await getRuns();
        renderHistory(runs);
        const latest = runs[0];
        if (!latest) return;
        if (waitForNew && latest.status !== "done") { setMsg("Running QA…"); return; }
        if (waitForNew && latest.id === seen && latest.status === "done") { /* still old */ return; }
        // a done run is available
        renderRun(latest);
        await renderFindings(latest.id);
        setMsg("");
        $("runBtn").disabled = false;
        if (latest.status === "done") { clearInterval(polling); polling = null; }
      };
      polling = setInterval(tick, 4000);
      tick();
    }
    async function latestRunId() { const r = await getRuns(); return r[0]?.id ?? null; }
    async function getRuns() {
      try { const r = await fetch("/api/adminqa/runs", { credentials: "same-origin" }); return (await r.json())?.data?.runs || []; }
      catch { return []; }
    }

    function renderRun(run) {
      $("runWrap").hidden = false;
      $("runWhen").textContent = (run.trigger === "scheduled" ? "Scheduled · " : "") + (run.finished || run.started || "");
      const c = run.counts || {};
      const pills = [];
      pills.push(`<span class="pill ${c.error ? "error" : "ok"}">${c.error || 0} error${c.error === 1 ? "" : "s"}</span>`);
      pills.push(`<span class="pill">${c.warning || 0} warning${c.warning === 1 ? "" : "s"}</span>`);
      pills.push(`<span class="pill">${c.pages || 0} pages</span>`);
      $("runPills").innerHTML = pills.join("");
      $("runSummary").textContent = run.summary || "";
    }

    async function renderFindings(runId) {
      const el = $("findings");
      let data;
      try { data = (await (await fetch("/api/adminqa/findings?run=" + runId, { credentials: "same-origin" })).json())?.data; }
      catch { el.textContent = "Couldn't load findings."; return; }
      const findings = (data?.findings || []).filter((f) => f.status !== "ignored");
      if (!findings.length) { el.innerHTML = `<p class="clean">✓ No open issues in this run.</p>`; return; }
      el.innerHTML = "";
      for (const sev of SEV_ORDER) {
        const group = findings.filter((f) => f.severity === sev);
        if (!group.length) continue;
        const wrap = document.createElement("div");
        wrap.className = "grp";
        wrap.innerHTML = `<h2>${sev} · ${group.length}</h2>`;
        for (const f of group) {
          const row = document.createElement("div");
          row.className = "fnd";
          const acts = f.status === "fix_queued"
            ? `<span class="state">fix queued</span>`
            : `<button class="fix" data-fix="${f.id}">Create fix task</button><button class="ignore" data-ignore="${f.id}">Ignore</button>`;
          row.innerHTML = `
            <span class="sev ${f.severity}"></span>
            <div class="body">
              <div class="top"><span class="ck">${esc(f.check)}</span><span class="url">${esc(f.url)}</span></div>
              <p class="detail">${esc(f.detail)}</p>
            </div>
            <div class="acts">${acts}</div>`;
          const fixBtn = row.querySelector("[data-fix]");
          if (fixBtn) fixBtn.addEventListener("click", () => createFix(f.id, runId));
          const igBtn = row.querySelector("[data-ignore]");
          if (igBtn) igBtn.addEventListener("click", () => ignore(f.id, runId));
          wrap.appendChild(row);
        }
        el.appendChild(wrap);
      }
    }

    async function createFix(id, runId) {
      try {
        const r = await fetch("/api/adminqa/fix", {
          method: "POST", credentials: "same-origin",
          headers: { "content-type": "application/json" }, body: JSON.stringify({ finding_id: id }),
        });
        const d = await r.json();
        if (!r.ok || !d?.data?.ok) { setMsg(d?.error?.message || "Couldn't queue the fix.", "error"); return; }
        await fetch("/devconsole/tasks/run", { method: "POST", credentials: "same-origin" });
        setMsg("Fix queued — the Web Designer is on it.", "ok");
        renderFindings(runId);
      } catch { setMsg("Couldn't queue the fix.", "error"); }
    }
    async function ignore(id, runId) {
      try {
        await fetch("/api/adminqa/ignore", { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json" }, body: JSON.stringify({ finding_id: id }) });
        renderFindings(runId);
      } catch {}
    }

    function renderHistory(runs) {
      $("history").innerHTML = runs.slice(0, 15).map((r) => {
        const c = r.counts || {};
        return `<div class="hrow"><span class="hw">${esc(r.finished || r.started || "")}</span><span>${c.error || 0} err · ${c.warning || 0} warn · ${r.status}</span></div>`;
      }).join("");
    }

    // initial load: show the latest completed run
    (async () => {
      const runs = await getRuns();
      renderHistory(runs);
      const latest = runs[0];
      if (latest && latest.status === "done") { renderRun(latest); await renderFindings(latest.id); }
      else if (latest && latest.status === "running") { setMsg("A QA run is in progress…"); pollRuns(false); }
      else { $("findings").innerHTML = `<p class="clean">No QA runs yet — click “Run QA now”.</p>`; }
    })();
  </script>
</AdminLayout>
