import { extname as pathExtname } from "node:path";
import { fileURLToPath } from "node:url";

export const NODE_TYPESCRIPT_SUPPORT_RANGE = "^20.19.0 || >=22.18.0";

const TS_MODULE_EXTENSIONS = new Set([".ts", ".mts", ".cts"]);

function normalizeModuleSpecifierPath(specifier: string): string {
  if (!specifier.startsWith("file:")) return specifier;

  try {
    return fileURLToPath(specifier);
  } catch {
    return specifier;
  }
}

export function isTypeScriptModuleSpecifier(specifier: string): boolean {
  const ext = pathExtname(normalizeModuleSpecifierPath(specifier)).toLowerCase();
  return TS_MODULE_EXTENSIONS.has(ext);
}

function isUnknownFileExtensionError(err: unknown): boolean {
  if ((err as { code?: unknown })?.code === "ERR_UNKNOWN_FILE_EXTENSION") return true;

  const message = (err as { message?: unknown })?.message;
  return typeof message === "string" && /unknown(?: or unsupported)? file extension/i.test(message);
}

function getErrorMessage(err: unknown): string {
  if (err instanceof Error) return err.message;
  return String(err);
}

/**
 * Returns a complete replacement string suitable for `Error.message` assignment
 * (includes the original error message + appended hint), or `null` when the
 * error is unrelated to TS module loading. Callers should overwrite, not append.
 */
export function getUnsupportedTypeScriptModuleLoadHintForError(
  err: unknown,
  specifier: string,
  nodeVersion: string = process.version,
): string | null {
  if (!isTypeScriptModuleSpecifier(specifier) || !isUnknownFileExtensionError(err)) {
    return null;
  }

  return `${getErrorMessage(err)}\n\nTypeScript config files require Node.js ${NODE_TYPESCRIPT_SUPPORT_RANGE}.\nDetected Node.js ${nodeVersion}.\nPlease upgrade Node.js or use a JSON config file instead.`;
}
