/**
 * ESM hooks for running integration tests without `tsx`.
 *
 * The sandbox blocks esbuild's pipe-based service-worker spawn (EPERM), so
 * this loader performs the TypeScript transform in-process with the locally
 * installed `typescript` package (ts.transpileModule — pure JS, no child
 * process), resolves the tsconfig `@/*` path alias, probes extensions for
 * extensionless relative imports, and falls back to raw package-subpath
 * files (`next/headers` etc.) that lack an exports map.
 *
 * Usage:  node --import ./tests/integration/tsconfig-paths-loader.mjs tests/integration/<suite>.test.ts
 */
import { pathToFileURL, fileURLToPath } from "node:url";
import { existsSync, readFileSync, statSync } from "node:fs";
import { dirname, join, resolve as pathResolve } from "node:path";
import ts from "typescript";

const ROOT = pathResolve(process.cwd());

const EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];

function isFile(p) {
  try {
    return statSync(p).isFile();
  } catch {
    return false;
  }
}

/** Resolve a specifier target, probing extensions and index files. */
function probe(base) {
  if (isFile(base)) return base;
  for (const ext of EXTENSIONS) {
    if (isFile(base + ext)) return base + ext;
  }
  for (const ext of EXTENSIONS) {
    if (isFile(join(base, "index" + ext))) return join(base, "index" + ext);
  }
  return null;
}

/** Fallback for bare package subpaths (e.g. `next/headers`) that lack an exports map. */
function probePackage(spec) {
  const base = pathResolve(ROOT, "node_modules", spec);
  for (const ext of [".js", ".mjs", ".cjs", ".json"]) {
    if (isFile(base + ext)) return base + ext;
  }
  if (isFile(base)) return base;
  return null;
}

export async function resolve(specifier, context, nextResolve) {
  if (specifier.startsWith("@/")) {
    const target = probe(pathResolve(ROOT, "src", specifier.slice(2)));
    if (target) return { url: pathToFileURL(target).href, shortCircuit: true };
  } else if (
    (specifier.startsWith("./") || specifier.startsWith("../")) &&
    context.parentURL
  ) {
    const parent = fileURLToPath(context.parentURL);
    const target = probe(pathResolve(dirname(parent), specifier));
    if (target) return { url: pathToFileURL(target).href, shortCircuit: true };
  } else if (!specifier.startsWith("node:")) {
    try {
      return await nextResolve(specifier, context);
    } catch (error) {
      const target = probePackage(specifier);
      if (target) return { url: pathToFileURL(target).href, shortCircuit: true };
      throw error;
    }
  }
  return nextResolve(specifier, context);
}

export async function load(url, context, nextLoad) {
  if (url.startsWith("file:")) {
    const filePath = fileURLToPath(url);
    if (/\.(ts|tsx|mts|cts)$/.test(filePath)) {
      const source = readFileSync(filePath, "utf8");
      const out = ts.transpileModule(source, {
        fileName: filePath,
        reportDiagnostics: false,
        compilerOptions: {
          module: ts.ModuleKind.ESNext,
          moduleResolution: ts.ModuleResolutionKind.Bundler,
          target: ts.ScriptTarget.ES2022,
          isolatedModules: true,
          esModuleInterop: true,
          allowJs: true,
          jsx: ts.JsxEmit.ReactJSX,
          verbatimModuleSyntax: false,
        },
      });
      return { format: "module", source: out.outputText, shortCircuit: true };
    }
  }
  return nextLoad(url, context);
}
