/**
 * Generates a self-contained HTML diagnostic reference report.
 * Produces dev-docs/DIAGNOSTICS_COMPREHENSIVE_REFERENCE.html with inline
 * CSS and JS — no external dependencies.
 *
 * Invoked via: npm run docs:diagnostics
 */

import * as fs from "fs";
import * as path from "path";
import {
    buildCoverageMap,
    buildDiagnosticRows,
    formatFixtureCoverage
} from "./generate-comprehensive-diagnostic-reference.js";
import { COLUMNS } from "./shared/diagnostic-report-columns.js";

import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const OUTPUT_PATH = path.join(__dirname, "..", "dev-docs", "DIAGNOSTICS_COMPREHENSIVE_REFERENCE.html");

function escapeHtml(str) {
    return String(str)
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;");
}

function buildHtmlCoverageLinks(messageId, coverageMap) {
    const byFixture = coverageMap.get(messageId);
    if (!byFixture || byFixture.size === 0) return "-";
    return Array.from(byFixture.entries())
        .sort(([a], [b]) => a.localeCompare(b))
        .map(([fixturePath, lines]) => {
            const label = fixturePath.replace(/\.TXT$/i, "");
            const sortedLines = Array.from(lines).sort((a, b) => a - b);
            const lineText = sortedLines.length === 0 ? ""
                : sortedLines.length === 1 ? ` L${sortedLines[0]}`
                : ` L${sortedLines.join(",")}`;
            const firstLine = sortedLines.length > 0 ? sortedLines[0] : "";
            const dataLine = firstLine ? ` data-line="${firstLine}"` : "";
            const vsBtn = `<a class="vscode-btn" href="#" data-rel="${escapeHtml("test/syntax-tests/" + fixturePath)}"${dataLine} title="Open in VS Code">VS</a>`;
            return `${vsBtn}<a href="../test/syntax-tests/${escapeHtml(fixturePath)}">${escapeHtml(label)}</a>${escapeHtml(lineText)}`;
        }).join("<br>");
}

function severityClass(sev) {
    switch (sev) {
        case "error": return "sev-error";
        case "warning": return "sev-warning";
        case "off": return "sev-off";
        case "information": return "sev-info";
        default: return "";
    }
}

function surfaceClass(surface) {
    if (surface === "IDE") return "surf-ide";
    if (surface === "CLI") return "surf-cli";
    return "surf-both";
}

/**
 * Generates the filter bar HTML from the column manifest.
 * Only columns with a truthy `filterable` value get a control.
 */
function buildFilterBar() {
    return COLUMNS.filter(c => c.filterable).map(c => {
        if (c.filterable === "qf") {
            return `    <div class="filter-group">
      <label>${escapeHtml(c.label)}</label>
      <select id="f-${c.key}">
        <option value="">All</option>
        <option value="yes">Has fix</option>
        <option value="no">No fix</option>
      </select>
    </div>`;
        }
        if (c.filterable === "checkbox") {
            const boxes = (c.checkboxOptions || []).map((opt, i) =>
                `<label class="checkbox-opt"><input type="checkbox" id="f-${c.key}-${i}" value="${escapeHtml(opt.value)}" checked> ${escapeHtml(opt.label)}</label>`
            ).join("");
            return `    <div class="filter-group">
      <label>${escapeHtml(c.label)}</label>
      <div class="checkbox-group" id="f-${c.key}">${boxes}</div>
    </div>`;
        }
        return `    <div class="filter-group">
      <label>${escapeHtml(c.label)}</label>
      <select id="f-${c.key}"><option value="">All</option></select>
    </div>`;
    }).join("\n");
}

/**
 * Generates the <thead> <th> elements from the column manifest.
 * Sortable columns get a data-col attribute; unsortable ones do not.
 */
function buildThead() {
    return COLUMNS.map(c =>
        c.sortable
            ? `          <th data-col="${c.key}">${escapeHtml(c.label)}</th>`
            : `          <th>${escapeHtml(c.label)}</th>`
    ).join("\n");
}

function buildHtmlCatalogLink(catalogLine) {
    if (!catalogLine) return "-";
    const rel = "core/src/diagnostics/diagnosticCatalog.js";
    return `<a class="vscode-btn" href="#" data-rel="${escapeHtml(rel)}" data-line="${catalogLine}" title="Open definition in VS Code">VS</a>`;
}

function buildHtml(rows, coverageMap, generatedAt) {
    const filterBarHtml = buildFilterBar();
    const theadHtml = buildThead();

    // Pass a minimal projection to the browser — only what the filter/search JS needs.
    const columnsJson = JSON.stringify(
        COLUMNS.map(({ key, filterable, searchable, multiValue, checkboxOptions }) => ({
            key,
            filterable: filterable || false,
            searchable: searchable || false,
            multiValue: !!multiValue,
            ...(checkboxOptions ? { checkboxOptions } : {})
        }))
    );

    // <tr> data-* attributes and <td> cells are hand-authored here because each
    // column requires custom rendering (badges, severity classes, hyperlinks, etc.).
    // Column order must match the order declared in diagnostic-report-columns.js.
    const tableRows = rows.map(r => {
        const coverageLinks = buildHtmlCoverageLinks(r.messageId, coverageMap);
        return `
        <tr
            data-id="${escapeHtml(r.messageId)}"
            data-cat="${escapeHtml(r.category)}"
            data-surf="${escapeHtml(r.surface)}"
            data-msg="${escapeHtml(r.message)}"
            data-rule="${escapeHtml(r.ruleId)}"
            data-val="${escapeHtml(r.validators)}"
            data-def="${escapeHtml(r.defSev)}"
            data-leg="${escapeHtml(r.legSev)}"
            data-str="${escapeHtml(r.strSev)}"
            data-qf="${r.quickFixCount}"
            data-suite="${escapeHtml(r.syntaxTestSuite)}"
            data-grp="${escapeHtml(r.group)}"
            data-set="${escapeHtml(r.settingKey)}"
        >
            <td class="col-id"><strong>${escapeHtml(r.messageId)}</strong></td>
            <td class="col-src">${buildHtmlCatalogLink(r.catalogLine)}</td>
            <td class="col-cat">${escapeHtml(r.category)}</td>
            <td class="col-surf"><span class="badge ${surfaceClass(r.surface)}">${escapeHtml(r.surface)}</span></td>
            <td class="col-msg"><code>${escapeHtml(r.message)}</code></td>
            <td class="col-rule">${escapeHtml(r.ruleId)}</td>
            <td class="col-val">${escapeHtml(r.validators)}</td>
            <td class="col-sev ${severityClass(r.strSev)}">${escapeHtml(r.strSev)}</td>
            <td class="col-sev ${severityClass(r.defSev)}">${escapeHtml(r.defSev)}</td>
            <td class="col-sev ${severityClass(r.legSev)}">${escapeHtml(r.legSev)}</td>
            <td class="col-qf">${r.quickFixCount > 0 ? `<span class="badge badge-fix">${r.quickFixCount}</span>` : "-"}</td>
            <td class="col-suite">${escapeHtml(r.syntaxTestSuite)}</td>
            <td class="col-cov">${coverageLinks}</td>
            <td class="col-grp">${escapeHtml(r.group)}</td>
            <td class="col-set"><code>${escapeHtml(r.settingKey)}</code></td>
        </tr>`;
    }).join("\n");

    return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Acurity Architect — Diagnostic Reference</title>
<style>
  *, *::before, *::after { box-sizing: border-box; }
  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 13px; margin: 0; background: #f7f8fa; color: #1a1a2e; }
  h1 { margin: 0 0 4px; font-size: 20px; }
  .meta { color: #666; font-size: 11px; margin-bottom: 16px; }
  .meta a { color: #0066cc; }

  /* Layout */
  .page { max-width: 100%; padding: 20px 24px; }

  /* Filter bar */
  .filter-bar { background: #fff; border: 1px solid #dde; border-radius: 8px; padding: 14px 16px; margin-bottom: 14px; display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
  .filter-group { display: flex; flex-direction: column; gap: 3px; }
  .filter-group label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: #888; }
  .filter-group input, .filter-group select { border: 1px solid #ccc; border-radius: 4px; padding: 4px 8px; font-size: 12px; background: #fafafa; min-width: 90px; }
  .filter-group input { min-width: 200px; }
  .filter-group input:focus, .filter-group select:focus { outline: 2px solid #0066cc44; border-color: #0066cc; }
  .btn-reset { padding: 5px 12px; background: #eee; border: 1px solid #ccc; border-radius: 4px; cursor: pointer; font-size: 12px; align-self: flex-end; }
  .btn-reset:hover { background: #e0e0e0; }
  .result-count { align-self: flex-end; font-size: 12px; color: #555; margin-left: auto; white-space: nowrap; }
  .checkbox-group { display: flex; flex-direction: column; gap: 2px; }
  .checkbox-opt { display: flex; align-items: center; gap: 5px; font-size: 12px; cursor: pointer; white-space: nowrap; }
  .checkbox-opt input[type="checkbox"] { cursor: pointer; margin: 0; min-width: 10px; }

  /* Table */
  .table-wrap { overflow-x: auto; overflow-y: auto; max-height: calc(100vh - 180px); border-radius: 8px; box-shadow: 0 1px 4px #0001; }
  table { border-collapse: collapse; width: 100%; background: #fff; }
  th { background: #1a1a2e; color: #fff; padding: 8px 10px; text-align: left; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; white-space: nowrap; cursor: pointer; user-select: none; position: sticky; top: 0; z-index: 2; }
  th:hover { background: #2a2a4e; }
  th.sort-asc::after  { content: " ▲"; font-size: 9px; }
  th.sort-desc::after { content: " ▼"; font-size: 9px; }
  td { padding: 7px 10px; border-bottom: 1px solid #eee; vertical-align: top; }
  tr:last-child td { border-bottom: none; }
  tr:hover td { background: #f0f4ff; }
  tr.hidden { display: none; }

  /* Column widths */
  .col-id   { white-space: nowrap; font-size: 12px; }
  .col-cat  { white-space: nowrap; }
  .col-surf { white-space: nowrap; }
  .col-msg  { min-width: 260px; max-width: 400px; word-break: break-word; }
  .col-msg code { font-size: 11px; background: #f4f4f8; padding: 1px 3px; border-radius: 3px; }
  .col-rule { white-space: nowrap; font-size: 12px; }
  .col-val  { white-space: nowrap; font-size: 12px; }
  .col-grp  { white-space: nowrap; }
  .col-sev  { white-space: nowrap; font-weight: 600; font-size: 11px; text-align: center; }
  .col-set  { font-size: 11px; max-width: 260px; word-break: break-all; }
  .col-set code { font-size: 10px; background: #f4f4f8; padding: 1px 3px; border-radius: 3px; }
  .col-qf   { text-align: center; white-space: nowrap; }
  .col-cov  { font-size: 11px; min-width: 140px; }
  .col-cov a { color: #0066cc; }
  .col-src  { text-align: center; white-space: nowrap; }

  /* VS Code open-in-editor button */
  .vscode-btn { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 14px; background: #007ACC; color: #fff !important; font-size: 8px; font-weight: 700; border-radius: 2px; text-decoration: none !important; margin-right: 4px; vertical-align: middle; letter-spacing: -0.5px; cursor: pointer; }
  .vscode-btn:hover { background: #005a9e; }

  /* Severity colours */
  .sev-error   { color: #c0392b; }
  .sev-warning { color: #d68910; }
  .sev-off     { color: #aaa; }
  .sev-info    { color: #2471a3; }

  /* Badges */
  .badge { display: inline-block; padding: 2px 7px; border-radius: 10px; font-size: 10px; font-weight: 600; }
  .surf-ide  { background: #e8f4fd; color: #1a6fa8; }
  .surf-cli  { background: #fef9e7; color: #9a6300; }
  .surf-both { background: #eafaf1; color: #1a7a40; }
  .badge-fix { background: #e8f4fd; color: #1a6fa8; }

  /* No-results */
  .no-results { display: none; text-align: center; padding: 32px; color: #888; font-size: 14px; }

  /* Concepts section */
  .concepts { background: #fff; border: 1px solid #dde; border-radius: 8px; padding: 0; margin-bottom: 14px; overflow: hidden; }
  .concepts summary { list-style: none; display: flex; align-items: center; gap: 8px; padding: 12px 20px; cursor: pointer; font-size: 15px; font-weight: 600; user-select: none; }
  .concepts summary::-webkit-details-marker { display: none; }
  .concepts summary::before { content: '▶'; font-size: 10px; color: #888; transition: transform 0.15s; display: inline-block; }
  .concepts[open] summary::before { transform: rotate(90deg); }
  .concepts summary:hover { background: #f7f8fa; }
  .concepts-body { padding: 0 20px 16px; }
  .concepts ul { margin: 0 0 12px; padding-left: 20px; line-height: 1.7; }
  .concepts h3 { margin: 12px 0 6px; font-size: 13px; }
  .concepts a { color: #0066cc; }
</style>
</head>
<body>
<div class="page">
  <h1>Acurity Architect — Diagnostic Reference</h1>
  <p class="meta">Auto-generated · Run <code>npm run docs:diagnostics</code> to refresh</p>

  <details class="concepts" open>
    <summary>Concepts</summary>
    <div class="concepts-body">
    <ul>
      <li><strong>Message</strong> — a specific diagnostic instance identified by a stable <code>ACU-XXX-NNN</code> ID.
        Appears in VS Code hover tooltips, the Problems panel, and suppression comments
        (<code>// @rule-ignore ACU-INT-001</code>). A Rule may produce several distinct Message types.</li>
      <li><strong>Rule</strong> — a named validation behaviour (camelCase key, e.g. <code>interpolationUndefined</code>).
        Each Rule maps to one VS Code setting (<code>acurity.rules.&lt;group&gt;.&lt;rule&gt;.severity</code>).
        Changing it adjusts the severity of <em>all</em> Messages belonging to that Rule.</li>
      <li><strong>Surface</strong> — where a diagnostic is raised.
        <span class="badge surf-ide">IDE</span> = VS Code only;
        <span class="badge surf-cli">CLI</span> = headless validator only;
        <span class="badge surf-both">IDE + CLI</span> = both.
        Declared via the <code>surfaces</code> field in
        <a href="../core/src/diagnostics/diagnosticCatalog.js">core/src/diagnostics/diagnosticCatalog.js</a>.</li>
    </ul>
    <h3>Where rule severities are set</h3>
    <ul>
      <li><strong>Default profiles</strong> (default / legacy / strict) — <code>severity</code> block on each entry in
        <a href="../core/src/diagnostics/diagnosticCatalog.js">core/src/diagnostics/diagnosticCatalog.js</a>.</li>
      <li><strong>VS Code</strong> — profile selection and per-rule overrides resolved in
        <a href="../vscode-extension/src/config/standardsConfig.js">vscode-extension/src/config/standardsConfig.js</a>.
        Active profile: <code>acurity.rules.profile</code>; per-rule override: <code>acurity.rules.&lt;group&gt;.&lt;rule&gt;.severity</code>.</li>
      <li><strong>CLI</strong> — always uses the <code>default</code> profile; severity mapping in
        <a href="../cli/index.js">cli/index.js</a>.</li>
    </ul>
    </div>
  </details>

  <div class="filter-bar" id="filterBar">
    <div class="filter-group">
      <label>Search</label>
      <input type="text" id="f-search" placeholder="ID, message, rule…">
    </div>
${filterBarHtml}
    <button class="btn-reset" id="btnReset">Reset</button>
    <span class="result-count" id="resultCount"></span>
  </div>

  <div class="table-wrap">
    <table id="diagTable">
      <thead>
        <tr>
${theadHtml}
        </tr>
      </thead>
      <tbody id="diagBody">
${tableRows}
      </tbody>
    </table>
    <div class="no-results" id="noResults">No diagnostics match the current filters.</div>
  </div>
</div>

<script>
(function () {
  // Column manifest injected at build time — drives all filter/search/sort wiring.
  const COLUMNS = ${columnsJson};
  const filterCols = COLUMNS.filter(c => c.filterable);
  const searchKeys = COLUMNS.filter(c => c.searchable).map(c => c.key);

  const rows = Array.from(document.querySelectorAll("#diagBody tr"));
  const noResults = document.getElementById("noResults");
  const resultCount = document.getElementById("resultCount");

  // Populate "select" dropdowns from the unique values present in table data.
  // Multi-value columns (comma-separated) are split before de-duplication.
  filterCols.filter(c => c.filterable === "select").forEach(c => {
    const sel = document.getElementById("f-" + c.key);
    const values = [...new Set(rows.flatMap(r => {
      const raw = r.dataset[c.key] || "";
      return c.multiValue ? raw.split(", ").filter(Boolean) : (raw ? [raw] : []);
    }))].sort();
    values.forEach(v => {
      const opt = document.createElement("option");
      opt.value = opt.textContent = v;
      sel.appendChild(opt);
    });
  });

  function applyFilters() {
    const search = document.getElementById("f-search").value.trim().toLowerCase();

    let visible = 0;
    rows.forEach(r => {
      const d = r.dataset;
      const matchSearch = !search || searchKeys.some(k => (d[k] || "").toLowerCase().includes(search));
      const matchFilters = filterCols.every(c => {
        if (c.filterable === "checkbox") {
          const opts = c.checkboxOptions || [];
          const checkedVals = opts
            .filter((opt, i) => document.getElementById("f-" + c.key + "-" + i)?.checked)
            .map(opt => opt.value);
          if (checkedVals.length === opts.length) return true;
          if (checkedVals.length === 0) return false;
          const parts = (d[c.key] || "-").split(" + ");
          return parts.some(p => checkedVals.includes(p));
        }
        const fv = document.getElementById("f-" + c.key)?.value || "";
        if (!fv) return true;
        if (c.filterable === "select") {
          // Multi-value columns: check if any item in the comma-separated list matches.
          return c.multiValue
            ? d[c.key].split(", ").includes(fv)
            : d[c.key] === fv;
        }
        if (c.filterable === "qf") return fv === "yes" ? Number(d.qf) > 0 : Number(d.qf) === 0;
        return true;
      });
      const show = matchSearch && matchFilters;
      r.classList.toggle("hidden", !show);
      if (show) visible++;
    });

    resultCount.textContent = visible + " of " + rows.length + " diagnostics";
    noResults.style.display = visible === 0 ? "block" : "none";
  }

  // Sort state
  let sortCol = null, sortDir = 1;

  function sortBy(col) {
    document.querySelectorAll("thead th[data-col]").forEach(th => th.classList.remove("sort-asc", "sort-desc"));
    if (sortCol === col) {
      sortDir *= -1;
    } else {
      sortCol = col;
      sortDir = 1;
    }
    const th = document.querySelector(\`thead th[data-col="\${col}"]\`);
    if (th) th.classList.add(sortDir === 1 ? "sort-asc" : "sort-desc");

    const tbody = document.getElementById("diagBody");
    const sorted = [...rows].sort((a, b) => {
      if (col === "qf") return (Number(a.dataset.qf) - Number(b.dataset.qf)) * sortDir;
      const av = (a.dataset[col] || "").toLowerCase();
      const bv = (b.dataset[col] || "").toLowerCase();
      return av < bv ? -sortDir : av > bv ? sortDir : 0;
    });
    sorted.forEach(r => tbody.appendChild(r));
    applyFilters();
  }

  document.querySelectorAll("thead th[data-col]").forEach(th => {
    th.addEventListener("click", () => sortBy(th.dataset.col));
  });

  document.getElementById("f-search").addEventListener("input", applyFilters);
  filterCols.forEach(c => {
    if (c.filterable === "checkbox") {
      (c.checkboxOptions || []).forEach((opt, i) => {
        document.getElementById("f-" + c.key + "-" + i)?.addEventListener("change", applyFilters);
      });
    } else {
      document.getElementById("f-" + c.key)?.addEventListener("change", applyFilters);
    }
  });

  document.getElementById("btnReset").addEventListener("click", () => {
    document.getElementById("f-search").value = "";
    filterCols.forEach(c => {
      if (c.filterable === "checkbox") {
        (c.checkboxOptions || []).forEach((opt, i) => {
          const el = document.getElementById("f-" + c.key + "-" + i);
          if (el) el.checked = true;
        });
      } else {
        document.getElementById("f-" + c.key).value = "";
      }
    });
    applyFilters();
  });

  // VS Code "Open in editor" buttons.
  // Constructs an absolute vscode://file/ URI at runtime so the HTML is portable
  // regardless of where the repository is cloned.
  document.querySelectorAll('.vscode-btn[data-rel]').forEach(btn => {
    btn.addEventListener('click', function (e) {
      e.preventDefault();
      const base = new URL('..', window.location.href).href; // up from dev-docs/ to repo root
      const fileUrl = base + this.dataset.rel;
      const line = this.dataset.line || '';
      // Convert "file:///path" → "vscode://file/path", decoding any percent-encoded chars.
      const vsUri = decodeURIComponent(fileUrl).replace('file://', 'vscode://file') + (line ? ':' + line : '');
      window.location.href = vsUri;
    });
  });

  applyFilters();
})();
</script>
</body>
</html>`;
}

function generateDiagnosticHtmlReport(outputPath = OUTPUT_PATH) {
    const coverageMap = buildCoverageMap();
    const rows = buildDiagnosticRows(coverageMap);
    const generatedAt = new Date().toISOString();
    const html = buildHtml(rows, coverageMap, generatedAt);
    fs.writeFileSync(outputPath, html, "utf8");
    return outputPath;
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
    const outPath = generateDiagnosticHtmlReport();
    console.log(`Generated HTML diagnostic reference in ${outPath}`);
}

export { generateDiagnosticHtmlReport };

