import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { buildRouter, createRouteManifest, nameRoutes } from "@wrnexus/router";
import { createPluginRunner, discoverPlugins } from "@wrnexus/plugin";
import { loadAppConfig } from "@wrnexus/styles";
import { uiComponentsDir } from "@wrnexus/ui/registry";
import { parse } from "@wrnexus/syntax";

export type InspectTarget =
  "packages" | "plugins" | "routes" | "assets" | "runtimes" | "styles" | "migrations" | "bundle";

export function inspectComponent(appRoot: string, requestedName: string): unknown {
  const root = resolve(appRoot);
  const bundledUiDir = uiComponentsDir();
  const router = buildRouter(join(root, "app"), {
    componentDirs: [bundledUiDir, join(bundledUiDir, "..", "styles")],
  });
  const component = router.components.find(
    (candidate) => candidate.name.toLowerCase() === requestedName.toLowerCase(),
  );
  if (!component) throw new Error(`Component not found: ${requestedName}`);
  const ast = parse(readFileSync(component.file, "utf8"));
  return {
    name: ast.name,
    file: relative(root, component.file).replace(/\\/g, "/"),
    props: ast.props.map(({ name, valueType, required, default: defaultValue }) => ({
      name,
      type: valueType ?? "unknown",
      required,
      ...(defaultValue === undefined || defaultValue === "undefined"
        ? {}
        : { default: defaultValue }),
    })),
    outputs: ast.outputs.map(({ name, payload }) => ({ name, payload: payload ?? null })),
    functions: ast.runtimeFunctions.map(({ name, runtime, async, parameters, returnType }) => ({
      name,
      runtime,
      async,
      parameters,
      returnType: returnType ?? "unknown",
    })),
  };
}

function json(path: string): Record<string, any> | null {
  try {
    return JSON.parse(readFileSync(path, "utf8")) as Record<string, any>;
  } catch {
    return null;
  }
}
function workspaceRoot(start: string): string {
  let current = resolve(start);
  while (true) {
    if (json(join(current, "package.json"))?.workspaces) return current;
    const parent = dirname(current);
    if (parent === current) return resolve(start);
    current = parent;
  }
}
function packageRows(root: string) {
  const dirs = [join(root, "packages"), join(root, "services")];
  const rows: Array<Record<string, unknown>> = [];
  for (const dir of dirs) {
    if (!existsSync(dir)) continue;
    for (const name of readdirSync(dir)) {
      const path = join(dir, name);
      if (!statSync(path).isDirectory()) continue;
      const pkg = json(join(path, "package.json"));
      if (!pkg?.name) continue;
      rows.push({
        name: pkg.name,
        version: pkg.version,
        private: pkg.private === true,
        path: relative(root, path).replace(/\\/g, "/"),
        plugin: !!pkg.wrnexus?.plugin,
      });
    }
  }
  return rows.sort((a, b) => String(a.name).localeCompare(String(b.name)));
}
export async function inspectProject(appRoot: string, target: InspectTarget): Promise<unknown> {
  const root = resolve(appRoot);
  const workspace = workspaceRoot(root);
  if (target === "packages") return packageRows(workspace);
  if (target === "bundle") {
    const report = join(root, "dist", "build-report.json");
    if (!existsSync(report)) throw new Error("Run `wrnexus build` before inspecting the bundle.");
    return json(report);
  }
  const config = await loadAppConfig(root);
  const input = await discoverPlugins(root, config.plugins, {
    includeDevDependencies: true,
    strict: true,
  });
  const runner = createPluginRunner(input, {
    root,
    mode: "development",
    command: "dev",
    metadata: new Map(),
    warn: () => {},
  });
  await runner.configure(config as Record<string, unknown>);
  await runner.configResolved(config as Readonly<Record<string, unknown>>);
  const contributions = await runner.contributions();
  if (target === "plugins")
    return runner.plugins.map((plugin) => ({
      name: plugin.name,
      version: plugin.version,
      enforce: plugin.enforce ?? "normal",
    }));
  if (target === "assets")
    return contributions.assets.map(({ id, publicPath, contentType, immutable }) => ({
      id,
      publicPath,
      contentType,
      immutable: immutable ?? false,
    }));
  if (target === "runtimes")
    return contributions.clientRuntimes.map(
      ({ id, publicPath, type, load, singleton, bundle }) => ({
        id,
        publicPath,
        type,
        load,
        singleton,
        bundle,
      }),
    );
  if (target === "styles")
    return contributions.styles.map(({ id, entry, source, order }) => ({
      id,
      entry,
      source,
      order: order ?? "normal",
    }));
  if (target === "migrations")
    return contributions.migrations.map(({ id, entry, source, database }) => ({
      id,
      entry,
      inline: source !== undefined,
      database: database ?? "default",
    }));
  const router = buildRouter(join(root, "app"), {
    componentDirs: [
      uiComponentsDir(),
      join(uiComponentsDir(), "..", "styles"),
      ...contributions.componentDirs,
    ],
    externalRoutes: contributions.routes,
    middlewareFiles: contributions.middleware,
  });
  return {
    pages: createRouteManifest(nameRoutes(router.pages)),
    api: createRouteManifest(nameRoutes(router.api)),
    realtime: createRouteManifest(nameRoutes(router.realtime)),
    middleware: router.middlewareFiles.map((file) => relative(root, file).replace(/\\/g, "/")),
    components: router.components.map((component) => ({
      ...component,
      file: relative(root, component.file).replace(/\\/g, "/"),
    })),
  };
}

export async function runInspect(
  appRoot: string,
  targetArg?: string,
  args: string[] = [],
): Promise<void> {
  const target = (targetArg ?? "plugins") as InspectTarget;
  if (
    ![
      "packages",
      "plugins",
      "routes",
      "assets",
      "runtimes",
      "styles",
      "migrations",
      "bundle",
    ].includes(target)
  )
    throw new Error(`Unknown inspect target: ${target}`);
  const value = await inspectProject(appRoot, target);
  if (args.includes("--json")) {
    console.log(JSON.stringify(value, null, 2));
    return;
  }
  console.log(`WRNexus ${target}\n`);
  if (Array.isArray(value))
    for (const row of value)
      console.log(
        `  ${Object.entries(row as Record<string, unknown>)
          .map(([key, item]) => `${key}=${String(item)}`)
          .join("  ")}`,
      );
  else console.log(JSON.stringify(value, null, 2));
}

export function runInspectComponent(appRoot: string, name: string, args: string[] = []): void {
  const value = inspectComponent(appRoot, name);
  if (args.includes("--json")) console.log(JSON.stringify(value, null, 2));
  else {
    console.log(`WRNexus component ${name}\n`);
    console.log(JSON.stringify(value, null, 2));
  }
}
