#!/usr/bin/env node
/* eslint-disable no-console */
const fs = require('fs');
const path = require('path');

const args = process.argv.slice(2);

// ---- CLI options -----------------------------------------------------------
function getArg(name, def) {
  const i = args.findIndex(a => a === `--${name}` || a.startsWith(`--${name}=`));
  if (i === -1) return def;
  const v = args[i].includes('=') ? args[i].split('=').slice(1).join('=') : args[i + 1];
  return v ?? true;
}
const ROOT = process.cwd();
const DIR  = path.resolve(ROOT, getArg('dir', 'lib'));
const OUT  = path.resolve(ROOT, getArg('out', 'code.js'));
const ENTRY= getArg('entry', 'lib/test-manual.js');
const ORDER= getArg('order', 'topo'); // 'topo' | 'alpha' | 'entry-first'

// ---- Helpers ---------------------------------------------------------------
function readFileSafe(p) {
  try { return fs.readFileSync(p, 'utf8'); }
  catch (e) { return null; }
}
function* walk(dir) {
  const list = fs.readdirSync(dir, { withFileTypes: true });
  for (const d of list) {
    const p = path.join(dir, d.name);
    if (d.isDirectory()) yield* walk(p);
    else if (d.isFile() && d.name.endsWith('.js')) yield p;
  }
}

// Normalise un chemin importé (./x, ../x, sans extension)
function resolveImport(fromFile, spec) {
  if (!spec) return null;
  if (spec.startsWith('node:')) return null;               // builtin
  if (!spec.startsWith('./') && !spec.startsWith('../')) return null; // externe: ignorer
  let target = path.resolve(path.dirname(fromFile), spec);
  if (!/\.js$/i.test(target)) {
    const withJs = `${target}.js`;
    if (fs.existsSync(withJs)) target = withJs;
    else if (fs.existsSync(path.join(target, 'index.js'))) target = path.join(target, 'index.js');
  }
  return target;
}

// Très simple parseur d’imports/require (suffisant pour 95 % des cas)
const RE_IMPORT_1 = /\bimport\s+[^'"]*\s+from\s+['"]([^'"]+)['"]/g; // import X from '...'
const RE_IMPORT_2 = /\bimport\s+['"]([^'"]+)['"]/g;                 // import '...'
const RE_REQUIRE  = /\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;         // require('...')

function extractDeps(absPath, code) {
  const specs = new Set();
  let m;
  for (RE_IMPORT_1.lastIndex = 0; (m = RE_IMPORT_1.exec(code)); ) specs.add(m[1]);
  for (RE_IMPORT_2.lastIndex = 0; (m = RE_IMPORT_2.exec(code)); ) specs.add(m[1]);
  for (RE_REQUIRE.lastIndex = 0; (m = RE_REQUIRE.exec(code)); )   specs.add(m[1]);
  const deps = [];
  for (const s of specs) {
    const r = resolveImport(absPath, s);
    if (r) deps.push(r);
  }
  return deps;
}

function asciiBox(title, width = 70) {
  const clean = title.length > width - 6 ? title.slice(0, width - 9) + '…' : title;
  const line  = '─'.repeat(width - 2);
  const pad   = width - 6 - clean.length;
  return [
    `/*`,
    `┌${line}┐`,
    `│  ${clean}${' '.repeat(Math.max(0, pad))}  │`,
    `└${line}┘`,
    `*/`
  ].join('\n');
}

// ---- Collecte des fichiers -------------------------------------------------
if (!fs.existsSync(DIR) || !fs.statSync(DIR).isDirectory()) {
  console.error(`[pack-lib] Dossier introuvable: ${DIR}`);
  process.exit(1);
}
const allFiles = Array.from(walk(DIR)).map(p => path.resolve(p));

// ---- Graphe de dépendances -------------------------------------------------
/**
 * graph: Map<file, { deps: string[], code: string }>
 */
const graph = new Map();
for (const f of allFiles) {
  const code = readFileSafe(f) ?? '';
  const deps = extractDeps(f, code).filter(d => allFiles.includes(d));
  graph.set(f, { deps, code });
}

// ---- Ordonnancement --------------------------------------------------------
function topoSort(graph, entryAbs) {
  const visited = new Set();
  const temp = new Set();
  const order = [];

  const visit = (n) => {
    if (visited.has(n)) return;
    if (temp.has(n)) return; // cycle: on ignore pour éviter boucle
    temp.add(n);
    const { deps = [] } = graph.get(n) || {};
    for (const d of deps) if (graph.has(d)) visit(d);
    temp.delete(n);
    visited.add(n);
    order.push(n);
  };

  if (entryAbs && graph.has(entryAbs)) visit(entryAbs);
  for (const n of graph.keys()) visit(n);
  return order;
}

let order;
const entryAbs = path.resolve(ROOT, ENTRY);
if (ORDER === 'alpha') {
  order = [...graph.keys()].sort((a, b) => a.localeCompare(b));
} else if (ORDER === 'entry-first') {
  order = [entryAbs, ...[...graph.keys()].filter(f => f !== entryAbs).sort((a,b)=>a.localeCompare(b))]
           .filter(Boolean);
} else {
  order = topoSort(graph, entryAbs);
}

// ---- Écriture du bundle ----------------------------------------------------
const header = `/*
  code.js — bundle concaténé
  Généré: ${new Date().toISOString()}
  Source: ${path.relative(ROOT, DIR)}
  Fichiers: ${order.length}
  Ordre: ${ORDER}
*/\n\n`;

let out = header;

for (const file of order) {
  const rel = path.relative(ROOT, file).replace(/\\/g, '/');
  const box = asciiBox(`File: ${rel}`);
  const code = (graph.get(file)?.code) ?? '';
  out += `${box}\n\n${code}\n\n`;
}

// S’assure que le dossier cible existe
fs.writeFileSync(OUT, out, 'utf8');

const relOut = path.relative(ROOT, OUT).replace(/\\/g, '/');
console.log(`[pack-lib] OK -> ${relOut} (${order.length} fichiers)`);

