#!/usr/bin/env node

// src/index.ts
import { Command } from "commander";

// src/project.ts
import { resolve } from "path";
function resolveProjectPath(project) {
  return resolve(process.cwd(), project ?? ".");
}

// src/coreShim.ts
async function loadCore() {
  try {
    return await import("./src-TB5KSAZH.js");
  } catch {
    return await import(new URL("./vendor/core/index.js", import.meta.url).toString());
  }
}

// src/commands/init.ts
function registerInitCommand(program2) {
  program2.command("init").description("Create ArchLens config, ignore file, and output directory.").option("--project <path>", "project directory", ".").option("--force", "overwrite existing ArchLens config files").action(async (options) => {
    const { initializeProject } = await loadCore();
    const projectPath = resolveProjectPath(options.project);
    const result = await initializeProject(projectPath, { force: options.force });
    for (const item of result.created) {
      console.log(`created ${item}`);
    }
    for (const item of result.skipped) {
      console.log(`kept existing ${item}`);
    }
    console.log(`ArchLens initialized at ${result.outputDir}`);
  });
}

// src/commands/scan.ts
function registerScanCommand(program2) {
  program2.command("scan").description("Scan the current project and generate ArchLens output.").option("--project <path>", "project directory", ".").option("--workspace", "scan a multi-service workspace and generate workspace outputs").action(async (options) => {
    const projectPath = resolveProjectPath(options.project);
    console.log(`Scanning ${projectPath}${options.workspace ? " (workspace mode)" : ""}...`);
    try {
      const { scanProject, scanWorkspace } = await loadCore();
      if (options.workspace) {
        const result2 = await scanWorkspace(projectPath);
        console.log(`Workspace scanned: ${result2.workspace.services.length} services.`);
        console.log(`Workspace output written to .archlens/ (workspace-graph.json + docs/diagrams).`);
        console.log(`View results: archtechlens dashboard --project ${options.project ?? "."}`);
        return;
      }
      const result = await scanProject(projectPath);
      console.log(`Scanned ${result.summary.filesScanned} files for ${result.summary.projectName}.`);
      console.log(
        `Detected: ${result.summary.languages.join(", ") || "no languages"}; ${result.summary.frameworks.join(", ") || "no frameworks"}.`
      );
      console.log(
        `Generated ${result.summary.nodes} nodes, ${result.summary.edges} edges, ${result.summary.tables} tables, ${result.summary.routes} routes.`
      );
      if (result.review) {
        console.log(`Code review: ${result.review.summary.totalIssues} issues written to ${result.outputDir}/review/.`);
      }
      console.log(`Output written to ${result.outputDir}/`);
      if (result.summary.modules.length === 0 && result.summary.routes === 0) {
        console.log(
          "Tip: scan the app package folder (e.g. apps/api or backend/) if this is a monorepo, or ensure routes use Express/Fastify/NestJS patterns."
        );
      }
      console.log(`View results: archtechlens dashboard --project ${options.project ?? "."}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      console.error(`Scan failed: ${message}`);
      process.exitCode = 1;
    }
  });
}

// src/commands/diagram.ts
var diagramTypes = /* @__PURE__ */ new Set(["context", "container", "erd", "data-flow"]);
var formats = /* @__PURE__ */ new Set(["mermaid", "json"]);
function registerDiagramCommand(program2) {
  program2.command("diagram").description("Generate a Mermaid or JSON diagram from existing ArchLens output.").option("--project <path>", "project directory", ".").option("--type <type>", "context, container, erd, or data-flow", "context").option("--format <format>", "mermaid or json", "mermaid").option("--print", "print diagram content to stdout").action(async (options) => {
    if (!diagramTypes.has(options.type)) {
      throw new Error(`Unsupported diagram type: ${options.type}`);
    }
    if (!formats.has(options.format)) {
      throw new Error(`Unsupported diagram format: ${options.format}`);
    }
    const { renderGeneratedDiagram } = await loadCore();
    const result = await renderGeneratedDiagram(resolveProjectPath(options.project), options.type, options.format);
    if (options.print) {
      console.log(result.content);
    }
    console.log(`Diagram written to ${result.outputPath}`);
  });
}

// src/commands/export.ts
var formats2 = /* @__PURE__ */ new Set(["markdown", "html", "json"]);
function registerExportCommand(program2) {
  program2.command("export").description("Export generated ArchLens docs and graphs.").option("--project <path>", "project directory", ".").option("--format <format>", "markdown, html, or json", "markdown").action(async (options) => {
    if (!formats2.has(options.format)) {
      throw new Error(`Unsupported export format: ${options.format}`);
    }
    const { exportGenerated } = await loadCore();
    const result = await exportGenerated(resolveProjectPath(options.project), options.format);
    console.log(`Exported ${result.files.length} file(s) to ${result.outputDir}`);
  });
}

// src/commands/dashboard.ts
import { promises as fs } from "fs";
import { createReadStream } from "fs";
import http from "http";
import { dirname, extname, join, normalize, resolve as resolve2 } from "path";
import { fileURLToPath } from "url";
import { spawn } from "child_process";
function cliPackageRoot() {
  const currentDir = dirname(fileURLToPath(import.meta.url));
  return resolve2(currentDir, "..");
}
function repoRootFromCli() {
  return resolve2(cliPackageRoot(), "../..");
}
function contentType(filePath) {
  const extension = extname(filePath);
  if (extension === ".html") {
    return "text/html; charset=utf-8";
  }
  if (extension === ".js") {
    return "text/javascript; charset=utf-8";
  }
  if (extension === ".css") {
    return "text/css; charset=utf-8";
  }
  if (extension === ".json") {
    return "application/json; charset=utf-8";
  }
  if (extension === ".md") {
    return "text/markdown; charset=utf-8";
  }
  if (extension === ".svg") {
    return "image/svg+xml";
  }
  return "text/plain; charset=utf-8";
}
async function pathExists(filePath) {
  try {
    await fs.access(filePath);
    return true;
  } catch {
    return false;
  }
}
function serveFile(response, filePath) {
  response.setHeader("Content-Type", contentType(filePath));
  createReadStream(filePath).on("error", () => {
    response.statusCode = 500;
    response.end("Unable to read file");
  }).pipe(response);
}
async function startStaticDashboard(options) {
  const { dashboardDist, projectDir, host, port } = options;
  const outputDir = join(projectDir, ".archlens");
  const indexPath = join(dashboardDist, "index.html");
  const server = http.createServer(async (request, response) => {
    const url = new URL(request.url ?? "/", `http://${host}:${port}`);
    if (url.pathname.startsWith("/archlens-data/")) {
      const relativePath = decodeURIComponent(url.pathname.replace(/^\/archlens-data\//, ""));
      const filePath2 = normalize(join(outputDir, relativePath));
      if (!filePath2.startsWith(outputDir)) {
        response.statusCode = 403;
        response.end("Forbidden");
        return;
      }
      if (!await pathExists(filePath2)) {
        response.statusCode = 404;
        response.setHeader("Content-Type", "application/json; charset=utf-8");
        response.end(JSON.stringify({ error: "File not found", path: relativePath }));
        return;
      }
      serveFile(response, filePath2);
      return;
    }
    const requestedPath = url.pathname === "/" ? "index.html" : url.pathname.replace(/^\//, "");
    const staticPath = normalize(join(dashboardDist, requestedPath));
    const filePath = staticPath.startsWith(dashboardDist) && await pathExists(staticPath) ? staticPath : indexPath;
    serveFile(response, filePath);
  });
  await new Promise((resolveListen, rejectListen) => {
    server.once("error", rejectListen);
    server.listen(port, host, () => resolveListen());
  });
  console.log(`Dashboard URL: http://${host}:${port}`);
}
function registerDashboardCommand(program2) {
  program2.command("dashboard").description("Start the local ArchLens dashboard.").option("--project <path>", "project directory", ".").option("--port <port>", "dashboard port").option("--host <host>", "dashboard host").action(async (options) => {
    const projectPath = resolveProjectPath(options.project);
    const { loadConfig } = await loadCore();
    const config = await loadConfig(projectPath);
    const port = Number(options.port ?? config.dashboard.port);
    const host = options.host ?? config.dashboard.host;
    const packageRoot = cliPackageRoot();
    const dashboardDist = resolve2(packageRoot, "dashboard-dist");
    const dashboardDir = resolve2(repoRootFromCli(), "apps/dashboard");
    const markerPath = resolve2(dashboardDir, ".archlens-dashboard-project");
    const viteBin = resolve2(dashboardDir, "node_modules/vite/bin/vite.js");
    const outputDir = join(projectPath, ".archlens");
    const graphPath = join(outputDir, "architecture-graph.json");
    if (!await pathExists(graphPath)) {
      console.error(`No scan output found at ${graphPath}`);
      console.error(`Run \`archtechlens scan --project ${options.project ?? "."}\` first, then start the dashboard.`);
      process.exitCode = 1;
      return;
    }
    console.log(`Starting ArchTechLens dashboard for ${projectPath}`);
    if (await pathExists(dashboardDist)) {
      await startStaticDashboard({
        dashboardDist,
        projectDir: projectPath,
        host,
        port
      });
      return;
    }
    if (!await pathExists(viteBin)) {
      throw new Error("Dashboard dependencies are not installed. Run pnpm install before starting the dashboard.");
    }
    await fs.writeFile(markerPath, projectPath, "utf8");
    console.log(`Dashboard URL: http://${host}:${port}`);
    const child = spawn(
      process.execPath,
      [viteBin, "--host", host, "--port", String(port), "--clearScreen", "false"],
      {
        cwd: dashboardDir,
        stdio: "inherit"
      }
    );
    child.on("error", async (error) => {
      await fs.rm(markerPath, { force: true });
      console.error(`Failed to start dashboard: ${error.message}`);
      process.exitCode = 1;
    });
    child.on("exit", async (code) => {
      await fs.rm(markerPath, { force: true });
      process.exitCode = code ?? 0;
    });
  });
}

// src/commands/validate.ts
function registerValidateCommand(program2) {
  program2.command("validate").description("Validate generated ArchLens graph files.").option("--project <path>", "project directory", ".").action(async (options) => {
    const { validateGeneratedOutput } = await loadCore();
    const result = await validateGeneratedOutput(resolveProjectPath(options.project));
    if (result.issues.length === 0) {
      console.log("ArchLens output is valid.");
      return;
    }
    for (const issue of result.issues) {
      const location = issue.path ? ` (${issue.path})` : "";
      console.log(`${issue.severity.toUpperCase()}: ${issue.message}${location}`);
    }
    if (!result.valid) {
      process.exitCode = 1;
    }
  });
}

// src/commands/diff.ts
function registerDiffCommand(program2) {
  program2.command("diff").description("Compare two ArchLens graph snapshots and generate a diff report.").requiredOption("--from <path-or-json>", "source project, .archlens directory, graph JSON, or export JSON").requiredOption("--to <path-or-json>", "target project, .archlens directory, graph JSON, or export JSON").option("--project <path>", "project directory where the diff report should be written", ".").action(async (options) => {
    const { generateDiffReport } = await loadCore();
    const result = await generateDiffReport({
      from: options.from,
      to: options.to,
      cwd: process.cwd(),
      outputCwd: resolveProjectPath(options.project)
    });
    console.log(`Diff report written to ${result.outputPath}`);
    console.log(
      `Nodes +${result.report.addedNodes.length} -${result.report.removedNodes.length} changed ${result.report.changedNodes.length}; tables +${result.report.addedTables.length} -${result.report.removedTables.length} changed ${result.report.changedTables.length}; routes +${result.report.addedRoutes.length} -${result.report.removedRoutes.length} changed ${result.report.changedRoutes.length}.`
    );
  });
}

// src/commands/chat.ts
function registerChatCommand(program2) {
  program2.command("chat").description("Ask an offline graph-grounded question using generated ArchLens output.").option("--project <path>", "project directory", ".").argument("<question...>", "question to answer").action(async (question, options) => {
    const { chatWithGrounding } = await loadCore();
    const result = await chatWithGrounding({
      cwd: resolveProjectPath(options.project),
      question
    });
    console.log(result.answer);
  });
}

// src/commands/review.ts
var severityRank = {
  blocker: 5,
  critical: 4,
  major: 3,
  minor: 2,
  info: 1
};
function countBy(values) {
  return values.reduce((counts, value) => {
    counts[value] = (counts[value] ?? 0) + 1;
    return counts;
  }, {});
}
function filterReport(report, filters) {
  const issues = report.issues.filter((issue) => {
    if (filters.severity && issue.severity !== filters.severity) return false;
    if (filters.category && issue.category !== filters.category) return false;
    return true;
  });
  const bySeverity = countBy(issues.map((issue) => issue.severity));
  const byCategory = countBy(issues.map((issue) => issue.category));
  const byType = countBy(issues.map((issue) => issue.type));
  const byFile = countBy(issues.map((issue) => issue.filePath));
  const byModule = countBy(issues.map((issue) => issue.module ?? "root"));
  return {
    ...report,
    summary: {
      totalIssues: issues.length,
      bySeverity,
      byCategory,
      byType,
      byFile,
      byModule,
      blocker: bySeverity.blocker ?? 0,
      critical: bySeverity.critical ?? 0,
      major: bySeverity.major ?? 0,
      minor: bySeverity.minor ?? 0,
      info: bySeverity.info ?? 0
    },
    issues,
    files: report.files.filter((file) => Object.prototype.hasOwnProperty.call(byFile, file.filePath)),
    modules: report.modules.filter((module) => Object.prototype.hasOwnProperty.call(byModule, module.module))
  };
}
function formatIssue(issue) {
  const location = `${issue.filePath}${issue.line ? `:${issue.line}` : ""}`;
  return `- ${issue.severity} ${issue.category} ${issue.ruleId}: ${issue.title} (${location})`;
}
function printSummary(report, filtered, filters) {
  const activeFilters = [filters.severity ? `severity=${filters.severity}` : void 0, filters.category ? `category=${filters.category}` : void 0].filter(Boolean).join(", ");
  if (activeFilters) {
    console.log(`Code review complete: ${filtered.summary.totalIssues} filtered issues (${activeFilters}).`);
    console.log(`Full report contains ${report.summary.totalIssues} issues.`);
  } else {
    console.log(`Code review complete: ${report.summary.totalIssues} issues.`);
  }
  console.log(
    `Severity: blocker ${filtered.summary.blocker}, critical ${filtered.summary.critical}, major ${filtered.summary.major}, minor ${filtered.summary.minor}, info ${filtered.summary.info}.`
  );
  console.log(`Output written to .archlens/review/`);
  const topIssues = filtered.issues.filter((issue) => ["blocker", "critical"].includes(issue.severity)).slice(0, 10);
  if (topIssues.length > 0) {
    console.log("");
    console.log("Critical issues:");
    for (const issue of topIssues) {
      console.log(formatIssue(issue));
    }
  }
}
function hasFailingIssue(report, threshold) {
  return report.issues.some((issue) => severityRank[issue.severity] >= severityRank[threshold]);
}
function registerReviewCommand(program2) {
  program2.command("review").description("Run deterministic static code review and generate ArchTechLens review output.").option("--project <path>", "project directory", ".").option("--format <format>", "output format: summary, json, markdown", "summary").option("--severity <severity>", "only print issues with this severity").option("--category <category>", "only print issues in this category").option("--fail-on <severity>", "exit with code 1 if this severity threshold or higher is found").action(
    async (options) => {
      const projectPath = resolveProjectPath(options.project);
      const format = options.format ?? "summary";
      try {
        const { scanProject, loadConfig, generateCodeReviewReport, writeCodeReviewOutputs, renderCodeReviewMarkdown } = await loadCore();
        if (!["summary", "json", "markdown"].includes(format)) {
          throw new Error(`Unsupported review format "${format}". Use summary, json, or markdown.`);
        }
        if (format === "summary") {
          console.log(`Reviewing ${projectPath}...`);
        } else {
          console.error(`Reviewing ${projectPath}...`);
        }
        const scan = await scanProject(projectPath);
        let report = scan.review;
        if (!report) {
          const config = await loadConfig(projectPath);
          report = await generateCodeReviewReport({
            cwd: scan.cwd,
            project: scan.project,
            files: scan.files,
            source: scan.source,
            architectureGraph: scan.architectureGraph,
            databaseGraph: scan.databaseGraph,
            config,
            generatedAt: scan.architectureGraph.generatedAt
          });
          await writeCodeReviewOutputs(scan.cwd, scan.outputDir, report);
        }
        const filtered = filterReport(report, {
          severity: options.severity,
          category: options.category
        });
        if (format === "json") {
          console.log(JSON.stringify(filtered, null, 2));
        } else if (format === "markdown") {
          console.log(renderCodeReviewMarkdown(filtered));
        } else {
          printSummary(report, filtered, { severity: options.severity, category: options.category });
        }
        if (options.failOn && hasFailingIssue(report, options.failOn)) {
          process.exitCode = 1;
        }
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        console.error(`Review failed: ${message}`);
        process.exitCode = 1;
      }
    }
  );
}

// src/commands/db-usage.ts
import path from "path";
import { promises as fs2 } from "fs";
function registerDbUsageCommand(program2) {
  program2.command("db-usage").description("Map database table/column usage across the codebase.").option("--project <path>", "project directory", ".").option("--table <name>", "filter by table name").option("--field <name>", "filter by field (table.column)").option("--impact", "show impact analysis for a field").action(async (options) => {
    const projectPath = resolveProjectPath(options.project);
    const outputDir = path.join(projectPath, ".archlens");
    const reportPath = path.join(outputDir, "db-usage", "db-field-usage.json");
    try {
      const {
        buildDatabaseFieldUsageReport,
        filterDbUsageReport,
        formatDbUsageCliOutput,
        scanProject,
        writeDbUsageOutputs
      } = await loadCore();
      let report;
      try {
        const raw = await fs2.readFile(reportPath, "utf8");
        report = JSON.parse(raw);
      } catch {
        console.log("No existing db usage report. Running scan...");
        const scan = await scanProject(projectPath);
        if (scan.databaseGraph.tables.length === 0) {
          console.log("No database schema detected. Add Prisma/SQL/ORM schema files and scan again.");
          return;
        }
        report = await buildDatabaseFieldUsageReport({
          projectName: scan.project.name,
          rootPath: projectPath,
          databaseGraph: scan.databaseGraph,
          files: scan.files,
          routes: scan.source.routes,
          generatedAt: scan.architectureGraph.generatedAt
        });
        await writeDbUsageOutputs(projectPath, scan.outputDir, report);
      }
      const { report: filtered, impact } = filterDbUsageReport(report, {
        table: options.table,
        field: options.field,
        impact: options.impact
      });
      console.log(
        formatDbUsageCliOutput(filtered, {
          table: options.table,
          field: options.field,
          impact: options.impact
        }, impact)
      );
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      console.error(`db-usage failed: ${message}`);
      process.exitCode = 1;
    }
  });
}

// src/index.ts
var nodeMajor = Number(process.versions.node.split(".")[0] ?? 0);
if (nodeMajor < 22) {
  console.error(
    `ArchTechLens requires Node.js 22 or newer (current: ${process.versions.node}).
Upgrade Node, then run \`archtechlens scan\` again.`
  );
  process.exit(1);
}
var program = new Command();
program.name("archtechlens").description("Local-first architecture documentation and dashboard generator.").version("0.4.0");
registerInitCommand(program);
registerScanCommand(program);
registerDiagramCommand(program);
registerExportCommand(program);
registerDashboardCommand(program);
registerValidateCommand(program);
registerReviewCommand(program);
registerDbUsageCommand(program);
registerDiffCommand(program);
registerChatCommand(program);
await program.parseAsync(process.argv).catch((error) => {
  const message = error instanceof Error ? error.message : String(error);
  console.error(`ArchTechLens error: ${message}`);
  process.exitCode = 1;
});
