#!/usr/bin/env node

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import ts from "typescript";

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const SOURCE_PATH = path.join(ROOT, "src/public-surface/source.ts");
const DEFAULT_TS_OUT = path.join(ROOT, "src/public-surface/generated-manifest.ts");
const DEFAULT_JSON_OUT = path.join(ROOT, "src/public-surface/generated-manifest.json");

function parseArgs(argv) {
  const options = {
    tsOut: DEFAULT_TS_OUT,
    jsonOut: DEFAULT_JSON_OUT
  };

  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    if (arg === "--out") {
      options.tsOut = path.resolve(ROOT, argv[index + 1]);
      index += 1;
      continue;
    }
    if (arg?.startsWith("--out=")) {
      options.tsOut = path.resolve(ROOT, arg.split("=", 2)[1]);
      continue;
    }
    if (arg === "--json-out") {
      options.jsonOut = path.resolve(ROOT, argv[index + 1]);
      index += 1;
      continue;
    }
    if (arg?.startsWith("--json-out=")) {
      options.jsonOut = path.resolve(ROOT, arg.split("=", 2)[1]);
      continue;
    }
    if (arg === "--help") {
      console.log([
        "Usage: node scripts/generate-public-surface-manifest.mjs [options]",
        "",
        "Options:",
        "  --out <path>       Write the generated TypeScript manifest module",
        "  --json-out <path>  Write the generated JSON manifest snapshot",
        "  --help             Show help"
      ].join("\n"));
      process.exit(0);
    }
    throw new Error(`Unknown option: ${arg}`);
  }

  return options;
}

function relativeImportPath(fromPath, toPath) {
  const fromDir = path.dirname(fromPath);
  const relativePath = path.relative(fromDir, toPath).replace(/\.[^.]+$/, "");
  return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
}

function writeFile(targetPath, content) {
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
  fs.writeFileSync(targetPath, content, "utf8");
}

async function loadSourceModule() {
  const sourceText = fs.readFileSync(SOURCE_PATH, "utf8");
  const transpiled = ts.transpileModule(sourceText, {
    compilerOptions: {
      module: ts.ModuleKind.ES2022,
      target: ts.ScriptTarget.ES2022
    },
    fileName: SOURCE_PATH
  });

  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "odb-public-surface-"));
  const tempFile = path.join(tempDir, "source.mjs");
  writeFile(tempFile, transpiled.outputText);

  try {
    return await import(`${pathToFileURL(tempFile).href}?t=${Date.now()}`);
  } finally {
    fs.rmSync(tempDir, { recursive: true, force: true });
  }
}

function renderManifestModule(manifest, outPath) {
  const typeImportPath = relativeImportPath(outPath, SOURCE_PATH);
  const payload = JSON.stringify(manifest, null, 2);
  return [
    "/**",
    " * Generated by scripts/generate-public-surface-manifest.mjs.",
    " * Do not edit manually.",
    " */",
    "",
    `import type {`,
    "  CommandHelpDetail,",
    "  PublicSurfaceCliCommandGroupDefinition,",
    "  PublicSurfaceCliCommandName,",
    "  PublicSurfaceFlagName,",
    "  PublicSurfaceManifest,",
    "  ToolSurfaceEntry",
    `} from "${typeImportPath}";`,
    "",
    `export const PUBLIC_SURFACE_MANIFEST_SCHEMA_VERSION = ${JSON.stringify(manifest.schemaVersion)} as const;`,
    `export const PUBLIC_SURFACE_MANIFEST_GENERATED_AT = ${JSON.stringify(manifest.generatedAt)} as const;`,
    "",
    `export const PUBLIC_SURFACE_MANIFEST = ${payload} satisfies PublicSurfaceManifest;`,
    "",
    "export const VALID_FLAGS = PUBLIC_SURFACE_MANIFEST.cli.flags.map((flag) => flag.name) as PublicSurfaceFlagName[];",
    "export const VALID_EQUALS_FLAGS = [...PUBLIC_SURFACE_MANIFEST.cli.equalsFlags] as PublicSurfaceFlagName[];",
    "export const CLI_COMMANDS = PUBLIC_SURFACE_MANIFEST.cli.commands.map((command) => command.name) as PublicSurfaceCliCommandName[];",
    "",
    "export const CLI_COMMAND_HELP_DETAILS = Object.fromEntries(",
    "  PUBLIC_SURFACE_MANIFEST.cli.commands.map((command) => [",
    "    command.name,",
    "    {",
    "      description: command.description,",
    "      usage: command.usage,",
    "      flags: [...command.flags],",
    "      examples: [...command.examples],",
    "      notes: [...command.notes]",
    "    } satisfies CommandHelpDetail",
    "  ] as const)",
    ") as unknown as Record<PublicSurfaceCliCommandName, CommandHelpDetail>;",
    "",
    "const COMMANDS_BY_GROUP = Object.fromEntries(",
    "  PUBLIC_SURFACE_MANIFEST.cli.groups.map((group) => [",
    "    group.id,",
    "    PUBLIC_SURFACE_MANIFEST.cli.commands",
    "      .filter((command) => command.groupId === group.id)",
    "      .map((command) => ({",
    "        name: command.name,",
    "        description: command.description,",
    "        usage: command.usage,",
    "        flags: [...command.flags],",
    "        examples: [...command.examples],",
    "        notes: [...command.notes]",
    "      }))",
    "  ] as const)",
    ") as Record<string, PublicSurfaceCliCommandGroupDefinition['commands']>;",
    "",
    "export const PUBLIC_CLI_COMMAND_GROUPS = PUBLIC_SURFACE_MANIFEST.cli.groups.map((group) => ({",
    "  id: group.id,",
    "  title: group.title,",
    "  summary: group.summary,",
    "  commands: COMMANDS_BY_GROUP[group.id]",
    "})) as readonly PublicSurfaceCliCommandGroupDefinition[];",
    "",
    "export const TOOL_SURFACE_ENTRIES = PUBLIC_SURFACE_MANIFEST.tools.entries.map((entry) => ({",
    "  name: entry.name,",
    "  description: entry.description,",
    "  ...(entry.cliEquivalent ? { cliEquivalent: entry.cliEquivalent } : {}),",
    "  example: entry.example,",
    "  ...(entry.notes ? { notes: [...entry.notes] } : {})",
    "})) as readonly ToolSurfaceEntry[];",
    ""
  ].join("\n");
}

async function buildManifest() {
  const sourceModule = await loadSourceModule();
  if (typeof sourceModule.buildPublicSurfaceManifest !== "function") {
    throw new Error("source.ts must export buildPublicSurfaceManifest().");
  }
  return sourceModule.buildPublicSurfaceManifest();
}

export async function runGeneratePublicSurfaceManifest(options) {
  const manifest = await buildManifest();
  writeFile(options.tsOut, renderManifestModule(manifest, options.tsOut));
  writeFile(options.jsonOut, `${JSON.stringify(manifest, null, 2)}\n`);
  return manifest;
}

if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
  await runGeneratePublicSurfaceManifest(parseArgs(process.argv.slice(2)));
}
