#!/usr/bin/env node
// generate_conformance_doc.mjs
//
// Generates two outputs from `spec-compliance/registry/*.md`:
//   1. `spec-compliance/CONFORMANCE.md` — the full normative-claims summary.
//   2. The `<!-- AUTO-GENERATED:conformance-summary START -->` … `END` block
//      inside the canonical `README.md` at the repo root. Localized READMEs
//      (`README.es.md`, `README.zh.md`, etc.) carry hand-translated static
//      content and are deliberately excluded — see the README_FILES const
//      comment below for the rationale.
//
// The drift gate `scripts/check_conformance_doc.mjs` runs this script and
// then `git diff --exit-code` to catch hand edits to either output.

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadRegistry } from './lib/conformance-registry.mjs';

const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const OUT_CONFORMANCE = path.join(REPO_ROOT, 'spec-compliance', 'CONFORMANCE.md');
// Only the canonical English README receives the auto-generated dynamic
// summary. Localized READMEs declare a translation-lag contract at their
// top; injecting English text into the middle of each one would surprise
// translators and risk breaking the AUTO-GENERATED markers under translation
// tooling. Localized files carry a static one-liner that points readers at
// the canonical surface — see the dedicated marker-block style below.
const README_FILES = ['README.md'];

const SUMMARY_START = '<!-- AUTO-GENERATED:conformance-summary START — generated by scripts/generate_conformance_doc.mjs; do not edit by hand -->';
const SUMMARY_END = '<!-- AUTO-GENERATED:conformance-summary END -->';

function* walkFiles(dir, predicate) {
  if (!fs.existsSync(dir)) return;
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) yield* walkFiles(full, predicate);
    else if (predicate(full)) yield full;
  }
}

// ── Rendering ───────────────────────────────────────────────────────────────

function trimmedProse(prose) {
  return prose.join('\n').trim();
}

// Render the detailed subsection for one registry entry. Shared by targeted
// sections and Non-Goals so both carry identical spec/schema binding; Non-Goals
// pass includeVerifiedBy=false since nothing verifies a non-claim. Always ends
// with a trailing blank line so the caller can append the next block directly.
function renderEntryDetail(lines, e, { includeVerifiedBy }) {
  lines.push(`### ${e.id} — ${e.title}`);
  lines.push('');
  lines.push(`- **Edition:** ECMA-376 ${e.meta.edition ?? '?'}`);
  lines.push(`- **Part / Section:** Part ${e.meta.part ?? '?'} § ${e.meta.section ?? '?'}`);
  lines.push(`- **Canonical URL:** ${e.meta.url ?? '—'}`);
  lines.push(`- **Schema reference:** \`${e.meta.schemaRef ?? '—'}\``);
  if (includeVerifiedBy && (e.meta.verifiedBy ?? '').trim()) {
    lines.push(`- **Verified by:** ${e.meta.verifiedBy}`);
  }
  lines.push('');
  const prose = trimmedProse(e.prose);
  if (prose) {
    lines.push(prose);
    lines.push('');
  }
}

function specFamilyTitle(entries) {
  const families = new Set();
  for (const e of entries) {
    if (e.id.startsWith('ECMA')) families.add('ECMA-376');
    else families.add(e.id.replace(/^([A-Z]+).*$/, '$1'));
  }
  return [...families].sort().join(', ');
}

function renderConformanceMd(registry) {
  const { entries, nonGoals } = registry;
  const lines = [];
  lines.push('# Conformance');
  lines.push('');
  // Editor-facing provenance only; an HTML comment keeps it out of the rendered
  // page (GitHub etc.) while still warning anyone editing the raw Markdown. The
  // README marker block uses the same pattern. Placed after the H1 so renderers
  // still treat `# Conformance` as the document title.
  lines.push('<!-- Generated by scripts/generate_conformance_doc.mjs from spec-compliance/registry/*.md. Do not edit by hand — `npm run check:conformance-doc` reverts hand edits. -->');
  lines.push('');
  lines.push(`safe-docx targets a defined subset of **${specFamilyTitle(entries) || 'no specs yet'}**.`);
  lines.push('Each entry below carries a stable serial ID, the section it claims, and a');
  lines.push('binding into a vendored normative schema. Tests carry matching structured');
  lines.push('Allure labels via `testAllure.conformance({…})`; source code carries');
  lines.push('`@conformance` JSDoc tags. The citation-hygiene lint at');
  lines.push('`scripts/check_conformance_citations.mjs` enforces both.');
  lines.push('');
  lines.push('## Targeted sections');
  lines.push('');
  if (entries.length === 0) {
    lines.push('_The registry is empty._');
  } else {
    lines.push('| ID | Title | Edition | Part | Section | Schema reference | Verified by |');
    lines.push('| --- | --- | --- | --- | --- | --- | --- |');
    for (const e of entries) {
      const verified = (e.meta.verifiedBy ?? '').trim() || '—';
      const schemaRef = (e.meta.schemaRef ?? '').trim() || '—';
      lines.push(`| \`${e.id}\` | ${e.title} | ${e.meta.edition ?? '?'} | ${e.meta.part ?? '?'} | ${e.meta.section ?? '?'} | \`${schemaRef}\` | ${verified} |`);
    }
    lines.push('');
    for (const e of entries) {
      renderEntryDetail(lines, e, { includeVerifiedBy: true });
    }
  }

  lines.push('## Non-Goals');
  lines.push('');
  if (registry.nonGoalsPreamble) {
    lines.push(registry.nonGoalsPreamble);
    lines.push('');
  }
  if (nonGoals.length === 0) {
    lines.push('_None yet. As the registry grows, sections explicitly out of scope will be');
    lines.push('listed here so the framework treats "we do not target this" as a first-class');
    lines.push('statement._');
    lines.push('');
  } else {
    // Non-Goals render with the same detail block as targeted sections (minus
    // the Verified-by line — nothing verifies a non-claim) so "we explicitly do
    // not target this" carries the same spec/schema binding as a claim.
    for (const e of nonGoals) {
      renderEntryDetail(lines, e, { includeVerifiedBy: false });
    }
  }
  lines.push(`_Sources: ${registry.sources.map((s) => `\`${s}\``).join(', ') || 'none'}._`);
  lines.push('');
  return lines.join('\n');
}

function countGaps() {
  // Count @conformance-gap occurrences across packages/*/src/ (excluding tests).
  let count = 0;
  for (const file of walkFiles(path.join(REPO_ROOT, 'packages'), (f) => f.endsWith('.ts') && !f.endsWith('.test.ts'))) {
    const rel = path.relative(REPO_ROOT, file);
    if (!/^packages\/[^/]+\/src\//.test(rel)) continue;
    const text = fs.readFileSync(file, 'utf8');
    const matches = text.match(/@conformance-gap\b/g);
    if (matches) count += matches.length;
  }
  return count;
}

function renderReadmeBlock(registry) {
  const N = registry.entries.length;
  const M = registry.nonGoals.length;
  const K = countGaps();
  const lines = [];
  lines.push(SUMMARY_START);
  lines.push('safe-docx targets a defined subset of **ECMA-376 5th edition**. The full surface');
  lines.push('(targeted sections, Non-Goals, and verification status) lives at');
  lines.push('[spec-compliance/CONFORMANCE.md](spec-compliance/CONFORMANCE.md).');
  lines.push('');
  lines.push(`- **${N}** section${N === 1 ? '' : 's'} claimed`);
  lines.push(`- **${M}** section${M === 1 ? '' : 's'} explicitly out-of-scope (Non-Goals)`);
  lines.push(`- **${K}** known gap${K === 1 ? '' : 's'} under \`@conformance-gap\``);
  lines.push('- Vendored normative schemas: `spec-compliance/ecma-376/schemas/`');
  lines.push(SUMMARY_END);
  return lines.join('\n');
}

function replaceMarkerBlock(filePath, replacement) {
  const original = fs.readFileSync(filePath, 'utf8');
  const startIdx = original.indexOf(SUMMARY_START);
  const endIdx = original.indexOf(SUMMARY_END);
  if (startIdx === -1 || endIdx === -1) {
    throw new Error(`Marker block missing in ${path.relative(REPO_ROOT, filePath)}. Add the START and END comments before regenerating.`);
  }
  const before = original.slice(0, startIdx);
  const after = original.slice(endIdx + SUMMARY_END.length);
  const next = before + replacement + after;
  if (next !== original) {
    fs.writeFileSync(filePath, next);
  }
}

// ── Main ────────────────────────────────────────────────────────────────────

function main() {
  const registry = loadRegistry();
  const conformanceMd = renderConformanceMd(registry);
  fs.writeFileSync(OUT_CONFORMANCE, conformanceMd);

  const readmeBlock = renderReadmeBlock(registry);
  for (const readme of README_FILES) {
    const full = path.join(REPO_ROOT, readme);
    if (!fs.existsSync(full)) {
      console.warn(`[warn] ${readme} does not exist; skipping`);
      continue;
    }
    try {
      replaceMarkerBlock(full, readmeBlock);
    } catch (e) {
      console.error(`[error] ${readme}: ${e.message}`);
      process.exit(1);
    }
  }

  console.log(`generate_conformance_doc: wrote ${path.relative(REPO_ROOT, OUT_CONFORMANCE)} and updated ${README_FILES.length} README files`);
}

main();
