import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
import { dirname, extname, join, normalize, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const defaultRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const rootArgIndex = process.argv.indexOf('--root');
const root = resolve(rootArgIndex >= 0 && process.argv[rootArgIndex + 1] ? process.argv[rootArgIndex + 1] : defaultRoot);
const failures = [];
const extensions = ['.mjs', '.js', '.cjs'];
const sourceDirectories = ['app', 'lib', 'public', 'scripts', 'tools'];
const rootSourceFiles = ['bootstrap.mjs', 'server.mjs'];
const ignoredDirectoryNames = new Set([
  'node_modules', '.git', 'release-output', 'runtime', 'uploads', 'backups', 'logs', 'updates', 'modules',
  'archiv', 'archive', 'archives',
]);

function walk(dir, out = []) {
  if (!existsSync(dir)) return out;
  for (const name of readdirSync(dir)) {
    if (ignoredDirectoryNames.has(name.toLowerCase())) continue;
    const abs = join(dir, name);
    const stat = statSync(abs);
    if (stat.isDirectory()) walk(abs, out);
    else if (extensions.includes(extname(name))) out.push(abs);
  }
  return out;
}

function productionSourceFiles() {
  const files = [];
  for (const name of rootSourceFiles) {
    const file = join(root, name);
    if (existsSync(file)) files.push(file);
  }
  for (const directory of sourceDirectories) walk(join(root, directory), files);
  return [...new Set(files.map(file => resolve(file)))];
}

function resolveLocalImport(importer, specifier) {
  const base = resolve(dirname(importer), specifier);
  const candidates = [base, ...extensions.map(ext => `${base}${ext}`), ...extensions.map(ext => join(base, `index${ext}`))];
  return candidates.find(candidate => existsSync(candidate)) || null;
}

function collectExportsFromFile(file, visited = new Set()) {
  const resolvedFile = resolve(file);
  if (visited.has(resolvedFile)) return new Set();
  visited.add(resolvedFile);
  const source = readFileSync(resolvedFile, 'utf8');
  const names = new Set();
  for (const match of source.matchAll(/\bexport\s+(?:async\s+)?(?:function|class|const|let|var)\s+([A-Za-z_$][\w$]*)/g)) names.add(match[1]);
  for (const match of source.matchAll(/\bexport\s*\{([^}]*)\}(?:\s*from\s*['"]([^'"]+)['"])?\s*;?/g)) {
    for (const rawPart of match[1].split(',')) {
      const part = rawPart.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/g, '').trim();
      if (!part) continue;
      const alias = part.split(/\s+as\s+/i).pop()?.trim();
      if (/^[A-Za-z_$][\w$]*$/.test(alias || '')) names.add(alias);
    }
  }
  for (const match of source.matchAll(/\bexport\s*\*\s*from\s*['"](\.[^'"]+)['"]\s*;?/g)) {
    const target = resolveLocalImport(resolvedFile, match[1]);
    if (!target) continue;
    for (const name of collectExportsFromFile(target, visited)) {
      if (name !== 'default') names.add(name);
    }
  }
  if (/\bexport\s+default\b/.test(source)) names.add('default');
  return names;
}

const files = productionSourceFiles();
for (const importer of files) {
  const source = readFileSync(importer, 'utf8');
  const importPattern = /^[ \t]*import\s*\{([^}]*)\}\s*from\s*['"](\.[^'"]+)['"](?:\s+with\s*\{[^}]*\})?\s*;?/gm;
  for (const match of source.matchAll(importPattern)) {
    const target = resolveLocalImport(importer, match[2]);
    if (!target) {
      failures.push(`${normalize(importer)} importiert fehlende lokale Datei ${match[2]}`);
      continue;
    }
    const exported = collectExportsFromFile(target);
    for (const rawPart of match[1].split(',')) {
      const part = rawPart.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/g, '').trim();
      if (!part) continue;
      const importedName = part.split(/\s+as\s+/i)[0]?.trim();
      if (!/^[A-Za-z_$][\w$]*$/.test(importedName || '')) continue;
      if (!exported.has(importedName)) {
        failures.push(`${normalize(importer)} importiert '${importedName}' aus ${normalize(target)}, aber der Export fehlt.`);
      }
    }
  }
}

if (failures.length) {
  console.error(`ESM-Export-Vertragsprüfung fehlgeschlagen (${failures.length}):`);
  failures.slice(0, 100).forEach(item => console.error(`- ${item}`));
  process.exit(1);
}

console.log(`ESM-Export-Vertragsprüfung erfolgreich: ${files.length} produktive Quelldateien geprüft; Archive und Übergabepakete ausgeschlossen.`);
