#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
import process from "node:process";

const CWD = process.cwd();
const DIST_ROOT = path.join(CWD, "dist");
const ALIASES_PATH = path.join(CWD, "src", "anchors", "anchor-aliases.json");

const argv = process.argv.slice(2);
const DRY_RUN = argv.includes("--dry-run");
const STRICT = argv.includes("--strict") || process.env.CI === "1" || process.env.CI === "true";

function escRe(s) {
  return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

async function exists(p) {
  try {
    await fs.access(p);
    return true;
  } catch {
    return false;
  }
}

function normalizeRoute(route) {
  let r = String(route || "").trim();
  if (!r.startsWith("/")) r = "/" + r;
  if (!r.endsWith("/")) r = r + "/";
  r = r.replace(/\/{2,}/g, "/");
  return r;
}

function countIdAttr(html, id) {
  const re = new RegExp(`\\bid=(["'])${escRe(id)}\\1`, "gi");
  let c = 0;
  while (re.exec(html)) c++;
  return c;
}

function findStartTagWithId(html, id) {
  const re = new RegExp(
    `<([a-zA-Z0-9:-]+)\\b[^>]*\\bid=(["'])${escRe(id)}\\2[^>]*>`,
    "i"
  );
  const m = re.exec(html);
  if (!m) return null;
  return { tagName: String(m[1]).toLowerCase(), tag: m[0] };
}

function isInjectedAliasSpan(html, id) {
  const found = findStartTagWithId(html, id);
  if (!found) return false;
  if (found.tagName !== "span") return false;
  return /\bclass=(["'])(?:(?!\1).)*\bpara-alias\b(?:(?!\1).)*\1/i.test(found.tag);
}

function injectBeforeId(html, newId, injectHtml) {
  const re = new RegExp(
    `(<[^>]+\\bid=(["'])${escRe(newId)}\\2[^>]*>)`,
    "i"
  );
  const m = html.match(re);
  if (!m || m.index == null) return { html, injected: false };
  const i = m.index;
  const out = html.slice(0, i) + injectHtml + "\n" + html.slice(i);
  return { html: out, injected: true };
}

async function main() {
  if (!(await exists(ALIASES_PATH))) {
    console.log("ℹ️  Aucun fichier d'aliases (src/anchors/anchor-aliases.json). Skip.");
    return;
  }

  const raw = await fs.readFile(ALIASES_PATH, "utf-8");

  /** @type {Record<string, Record<string,string>>} */
  let aliases;
  try {
    aliases = JSON.parse(raw);
  } catch (e) {
    throw new Error(`JSON invalide: ${ALIASES_PATH} (${e?.message || e})`);
  }

  if (!aliases || typeof aliases !== "object" || Array.isArray(aliases)) {
    throw new Error(`Format invalide: attendu { route: { oldId: newId } } dans ${ALIASES_PATH}`);
  }

  const routes = Object.keys(aliases || {});
  if (routes.length === 0) {
    console.log("ℹ️  Aliases vides. Rien à injecter.");
    return;
  }

  let changedFiles = 0;
  let injectedCount = 0;
  let warnCount = 0;

  for (const routeKey of routes) {
    const route = normalizeRoute(routeKey);
    const map = aliases[routeKey] || {};
    const entries = Object.entries(map);

    if (route !== routeKey) {
      const msg = `⚠️  routeKey non normalisée: "${routeKey}" → "${route}" (corrige anchor-aliases.json)`;
      if (STRICT) throw new Error(msg);
      console.log(msg);
      warnCount++;
    }

    if (entries.length === 0) continue;

    const rel = route.replace(/^\/+|\/+$/g, "");
    const htmlPath = path.join(DIST_ROOT, rel, "index.html");

    if (!(await exists(htmlPath))) {
      const msg = `⚠️  dist introuvable pour route=${route} (${htmlPath})`;
      if (STRICT) throw new Error(msg);
      console.log(msg);
      warnCount++;
      continue;
    }

    let html = await fs.readFile(htmlPath, "utf-8");
    let fileChanged = false;

    for (const [oldId, newId] of entries) {
      if (!oldId || !newId) continue;

      const oldCount = countIdAttr(html, oldId);

      // ✅ déjà injecté => idempotent
      if (oldCount > 0 && isInjectedAliasSpan(html, oldId)) {
        if (STRICT && oldCount !== 1) {
          throw new Error(`oldId dupliqué (${oldCount}) alors qu'il est censé être unique: ${route} id=${oldId}`);
        }
        continue;
      }

      // ⛔️ oldId existe déjà "en vrai" => alias inutile/inversé
      if (oldCount > 0) {
        const found = findStartTagWithId(html, oldId);
        const where = found ? `<${found.tagName} … id="${oldId}" …>` : `id="${oldId}"`;
        const msg =
          `⚠️  alias inutile/inversé: oldId déjà présent (${where}). ` +
          `Supprime ${oldId} -> ${newId} (ou corrige le sens) pour route=${route}`;
        if (STRICT) throw new Error(msg);
        console.log(msg);
        warnCount++;
        continue;
      }

      // newId doit exister UNE fois (sinon injection ambiguë)
      const newCount = countIdAttr(html, newId);
      if (newCount !== 1) {
        const msg = `⚠️  newId non-unique (${newCount}) : ${route} new=${newId} (injection ambiguë)`;
        if (STRICT) throw new Error(msg);
        console.log(msg);
        warnCount++;
        continue;
      }

      const aliasSpan = `<span id="${oldId}" class="para-alias" aria-hidden="true"></span>`;
      const r = injectBeforeId(html, newId, aliasSpan);

      if (!r.injected) {
        const msg = `⚠️  injection impossible (pattern non trouvé) : ${route} new=${newId}`;
        if (STRICT) throw new Error(msg);
        console.log(msg);
        warnCount++;
        continue;
      }

      html = r.html;
      fileChanged = true;
      injectedCount++;
    }

    if (fileChanged) {
      changedFiles++;
      if (!DRY_RUN) await fs.writeFile(htmlPath, html, "utf-8");
    }
  }

  console.log(
    `✅ inject-anchor-aliases: files_changed=${changedFiles} aliases_injected=${injectedCount} warnings=${warnCount}` +
      (DRY_RUN ? " (dry-run)" : "")
  );

  if (STRICT && warnCount > 0) process.exit(2);
}

main().catch((e) => {
  console.error("💥 inject-anchor-aliases:", e?.message || e);
  process.exit(1);
});
