// scripts/generate-spec-content.ts
// Usage: bun scripts/generate-spec-content.ts [--force]
//
// Clone le contenu complet des 135 topics de specification.website chez nous.
// Pour chaque item de CHECKLIST, dérive l'URL .md (item.url sans `/` final + ".md"),
// fetch le markdown brut (frontmatter + corps) et l'écrit verbatim dans
// src/content/spec/<category>/<slug>.md.
//
// - Concurrence bornée (~5), timeout 10s/fetch, retry une fois.
// - Skip si le fichier local existe déjà, sauf --force.
// - Idempotent / re-runnable (sous-projet A : re-sync périodique).
// - Exit non-zéro si au moins un fetch requis échoue.

import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { CHECKLIST } from "../src/lib/spec/checklist-data";
import type { SpecItem } from "../src/lib/spec/types";

const ROOT = join(import.meta.dir, "..");
const OUT_DIR = join(ROOT, "src", "content", "spec");
const CONCURRENCY = 5;
const TIMEOUT_MS = 10_000;
const FORCE = process.argv.includes("--force");

/** All items, flattened across the 10 categories. */
const ITEMS: SpecItem[] = CHECKLIST.flatMap((c) => c.items);
const TOTAL = ITEMS.length;

/** Derive the raw-markdown URL: strip the trailing slash(es) and append `.md`. */
function mdUrl(item: SpecItem): string {
  return `${item.url.replace(/\/+$/, "")}.md`;
}

/** Local destination: src/content/spec/<category>/<slug>.md. */
function destPath(item: SpecItem): string {
  // id is `${category}/${slug}` — reuse it directly as the relative path.
  return join(OUT_DIR, `${item.id}.md`);
}

async function fetchWithTimeout(url: string): Promise<string> {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
  try {
    const res = await fetch(url, { signal: ctrl.signal });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.text();
  } finally {
    clearTimeout(timer);
  }
}

/** Fetch with a single retry on any failure. */
async function fetchMd(url: string): Promise<string> {
  try {
    return await fetchWithTimeout(url);
  } catch {
    return await fetchWithTimeout(url);
  }
}

type Outcome = "written" | "skipped" | "failed";

let written = 0;
let skipped = 0;
const failed: { id: string; url: string; error: string }[] = [];
let done = 0;

async function processItem(item: SpecItem): Promise<Outcome> {
  const dest = destPath(item);
  if (!FORCE && existsSync(dest)) {
    skipped++;
    done++;
    return "skipped";
  }
  const url = mdUrl(item);
  try {
    const body = await fetchMd(url);
    if (!body.trim().startsWith("---")) {
      throw new Error("unexpected body (no frontmatter)");
    }
    mkdirSync(dirname(dest), { recursive: true });
    writeFileSync(dest, body);
    written++;
    done++;
    console.log(`[${done}/${TOTAL}] wrote ${item.id}`);
    return "written";
  } catch (err) {
    const error = err instanceof Error ? err.message : String(err);
    failed.push({ id: item.id, url, error });
    done++;
    console.error(`[${done}/${TOTAL}] FAILED ${item.id} (${url}): ${error}`);
    return "failed";
  }
}

/** Small concurrency pool — politeness over speed. */
async function run(): Promise<void> {
  const queue = [...ITEMS];
  const workers = Array.from({ length: CONCURRENCY }, async () => {
    while (queue.length > 0) {
      const item = queue.shift();
      if (!item) break;
      await processItem(item);
    }
  });
  await Promise.all(workers);
}

await run();

console.log(
  `\nDone — ${written} written, ${skipped} skipped, ${failed.length} failed (out of ${TOTAL}).`,
);

if (failed.length > 0) {
  console.error("\nFailures:");
  for (const f of failed) console.error(`  - ${f.id}: ${f.error} (${f.url})`);
  process.exit(1);
}
