#!/usr/bin/env node
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { join, relative, resolve, sep } from "node:path";
import console from "node:console";
import process from "node:process";
import { URL, fileURLToPath } from "node:url";

const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
const packagesDir = join(root, "packages");
const BUILT_INS = new Set([
  "Async",
  "Loading",
  "Success",
  "Error",
  "Static",
  "Dynamic",
  "Portal",
  "Transition",
  "Component",
  "KeepAlive",
  "Slot",
]);

const IGNORED_DIRECTORIES = new Set([
  "node_modules",
  "dist",
  "coverage",
  ".git",
  ".wrnexus",
  ".publish",
]);

function walk(directory, extension, output = []) {
  if (!existsSync(directory)) return output;
  for (const name of readdirSync(directory)) {
    if (IGNORED_DIRECTORIES.has(name)) continue;
    const file = join(directory, name);
    const stat = statSync(file);
    if (stat.isDirectory()) walk(file, extension, output);
    else if (file.endsWith(extension)) output.push(file);
  }
  return output;
}

const packageManifests = new Map();
for (const name of readdirSync(packagesDir)) {
  const manifestPath = join(packagesDir, name, "package.json");
  if (!existsSync(manifestPath)) continue;
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
  packageManifests.set(name, {
    name: manifest.name,
    dependencies: new Set([
      ...Object.keys(manifest.dependencies ?? {}),
      ...Object.keys(manifest.peerDependencies ?? {}),
      ...Object.keys(manifest.optionalDependencies ?? {}),
    ]),
  });
}

const files = walk(packagesDir, ".wrn");
const declared = new Map();
for (const file of files) {
  const source = readFileSync(file, "utf8");
  const match = /\b(?:component|layout|page)\s+([A-Z][A-Za-z0-9_$]*)\b/.exec(source);
  if (match) declared.set(match[1], file);
}

const failures = [];
for (const file of files) {
  const source = readFileSync(file, "utf8");
  const packageName = relative(packagesDir, file).split(sep)[0];
  const manifest = packageManifests.get(packageName);
  for (const match of source.matchAll(/\bfrom\s+["'](@wrnexus\/[^"']+)["']/g)) {
    const dependency = match[1].split("/").slice(0, 2).join("/");
    if (dependency === manifest?.name || manifest?.dependencies.has(dependency)) continue;
    failures.push(
      `${relative(root, file).replace(/\\/g, "/")}: ${dependency} must be declared as a package dependency`,
    );
  }
  const imported = new Set();
  for (const match of source.matchAll(
    /^\s*import\s+([A-Z][A-Za-z0-9_$]*)\s+from\s+["'][^"']+["']/gm,
  )) {
    imported.add(match[1]);
  }
  for (const match of source.matchAll(/^\s*import\s*\{([^}]+)\}\s*from\s*["'][^"']+["']/gm)) {
    for (const part of match[1].split(",")) {
      const binding = part
        .trim()
        .replace(/^type\s+/, "")
        .split(/\s+as\s+/)
        .pop()
        ?.trim();
      if (binding) imported.add(binding);
    }
  }
  const own = /\b(?:component|layout|page)\s+([A-Z][A-Za-z0-9_$]*)\b/.exec(source)?.[1];
  const used = new Set([...source.matchAll(/<([A-Z][A-Za-z0-9_$]*)\b/g)].map((match) => match[1]));
  for (const name of used) {
    if (name === own || BUILT_INS.has(name) || !declared.has(name) || imported.has(name)) continue;
    failures.push(
      `${relative(root, file).replace(/\\/g, "/")}: <${name}> requires an explicit import`,
    );
  }
  const layoutSymbol = /\blayout\s*=\s*([A-Z][A-Za-z0-9_$]*)\b/.exec(source)?.[1];
  if (
    layoutSymbol &&
    layoutSymbol !== own &&
    declared.has(layoutSymbol) &&
    !imported.has(layoutSymbol)
  ) {
    failures.push(
      `${relative(root, file).replace(/\\/g, "/")}: layout ${layoutSymbol} requires an explicit import`,
    );
  }
}

if (failures.length) {
  console.error(`WRN-IMPORT-PACKAGE: ${failures.length} package component import error(s):`);
  for (const failure of failures) console.error(`- ${failure}`);
  process.exit(1);
}
console.log(`✓ Package component imports are explicit (${files.length} WRN files checked)`);
