import { createHash } from "node:crypto";
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, join, relative, resolve, sep } from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";

const require = createRequire(import.meta.url);
const here = dirname(fileURLToPath(import.meta.url));
const root = resolve(here, "..");
const scriptPath = fileURLToPath(import.meta.url);

function normalizeText(value) {
  return value.replace(/\r\n?/g, "\n");
}

function sha256(value) {
  return createHash("sha256").update(value).digest("hex");
}

function loadTypeScript() {
  const candidates = [process.env.TYPESCRIPT_PATH, "typescript"].filter(Boolean);
  for (const candidate of candidates) {
    try {
      return require(candidate);
    } catch {
      // Try the next local or explicitly supplied compiler path.
    }
  }
  throw new Error(
    "TypeScript is required to build the editor compiler. Run `bun install` or set TYPESCRIPT_PATH.",
  );
}

const ts = loadTypeScript();

function walk(dir) {
  const files = [];
  for (const entry of readdirSync(dir)) {
    const path = join(dir, entry);
    const stat = statSync(path);
    if (stat.isDirectory()) files.push(...walk(path));
    else if (stat.isFile() && path.endsWith(".ts") && !path.endsWith(".test.ts")) files.push(path);
  }
  return files;
}

function moduleId(file) {
  return relative(root, file).split(sep).join("/");
}

const sourceFiles = [
  ...walk(join(root, "packages", "syntax", "src")),
  ...walk(join(root, "packages", "compiler", "src")),
].sort();

const sourceHashBuilder = createHash("sha256");
const modules = [];
for (const file of sourceFiles) {
  const id = moduleId(file);
  const source = normalizeText(readFileSync(file, "utf8"));
  sourceHashBuilder.update(id);
  sourceHashBuilder.update("\0");
  sourceHashBuilder.update(source);
  sourceHashBuilder.update("\0");
  const result = ts.transpileModule(source, {
    fileName: file,
    reportDiagnostics: true,
    compilerOptions: {
      target: ts.ScriptTarget.ES2022,
      module: ts.ModuleKind.CommonJS,
      moduleResolution: ts.ModuleResolutionKind.Node10,
      esModuleInterop: true,
      skipLibCheck: true,
      sourceMap: false,
      inlineSourceMap: false,
      removeComments: false,
      rewriteRelativeImportExtensions: true,
      newLine: ts.NewLineKind.LineFeed,
    },
  });
  const diagnostics = result.diagnostics ?? [];
  if (diagnostics.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) {
    const message = ts.formatDiagnosticsWithColorAndContext(diagnostics, {
      getCanonicalFileName: (name) => name,
      getCurrentDirectory: () => root,
      getNewLine: () => "\n",
    });
    throw new Error(message);
  }
  modules.push(
    `${JSON.stringify(id)}: function (module, exports, require, __filename, __dirname) {\n${normalizeText(result.outputText)}\n}`,
  );
}

const sourceHash = sourceHashBuilder.digest("hex");
const generatorHash = sha256(normalizeText(readFileSync(scriptPath, "utf8")));

const output = `"use strict";
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
// WRN editor compiler source hash: ${sourceHash}
// WRN editor compiler generator hash: ${generatorHash}
// Generated with TypeScript: ${ts.version}
const __nodeRequire = require;
const __path = __nodeRequire("node:path");
const __modules = {
${modules.join(",\n")}
};
const __aliases = {
  "@wrnexus/syntax": "packages/syntax/src/index.ts",
  "@wrnexus/syntax/parser": "packages/syntax/src/parser.ts",
  "@wrnexus/syntax/tokenizer": "packages/syntax/src/tokenizer.ts",
  "@wrnexus/syntax/types": "packages/syntax/src/types.ts",
  "@wrnexus/syntax/diagnostics": "packages/syntax/src/diagnostics.ts",
  "@wrnexus/syntax/spec": "packages/syntax/src/spec.ts"
};
const __cache = Object.create(null);
function __normalize(id) {
  const normalized = id.split("\\\\").join("/");
  return normalized.startsWith("./") ? normalized.slice(2) : normalized;
}
function __resolve(request, parent) {
  if (__aliases[request]) return __aliases[request];
  if (!request.startsWith(".")) return null;
  const base = __normalize(__path.posix.join(__path.posix.dirname(parent), request));
  const candidates = [
    base,
    base.endsWith(".js") ? base.slice(0, -3) + ".ts" : base,
    base.endsWith(".ts") ? base : base + ".ts",
    (base.endsWith("/") ? base.slice(0, -1) : base) + "/index.ts"
  ];
  for (const candidate of candidates) {
    if (__modules[candidate]) return candidate;
  }
  return null;
}
function __load(id) {
  if (__cache[id]) return __cache[id].exports;
  const factory = __modules[id];
  if (!factory) throw new Error("WRN editor compiler module not found: " + id);
  const module = { exports: {} };
  __cache[id] = module;
  const localRequire = (request) => {
    const resolved = __resolve(request, id);
    return resolved ? __load(resolved) : __nodeRequire(request);
  };
  factory(module, module.exports, localRequire, id, __path.posix.dirname(id));
  return module.exports;
}
module.exports = __load("packages/compiler/src/index.ts");
`;

const destination = join(root, "editors", "vscode", "src", "compiler.cjs");
if (process.argv.includes("--check")) {
  const existing = existsSync(destination) ? normalizeText(readFileSync(destination, "utf8")) : "";
  const embeddedSourceHash = existing.match(
    /^\/\/ WRN editor compiler source hash: ([a-f0-9]{64})$/m,
  )?.[1];
  const embeddedGeneratorHash = existing.match(
    /^\/\/ WRN editor compiler generator hash: ([a-f0-9]{64})$/m,
  )?.[1];
  const embeddedTypeScriptVersion = existing.match(/^\/\/ Generated with TypeScript: (.+)$/m)?.[1];

  const hashesMatch = embeddedSourceHash === sourceHash && embeddedGeneratorHash === generatorHash;
  const exactMatch = existing === normalizeText(output);
  const compatibleCompilerDifference =
    hashesMatch && Boolean(embeddedTypeScriptVersion) && embeddedTypeScriptVersion !== ts.version;

  if (!hashesMatch || (!exactMatch && !compatibleCompilerDifference)) {
    throw new Error(
      `${relative(root, destination)} is stale. Run bun run --cwd editors/vscode build and commit the result.`,
    );
  }

  const versionDetail = exactMatch
    ? `TypeScript ${ts.version}`
    : `source-compatible output generated with TypeScript ${embeddedTypeScriptVersion}; current TypeScript is ${ts.version}`;
  process.stdout.write(
    `Verified ${relative(root, destination)} matches ${sourceFiles.length} TypeScript modules (${versionDetail}).\n`,
  );
} else {
  writeFileSync(destination, output, "utf8");
  process.stdout.write(
    `Built ${relative(root, destination)} from ${sourceFiles.length} TypeScript modules with TypeScript ${ts.version}.\n`,
  );
}
