#!/usr/bin/env node
/**
 * Porte de qualité — exécute les vérifications EN PARALLÈLE.
 *
 * Les cinq vérifications sont indépendantes (aucune ne lit la sortie d'une
 * autre). Les enchaîner avec `&&` imposait deux coûts inutiles :
 *   - la somme des durées au lieu du maximum ;
 *   - sept invocations `npm run` imbriquées, chacune avec son propre coût de
 *     démarrage npm (~300 ms).
 *
 * On les lance donc toutes d'un coup, on tamponne les sorties, et on les
 * restitue dans un ordre stable à la fin. Le détail par tâche reste affiché
 * pour que le goulot d'étranglement demeure visible.
 *
 * `--sequential` rétablit l'exécution en série (utile pour isoler un échec
 * quand deux tâches écrivent des messages entremêlés).
 */
import { spawn } from "node:child_process";

const isWin = process.platform === "win32";
const sequential = process.argv.includes("--sequential");

/** bin = binaire de node_modules/.bin (résolu par npm via PATH). */
const TASKS = [
  {
    name: "eslint",
    cmd: "eslint",
    args: [".", "--cache", "--cache-location", "node_modules/.cache/eslint/"],
  },
  {
    name: "tsc",
    cmd: "tsc",
    args: ["-p", "jsconfig.json"],
  },
  // Séparer les tests API (node) et frontend (jsdom) en deux processus a été
  // mesuré : aucun gain (4,9 s dans les deux cas — c'est la mise en place de
  // jsdom qui domine, et les deux processus se disputent les cœurs). Gardé en
  // un seul.
  { name: "vitest", cmd: "vitest", args: ["run"] },
  {
    name: "ruff",
    cmd: "python",
    args: ["-m", "ruff", "check", "."],
  },
  // Les deux suites Python tournent dans des processus distincts : chaque app a
  // son propre paquet `src`, et les réunir dans un seul pytest fait que le
  // premier `src` importé masque l'autre (ModuleNotFoundError: src.parser).
  // Séparées, elles s'exécutent aussi en parallèle.
  { name: "pytest:geocoder", cmd: "python", args: ["-m", "pytest", "tests", "-q"], cwd: "apps/geocoder" },
  { name: "pytest:crawler", cmd: "python", args: ["-m", "pytest", "tests", "-q"], cwd: "apps/crawler" },
];

function run(task) {
  return new Promise((resolve) => {
    const started = Date.now();
    const child = spawn(task.cmd, task.args, {
      shell: isWin,
      cwd: task.cwd ?? process.cwd(),
    });

    let out = "";
    child.stdout.on("data", (b) => (out += b));
    child.stderr.on("data", (b) => (out += b));
    child.on("error", (err) => {
      resolve({ ...task, code: 1, out: `${err.message}\n`, ms: Date.now() - started });
    });
    child.on("close", (code) => {
      resolve({ ...task, code, out, ms: Date.now() - started });
    });
  });
}

const started = Date.now();
let results;

if (sequential) {
  results = [];
  for (const t of TASKS) results.push(await run(t));
} else {
  results = await Promise.all(TASKS.map(run));
}

const failed = results.filter((r) => r.code !== 0);

for (const r of results) {
  const mark = r.code === 0 ? "✓" : "✗";
  console.log(`\n${mark} ${r.name} (${(r.ms / 1000).toFixed(1)}s)`);
  // Sur succès on ne montre que les avertissements éventuels, pas le bruit.
  if (r.code !== 0 || /warning|warn/i.test(r.out)) {
    const body = r.out.trimEnd();
    if (body) console.log(body.replace(/^/gm, "   "));
  }
}

const total = ((Date.now() - started) / 1000).toFixed(1);

if (failed.length) {
  console.error(`\n✗ Porte de qualité en échec : ${failed.map((f) => f.name).join(", ")} (${total}s)`);
  process.exit(1);
}
console.log(`\n✓ Tout est vert (${total}s)`);
