/**
 * Build the standalone, dependency-free directory site -> public/index.html.
 *
 *   node build-site.mjs
 *
 * The full directory is embedded (compact per-state arrays) so the page works
 * offline as a single file: pick a state, search, and browse every organization
 * with its real phone + address. The four seam columns (contact, title, email,
 * domain) are shown as "— not in source" so the gap is visible, never faked.
 */
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { JURISDICTIONS } from './src/fips.mjs';
import { SOURCE_NAME } from './src/schema.mjs';

const DIR = dirname(fileURLToPath(import.meta.url));
const DATASET = join(DIR, 'dataset');
const contacts = JSON.parse(readFileSync(join(DATASET, 'contacts.json'), 'utf8'));
const summary = JSON.parse(readFileSync(join(DATASET, 'summary.json'), 'utf8'));

const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

// Compact per-state payload: { ST: { name, rows: [[org,type,city,county,phone,zip,enroll], ...] } }
const NAME = Object.fromEntries(JURISDICTIONS.map((j) => [j.usps, j.name]));
const data = {};
for (const r of contacts) {
  (data[r.state] = data[r.state] || { name: NAME[r.state] || r.stateName, rows: [] }).rows
    .push([r.organizationName, r.organizationType, r.city, r.county, r.phone, r.zip, r.enrollment]);
}
const stateCounts = Object.fromEntries(Object.entries(data).map(([k, v]) => [k, v.rows.length]));

const THEME = `
:root{--bg:#0f1320;--panel:#161c2d;--panel2:#1c2436;--text:#e8ecf5;--muted:#94a0bb;--accent:#4f8cff;--accent2:#36d39a;--border:#283048;--seam:#f0a93b}
*{box-sizing:border-box}html,body{margin:0}body{font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:linear-gradient(180deg,#0c1019,#0f1320);color:var(--text);line-height:1.55}
a{color:var(--accent)}
.wrap{max-width:1180px;margin:0 auto;padding:24px}
header.top{padding:28px 0 8px}
h1{font-size:clamp(22px,3.4vw,32px);margin:0 0 6px}
.lead{color:var(--muted);max-width:820px;margin:0}
.src{font-size:12.5px;color:var(--muted);margin-top:10px}
.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:20px 0}
.metric{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:14px}
.metric .v{font-size:24px;font-weight:800;color:var(--accent2)}
.metric .l{font-size:11.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-top:4px}
.seamcard{background:var(--panel);border:1px solid var(--border);border-left:4px solid var(--seam);border-radius:12px;padding:14px 16px;margin:0 0 18px}
.seamcard b{color:var(--seam)}
.controls{display:flex;gap:10px;flex-wrap:wrap;align-items:center;position:sticky;top:0;background:rgba(15,19,32,.92);backdrop-filter:blur(8px);padding:12px 0;z-index:5;border-bottom:1px solid var(--border)}
select,input{background:var(--panel2);color:var(--text);border:1px solid var(--border);border-radius:9px;padding:9px 12px;font-size:14px}
input{min-width:280px;flex:1}
.count{color:var(--muted);font-size:13px}
table{width:100%;border-collapse:collapse;margin-top:14px;font-size:13.5px}
th,td{border-bottom:1px solid var(--border);padding:8px 10px;text-align:left;vertical-align:top}
th{position:sticky;top:60px;background:var(--panel2);color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.06em;cursor:pointer}
tbody tr:hover{background:var(--panel)}
.seamcol{color:var(--seam);opacity:.75;font-style:italic}
.statehdr{background:var(--panel);font-weight:800;color:var(--accent)}
.foot{color:var(--muted);font-size:12px;margin:26px 0;border-top:1px solid var(--border);padding-top:14px}
@media(max-width:760px){.metrics{grid-template-columns:repeat(2,1fr)}input{min-width:0}}
`;

const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>US SLED Contact Directory — all 50 states + DC</title>
<meta name="generator" content="Forge — Outcome-as-a-Service"/>
<style>${THEME}</style></head><body><div class="wrap">
<header class="top">
  <h1>US SLED Contact Directory — Education layer</h1>
  <p class="lead">Every public school district / education agency (a State, Local &amp; Education organization) across all 50 US states + DC, <strong>grouped by state</strong>, with its real main phone and address. Built entirely from public-record data.</p>
  <p class="src">Source: ${esc(SOURCE_NAME)} · dataset year ${esc(summary.datasetYear)} · ${contacts.length.toLocaleString()} organizations.</p>
</header>

<div class="metrics">
  <div class="metric"><div class="v">${contacts.length.toLocaleString()}</div><div class="l">Organizations</div></div>
  <div class="metric"><div class="v">51</div><div class="l">States + DC</div></div>
  <div class="metric"><div class="v">${esc(summary.phoneCoveragePct)}%</div><div class="l">Phone coverage</div></div>
  <div class="metric"><div class="v">${esc(summary.datasetYear)}</div><div class="l">Data year</div></div>
</div>

<div class="seamcard">
  <b>Honest scope (disclosed seam).</b> This public source publishes the organization name, address, county and main phone — it does <b>not</b> publish a named contact, that person's title, an email, or a web domain. Those four columns are shown as <span class="seamcol">— not in source</span> and are <b>never fabricated</b>. See the delivery package's <code>proof/LIMITATIONS.md</code> for how to source them.
</div>

<div class="controls">
  <select id="state"><option value="">All states (${contacts.length.toLocaleString()})</option></select>
  <input id="q" type="search" placeholder="Search organization, city, county, phone…"/>
  <span class="count" id="count"></span>
</div>

<table id="tbl"><thead><tr>
  <th data-k="0">Organization (company name)</th>
  <th data-k="1">Type</th>
  <th data-k="4">Phone</th>
  <th>Title</th><th>Email</th><th>Domain</th>
  <th data-k="2">City</th><th data-k="3">County</th><th data-k="5">ZIP</th>
</tr></thead><tbody id="rows"></tbody></table>

<p class="foot">Grouped by state · generated by Forge. Real fields (name, type, phone, city, county, ZIP) come directly from ${esc(SOURCE_NAME)}. Title / Email / Domain are disclosed seams (not provided by this source).</p>
</div>
<script id="data" type="application/json">${JSON.stringify(data)}</script>
<script>
const DATA = JSON.parse(document.getElementById('data').textContent);
const COUNTS = ${JSON.stringify(stateCounts)};
const sel = document.getElementById('state'), q = document.getElementById('q'),
      rows = document.getElementById('rows'), count = document.getElementById('count');
const states = Object.keys(DATA).sort();
for (const s of states) { const o = document.createElement('option'); o.value = s; o.textContent = s + ' — ' + DATA[s].name + ' (' + COUNTS[s] + ')'; sel.appendChild(o); }
const seam = '<td class="seamcol">— not in source</td>';
function render() {
  const st = sel.value, term = q.value.trim().toLowerCase();
  const picked = st ? [st] : states;
  let html = '', n = 0;
  for (const s of picked) {
    const matched = DATA[s].rows.filter(r => !term || (r[0]+' '+r[2]+' '+r[3]+' '+r[4]).toLowerCase().includes(term));
    if (!matched.length) continue;
    html += '<tr class="statehdr"><td colspan="9">' + s + ' — ' + DATA[s].name + ' · ' + matched.length + ' organizations</td></tr>';
    for (const r of matched) {
      n++;
      html += '<tr><td>' + esc(r[0]) + '</td><td>' + esc(r[1]) + '</td><td>' + esc(r[4]) + '</td>'
            + seam + seam + seam
            + '<td>' + esc(r[2]) + '</td><td>' + esc(r[3]) + '</td><td>' + esc(r[5]) + '</td></tr>';
      if (n > 4000 && !st) break; // cap "all states" render for responsiveness; pick a state to see all
    }
    if (n > 4000 && !st) { html += '<tr><td colspan="9" style="color:var(--muted)">… showing first 4,000 of ' + ${contacts.length} + '. Pick a state to see every record.</td></tr>'; break; }
  }
  rows.innerHTML = html;
  count.textContent = (st ? DATA[st].name : 'All states') + ' · ' + n.toLocaleString() + ' shown';
}
function esc(s){return String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
sel.onchange = render; q.oninput = render;
render();
</script>
</body></html>`;

mkdirSync(join(DIR, 'public'), { recursive: true });
writeFileSync(join(DIR, 'public', 'index.html'), html);
console.log(`[build-site] public/index.html (${(Buffer.byteLength(html) / 1024 / 1024).toFixed(2)} MB), ${contacts.length} orgs, ${Object.keys(data).length} states embedded.`);
