<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>openCypher Compliance Report</title>
<style>
:root {
  --pass: #22c55e;
  --fail: #ef4444;
  --bg: #f8fafc;
  --card: #ffffff;
  --border: #e2e8f0;
  --text: #1e293b;
  --muted: #64748b;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); line-height: 1.5; padding: 1.5rem; }
h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
.subtitle { color: var(--muted); font-size: 0.85rem; margin-bottom: 1.5rem; }

/* Dashboard cards */
.cards { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem 1.25rem; min-width: 220px; flex: 1; }
.card h3 { font-size: 1rem; margin-bottom: 0.5rem; }
.card .stats { display: flex; gap: 1rem; font-size: 0.85rem; margin-bottom: 0.5rem; }
.card .stats .pass { color: var(--pass); }
.card .stats .fail { color: var(--fail); }
.bar-bg { height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden; }
.bar-fill { height: 100%; background: var(--pass); border-radius: 4px; transition: width 0.3s; }
.card .rate { font-size: 1.25rem; font-weight: 600; }

/* Sections */
.section { margin-bottom: 1.5rem; }
.section h2 { font-size: 1.15rem; margin-bottom: 0.75rem; }

/* Filters */
.filters { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 1rem; }
.filters input, .filters select { padding: 0.4rem 0.6rem; border: 1px solid var(--border); border-radius: 4px; font-size: 0.85rem; }
.filters input { min-width: 200px; }

/* Tables */
table { width: 100%; border-collapse: collapse; font-size: 0.85rem; background: var(--card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
th, td { padding: 0.5rem 0.75rem; text-align: left; border-bottom: 1px solid var(--border); }
th { background: #f1f5f9; cursor: pointer; user-select: none; white-space: nowrap; }
th:hover { background: #e2e8f0; }
th .arrow { font-size: 0.7rem; margin-left: 0.25rem; }

/* Badges */
.badge { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 500; }
.badge-pass { background: #dcfce7; color: #166534; }
.badge-fail { background: #fee2e2; color: #991b1b; cursor: help; }
.badge-skip { background: #f1f5f9; color: var(--muted); }

/* Tooltip */
.tooltip { position: relative; }
.tooltip .tip { display: none; position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: #1e293b; color: #fff; padding: 0.4rem 0.6rem; border-radius: 4px; font-size: 0.75rem; white-space: pre-wrap; max-width: 350px; z-index: 10; margin-bottom: 4px; }
.tooltip:hover .tip { display: block; }

.pct { color: var(--muted); font-size: 0.8em; }

/* Type breakdown: expandable toggle */
.type-toggle { cursor: pointer; user-select: none; }
.type-toggle::before { content: "\25B6"; display: inline-block; width: 1em; font-size: 0.7em; transition: transform 0.15s; }
.type-toggle.open::before { transform: rotate(90deg); }
.type-detail-row { display: none; }
.type-detail-row.open { display: table-row; }
.type-detail-row td { padding: 0.25rem 0.75rem 0.25rem 2rem; background: #fafbfc; }
.failure-list { list-style: none; padding: 0; margin: 0; }
.failure-list li { padding: 0.15rem 0; font-size: 0.8rem; }
.failure-list .err-msg { color: var(--muted); font-size: 0.75rem; margin-left: 0.5rem; }

/* Responsive */
@media (max-width: 768px) {
  .cards { flex-direction: column; }
  table { display: block; overflow-x: auto; }
}
</style>
</head>
<body>

<h1>openCypher Compliance Report</h1>
<p class="subtitle">Generated: <span id="generated-at"></span></p>

<div class="cards" id="dashboard"></div>

<div class="section">
  <h2>Type Breakdown</h2>
  <table id="type-table">
    <thead id="type-thead"></thead>
    <tbody id="type-tbody"></tbody>
  </table>
</div>

<div class="section">
  <h2>Test Comparison</h2>
  <div class="filters">
    <input type="text" id="search" placeholder="Search tests...">
    <select id="type-filter"><option value="">All types</option></select>
    <select id="status-filter">
      <option value="">All statuses</option>
      <option value="pass">Pass</option>
      <option value="fail">Fail</option>
    </select>
  </div>
  <table id="comparison-table">
    <thead id="comp-thead"></thead>
    <tbody id="comp-tbody"></tbody>
  </table>
</div>

<script>
const DATA = $REPORT_DATA_JSON;

function esc(s) {
  var d = document.createElement("div");
  d.textContent = s;
  return d.innerHTML;
}

document.getElementById("generated-at").textContent = DATA.generated_at;

// Dashboard cards
var dashboard = document.getElementById("dashboard");
DATA.databases.forEach(function(db) {
  var pct = db.total > 0 ? Math.round((db.passed / db.total) * 100) : 0;
  var card = document.createElement("div");
  card.className = "card";
  var h3 = document.createElement("h3");
  h3.textContent = db.name;
  card.appendChild(h3);
  if (db.version) {
    var ver = document.createElement("div");
    ver.style.color = "var(--muted)";
    ver.style.fontSize = "0.8rem";
    ver.style.marginBottom = "0.25rem";
    ver.textContent = db.version;
    card.appendChild(ver);
  }
  var rate = document.createElement("div");
  rate.className = "rate";
  rate.textContent = pct + "%";
  card.appendChild(rate);
  var stats = document.createElement("div");
  stats.className = "stats";
  stats.innerHTML = '<span class="pass">' + db.passed + ' passed</span>'
    + '<span class="fail">' + db.failed + ' failed</span>';
  card.appendChild(stats);
  var barBg = document.createElement("div");
  barBg.className = "bar-bg";
  var barFill = document.createElement("div");
  barFill.className = "bar-fill";
  barFill.style.width = pct + "%";
  barBg.appendChild(barFill);
  card.appendChild(barBg);
  dashboard.appendChild(card);
});

// Helper: get failures for a given type and database from comparison data
function getFailures(type, dbName) {
  var failures = [];
  DATA.comparison.forEach(function(c) {
    if (c.type !== type) return;
    var dbInfo = c.databases[dbName];
    if (dbInfo && dbInfo.result !== "pass") {
      failures.push({ element: c.element, result: dbInfo.result, error: dbInfo.error || "" });
    }
  });
  return failures;
}

// Type breakdown table
var dbNames = DATA.databases.map(function(d) { return d.name; });
var typeThead = document.getElementById("type-thead");
var typeTbody = document.getElementById("type-tbody");

var typeHeaderHTML = "<tr><th>Type</th>";
dbNames.forEach(function(n) { typeHeaderHTML += "<th>" + esc(n) + "</th>"; });
typeHeaderHTML += "</tr>";
typeThead.innerHTML = typeHeaderHTML;

var typeKeys = Object.keys(DATA.types).sort();
typeKeys.forEach(function(type) {
  var hasAnyFailures = false;
  dbNames.forEach(function(name) {
    var info = DATA.types[type][name];
    if (info && info.passed < info.total) hasAnyFailures = true;
  });

  // Summary row
  var row = document.createElement("tr");
  var typeCell = document.createElement("td");
  var toggle = document.createElement("strong");
  if (hasAnyFailures) {
    toggle.className = "type-toggle";
    toggle.setAttribute("data-type", type);
  }
  toggle.textContent = type;
  typeCell.appendChild(toggle);
  row.appendChild(typeCell);

  dbNames.forEach(function(name) {
    var td = document.createElement("td");
    var info = DATA.types[type][name];
    if (info) {
      var pct = info.total > 0 ? Math.round((info.passed / info.total) * 100) : 0;
      td.innerHTML = info.passed + "/" + info.total + ' <span class="pct">(' + pct + '%)</span>';
    } else {
      td.className = "pct";
      td.textContent = "\u2014";
    }
    row.appendChild(td);
  });
  typeTbody.appendChild(row);

  // Detail row (hidden by default) — one cell spanning all columns
  if (hasAnyFailures) {
    var detailRow = document.createElement("tr");
    detailRow.className = "type-detail-row";
    detailRow.setAttribute("data-type-detail", type);
    var detailCell = document.createElement("td");
    detailCell.colSpan = 1 + dbNames.length;

    // Build failure list per database
    var detailHTML = "";
    dbNames.forEach(function(name) {
      var failures = getFailures(type, name);
      if (failures.length === 0) return;
      detailHTML += '<div style="margin-bottom:0.5rem"><strong>' + esc(name) + '</strong> &mdash; '
        + failures.length + ' not passing:<ul class="failure-list">';
      failures.forEach(function(f) {
        var badge = '<span class="badge badge-fail">' + esc(f.result) + '</span>';
        detailHTML += '<li>' + badge + ' ' + esc(f.element);
        if (f.error) {
          detailHTML += '<span class="err-msg">' + esc(f.error) + '</span>';
        }
        detailHTML += '</li>';
      });
      detailHTML += '</ul></div>';
    });
    detailCell.innerHTML = detailHTML;
    detailRow.appendChild(detailCell);
    typeTbody.appendChild(detailRow);
  }
});

// Toggle expand/collapse for type detail rows
typeTbody.addEventListener("click", function(e) {
  var toggle = e.target.closest(".type-toggle");
  if (!toggle) return;
  var type = toggle.getAttribute("data-type");
  var detailRow = typeTbody.querySelector('[data-type-detail="' + type + '"]');
  if (!detailRow) return;
  toggle.classList.toggle("open");
  detailRow.classList.toggle("open");
});

// Comparison table
var compThead = document.getElementById("comp-thead");
var compTbody = document.getElementById("comp-tbody");

var compHeaderHTML = '<tr><th data-col="element">Element <span class="arrow">\u25B2</span></th><th data-col="type">Type <span class="arrow"></span></th>';
dbNames.forEach(function(n) { compHeaderHTML += "<th>" + esc(n) + "</th>"; });
compHeaderHTML += "</tr>";
compThead.innerHTML = compHeaderHTML;

// Populate type filter
var typeFilter = document.getElementById("type-filter");
var allTypes = [];
var seenTypes = {};
DATA.comparison.forEach(function(c) {
  if (!seenTypes[c.type]) { allTypes.push(c.type); seenTypes[c.type] = true; }
});
allTypes.sort();
allTypes.forEach(function(t) {
  var opt = document.createElement("option");
  opt.value = t;
  opt.textContent = t;
  typeFilter.appendChild(opt);
});

function makeBadge(info) {
  if (!info) return '<span class="badge badge-skip">\u2014</span>';
  var cls = info.result === "pass" ? "badge-pass" : "badge-fail";
  var label = esc(info.result);
  if (info.error) {
    return '<span class="tooltip"><span class="badge ' + cls + '">' + label + '</span>'
      + '<span class="tip">' + esc(info.error) + '</span></span>';
  }
  return '<span class="badge ' + cls + '">' + label + '</span>';
}

var sortCol = "element";
var sortAsc = true;

function renderComparison() {
  var search = document.getElementById("search").value.toLowerCase();
  var typeVal = typeFilter.value;
  var statusVal = document.getElementById("status-filter").value;

  var rows = DATA.comparison.filter(function(c) {
    if (search && c.element.toLowerCase().indexOf(search) === -1) return false;
    if (typeVal && c.type !== typeVal) return false;
    if (statusVal) {
      var hasStatus = dbNames.some(function(n) { return c.databases[n] && c.databases[n].result === statusVal; });
      if (!hasStatus) return false;
    }
    return true;
  });

  rows.sort(function(a, b) {
    var va = a[sortCol] || "";
    var vb = b[sortCol] || "";
    if (va < vb) return sortAsc ? -1 : 1;
    if (va > vb) return sortAsc ? 1 : -1;
    return 0;
  });

  compTbody.innerHTML = "";
  rows.forEach(function(c) {
    var tr = document.createElement("tr");
    var html = "<td>" + esc(c.element) + "</td><td>" + esc(c.type) + "</td>";
    dbNames.forEach(function(n) {
      html += "<td>" + makeBadge(c.databases[n]) + "</td>";
    });
    tr.innerHTML = html;
    compTbody.appendChild(tr);
  });

  // Update sort arrows
  compThead.querySelectorAll("th").forEach(function(th) {
    var col = th.dataset.col;
    var arrow = th.querySelector(".arrow");
    if (arrow) arrow.textContent = col === sortCol ? (sortAsc ? "\u25B2" : "\u25BC") : "";
  });
}

compThead.addEventListener("click", function(e) {
  var th = e.target.closest("th");
  if (!th || !th.dataset.col) return;
  if (sortCol === th.dataset.col) { sortAsc = !sortAsc; }
  else { sortCol = th.dataset.col; sortAsc = true; }
  renderComparison();
});

document.getElementById("search").addEventListener("input", renderComparison);
typeFilter.addEventListener("change", renderComparison);
document.getElementById("status-filter").addEventListener("change", renderComparison);

renderComparison();
</script>
</body>
</html>
