#!/usr/bin/env node
import { Command } from 'commander';
import { spawn } from 'child_process';
import path, { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
import { generateHTML, UnifiedReportSchema, ToolName, Severity } from '@aiready/core';

var GraphBuilder = class _GraphBuilder {
  constructor(rootDir = process.cwd()) {
    this.rootDir = rootDir;
    this.nodesMap = /* @__PURE__ */ new Map();
    this.edges = [];
    this.edgesSet = /* @__PURE__ */ new Set();
  }
  normalizeLabel(filePath) {
    try {
      return path.relative(this.rootDir, filePath);
    } catch {
      return filePath;
    }
  }
  extractReferencedPaths(message) {
    if (!message || typeof message !== "string") return [];
    const reAbs = /\/(?:[\w\-.]+\/)+[\w\-.]+\.(?:ts|tsx|js|jsx|py|java|go)/g;
    const reRel = /(?:\.\/|\.\.\/)(?:[\w\-.]+\/)+[\w\-.]+\.(?:ts|tsx|js|jsx|py|java|go)/g;
    const abs = message.match(reAbs) || [];
    const rel = message.match(reRel) || [];
    return abs.concat(rel);
  }
  getPackageGroup(fp) {
    if (!fp) return null;
    const parts = fp.split(path.sep);
    const pkgIdx = parts.indexOf("packages");
    if (pkgIdx >= 0 && parts.length > pkgIdx + 1)
      return `packages/${parts[pkgIdx + 1]}`;
    const landingIdx = parts.indexOf("landing");
    if (landingIdx >= 0) return "landing";
    const scriptsIdx = parts.indexOf("scripts");
    if (scriptsIdx >= 0) return "scripts";
    return parts.length > 1 ? parts[1] : parts[0];
  }
  addNode(file, title = "", value = 1) {
    if (!file) return;
    const id = path.resolve(this.rootDir, file);
    if (!this.nodesMap.has(id)) {
      const node = {
        id,
        path: id,
        label: this.normalizeLabel(id),
        title,
        size: value || 1
      };
      this.nodesMap.set(id, node);
    } else {
      const node = this.nodesMap.get(id);
      if (title && (!node.title || !node.title.includes(title))) {
        node.title = (node.title ? node.title + "\n" : "") + title;
      }
      if (value > (node.size || 0)) node.size = value;
    }
  }
  addEdge(from, to, type = "link") {
    if (!from || !to) return;
    const a = path.resolve(this.rootDir, from);
    const b = path.resolve(this.rootDir, to);
    if (a === b) return;
    const key = `${a}->${b}`;
    if (!this.edgesSet.has(key)) {
      this.edges.push({ source: a, target: b, type });
      this.edgesSet.add(key);
    }
  }
  /**
   * Build final GraphData
   */
  build() {
    const nodes = Array.from(this.nodesMap.values());
    const edges = this.edges.map(
      (e) => ({ source: e.source, target: e.target, type: e.type })
    );
    return {
      nodes,
      edges,
      clusters: [],
      issues: [],
      metadata: {
        timestamp: (/* @__PURE__ */ new Date()).toISOString(),
        totalFiles: nodes.length,
        totalDependencies: edges.length,
        analysisTypes: [],
        criticalIssues: 0,
        majorIssues: 0,
        minorIssues: 0,
        infoIssues: 0
      },
      truncated: {
        nodes: false,
        edges: false
      }
    };
  }
  /**
   * Static helper to build graph from an aiready report JSON (ports logic from tools/generate_from_report.cjs)
   */
  static buildFromReport(report, rootDir = process.cwd()) {
    const validation = UnifiedReportSchema.safeParse(report);
    if (!validation.success) {
      console.warn(
        "Visualizer: Report does not fully match UnifiedReportSchema, proceeding with best-effort parsing."
      );
    }
    const builder = new _GraphBuilder(rootDir);
    const fileIssues = /* @__PURE__ */ new Map();
    const rankSeverity = (s) => {
      if (!s) return null;
      const ss = String(s).toLowerCase();
      if (ss.includes("critical")) return Severity.Critical;
      if (ss.includes("major")) return Severity.Major;
      if (ss.includes("minor")) return Severity.Minor;
      if (ss.includes("info")) return Severity.Info;
      return null;
    };
    const bumpIssue = (file, sev) => {
      if (!file) return;
      const id = path.resolve(rootDir, file);
      if (!fileIssues.has(id))
        fileIssues.set(id, { count: 0, maxSeverity: null, duplicates: 0 });
      const rec = fileIssues.get(id);
      rec.count += 1;
      if (sev) {
        const order = {
          [Severity.Critical]: 3,
          [Severity.Major]: 2,
          [Severity.Minor]: 1,
          [Severity.Info]: 0
        };
        if (!rec.maxSeverity || order[sev] > order[rec.maxSeverity])
          rec.maxSeverity = sev;
      }
    };
    const getResults = (toolKey, legacyKey) => {
      const camelKey = toolKey.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
      const toolData = report[toolKey] || report[camelKey] || (legacyKey ? report[legacyKey] : void 0);
      if (!toolData) return [];
      if (Array.isArray(toolData)) return toolData;
      return toolData.results || toolData.issues || [];
    };
    const basenameMap = /* @__PURE__ */ new Map();
    const patternResults = getResults(ToolName.PatternDetect, "patterns");
    patternResults.forEach((p) => {
      const fileName = p.fileName || p.file;
      if (fileName) {
        const base = path.basename(fileName);
        if (!basenameMap.has(base)) basenameMap.set(base, /* @__PURE__ */ new Set());
        basenameMap.get(base).add(fileName);
      }
    });
    patternResults.forEach((entry) => {
      const file = entry.fileName || entry.file;
      if (!file) return;
      builder.addNode(
        file,
        `Issues: ${(entry.issues || []).length}`,
        entry.metrics && entry.metrics.tokenCost || 5
      );
      if ((entry.issues || []).length > 0) {
        (entry.issues || []).forEach((issue) => {
          const sev = rankSeverity(
            issue.severity || issue.severityLevel || null
          );
          bumpIssue(file, sev);
        });
      }
      (entry.issues || []).forEach((issue) => {
        const message = issue.message || "";
        const refs = builder.extractReferencedPaths(message);
        refs.forEach((ref) => {
          let target = ref;
          if (!path.isAbsolute(ref)) {
            target = path.resolve(path.dirname(file), ref);
          }
          builder.addNode(target, "Referenced file", 5);
          builder.addEdge(file, target, "reference");
        });
        const percMatch = (message.match(/(\d+)%/) || [])[1];
        const perc = percMatch ? parseInt(percMatch, 10) : null;
        const wantFuzzy = issue.type === "duplicate-pattern" || /similar/i.test(message) || perc && perc >= 50;
        if (wantFuzzy) {
          const fileGroup = builder.getPackageGroup(file);
          for (const [base, pathsSet] of basenameMap.entries()) {
            if (!message.includes(base) || base === path.basename(file))
              continue;
            for (const target of pathsSet) {
              const targetGroup = builder.getPackageGroup(target);
              if (fileGroup !== targetGroup && !(perc && perc >= 80)) continue;
              builder.addNode(target, "Fuzzy match", 5);
              builder.addEdge(file, target, "similarity");
            }
          }
        }
      });
    });
    const patternData = report[ToolName.PatternDetect] || report.patternDetect || report.patterns || {};
    const duplicates = (Array.isArray(patternData.duplicates) ? patternData.duplicates : null) || (patternData.summary && Array.isArray(patternData.summary.duplicates) ? patternData.summary.duplicates : null) || (Array.isArray(report.duplicates) ? report.duplicates : []);
    duplicates.forEach((dup) => {
      builder.addNode(dup.file1, "Similarity target", 5);
      builder.addNode(dup.file2, "Similarity target", 5);
      builder.addEdge(dup.file1, dup.file2, "similarity");
      const f1 = path.resolve(rootDir, dup.file1);
      const f2 = path.resolve(rootDir, dup.file2);
      if (!fileIssues.has(f1))
        fileIssues.set(f1, { count: 0, maxSeverity: null, duplicates: 0 });
      if (!fileIssues.has(f2))
        fileIssues.set(f2, { count: 0, maxSeverity: null, duplicates: 0 });
      fileIssues.get(f1).duplicates += 1;
      fileIssues.get(f2).duplicates += 1;
    });
    const contextResults = getResults(ToolName.ContextAnalyzer, "context");
    contextResults.forEach((ctx) => {
      const file = ctx.fileName || ctx.file;
      if (!file) return;
      builder.addNode(file, `Deps: ${ctx.dependencyCount || 0}`, 10);
      if (ctx.issues && Array.isArray(ctx.issues)) {
        ctx.issues.forEach((issue) => {
          const sev = rankSeverity(
            typeof issue === "string" ? ctx.severity : issue.severity || issue.severityLevel || null
          );
          bumpIssue(file, sev);
        });
      }
      (ctx.relatedFiles || []).forEach((rel) => {
        const resolvedRel = path.isAbsolute(rel) ? rel : path.resolve(path.dirname(file), rel);
        const keyA = `${path.resolve(builder.rootDir, file)}->${path.resolve(builder.rootDir, resolvedRel)}`;
        const keyB = `${path.resolve(builder.rootDir, resolvedRel)}->${path.resolve(builder.rootDir, file)}`;
        if (builder.edgesSet.has(keyA) || builder.edgesSet.has(keyB))
          return;
        builder.addNode(resolvedRel, "Related file", 5);
        const n = builder.nodesMap.get(
          path.resolve(builder.rootDir, resolvedRel)
        );
        if (n) n.size = (n.size || 1) + 2;
        try {
          builder.addEdge(file, resolvedRel, "related");
        } catch {
        }
      });
      const absoluteFile = path.resolve(builder.rootDir, file);
      const fileDir = path.dirname(absoluteFile);
      (ctx.dependencyList || []).forEach((dep) => {
        if (dep.startsWith(".") || dep.startsWith("/")) {
          const possiblePaths = [
            path.resolve(fileDir, dep),
            path.resolve(fileDir, dep + ".ts"),
            path.resolve(fileDir, dep + ".tsx"),
            path.resolve(fileDir, dep + ".js"),
            path.resolve(fileDir, dep, "index.ts"),
            path.resolve(fileDir, dep, "index.tsx")
          ];
          for (const p of possiblePaths) {
            if (fs.existsSync(p)) {
              builder.addNode(p, "Dependency", 2);
              builder.addEdge(file, p, "dependency");
              break;
            }
          }
        }
      });
    });
    const docDriftResults = getResults(ToolName.DocDrift, "docDrift");
    docDriftResults.forEach((issue) => {
      const file = issue.fileName || issue.location?.file;
      if (file) {
        builder.addNode(file, "Doc-Drift Issue", 5);
        const sev = rankSeverity(issue.severity || null);
        bumpIssue(file, sev);
      }
    });
    const depsResults = getResults(
      ToolName.DependencyHealth,
      "dependencyHealth"
    );
    depsResults.forEach((issue) => {
      const file = issue.fileName || issue.location?.file;
      if (file) {
        builder.addNode(file, "Dependency Issue", 5);
        const sev = rankSeverity(issue.severity || null);
        bumpIssue(file, sev);
      }
    });
    const nodes = Array.from(builder.nodesMap.values());
    const edges = builder.edges;
    const colorFor = (sev) => {
      switch (sev) {
        case Severity.Critical:
          return "#ff4d4f";
        // red
        case Severity.Major:
          return "#ff9900";
        // orange
        case Severity.Minor:
          return "#ffd666";
        // yellow
        case Severity.Info:
          return "#91d5ff";
        // light blue
        default:
          return "#97c2fc";
      }
    };
    let criticalIssues = 0;
    let majorIssues = 0;
    let minorIssues = 0;
    let infoIssues = 0;
    for (const node of nodes) {
      const n = node;
      const rec = fileIssues.get(n.id);
      if (rec) {
        n.duplicates = rec.duplicates || 0;
        n.color = colorFor(rec.maxSeverity);
        n.group = builder.getPackageGroup(n.id) || void 0;
        if (rec.maxSeverity === Severity.Critical) criticalIssues += rec.count;
        else if (rec.maxSeverity === Severity.Major) majorIssues += rec.count;
        else if (rec.maxSeverity === Severity.Minor) minorIssues += rec.count;
        else if (rec.maxSeverity === Severity.Info) infoIssues += rec.count;
      } else {
        n.color = colorFor(null);
        n.group = builder.getPackageGroup(n.id) || void 0;
        n.duplicates = 0;
      }
    }
    const graph = {
      nodes,
      edges,
      clusters: [],
      issues: [],
      metadata: {
        timestamp: (/* @__PURE__ */ new Date()).toISOString(),
        totalFiles: nodes.length,
        totalDependencies: edges.length,
        analysisTypes: [],
        criticalIssues,
        majorIssues,
        minorIssues,
        infoIssues,
        tokenBudget: report.scoring?.tokenBudget
      },
      truncated: {
        nodes: false,
        edges: false
      }
    };
    return graph;
  }
};
function createSampleGraph() {
  const builder = new GraphBuilder(process.cwd());
  builder.addNode("src/components/Button.tsx", "Button", 15);
  builder.addNode("src/utils/helpers.ts", "helpers", 12);
  builder.addNode("src/services/api.ts", "api", 18);
  builder.addEdge(
    "src/components/Button.tsx",
    "src/utils/helpers.ts",
    "dependency"
  );
  builder.addEdge("src/utils/helpers.ts", "src/services/api.ts", "dependency");
  return builder.build();
}
var __filename$1 = fileURLToPath(import.meta.url);
var __dirname$1 = dirname(__filename$1);
var WEB_PORT = 8e3;
var program = new Command();
program.name("aiready-visualize").description(
  "Generate interactive visualizations from AIReady analysis results"
).version("0.1.0").option("-d, --dev", "Start interactive web application (default)", true).option("-o, --output <file>", "Output HTML file for static generation").passThroughOptions();
function startDevServer(rootDir) {
  const webDir = resolve(__dirname$1, "../web");
  console.log("\u{1F3AF} AIReady Visualizer");
  console.log("\u{1F680} Starting interactive web application...");
  console.log();
  console.log(`\u{1F4C1} Project root: ${rootDir}`);
  console.log(`\u{1F310} Web server: http://localhost:${WEB_PORT}`);
  console.log();
  console.log("\u{1F4A1} The web app requires report data to visualize.");
  console.log('   Run "pnpm aiready scan ." then copy the report to:');
  console.log(`   web/public/report-data.json`);
  console.log();
  console.log("Press Ctrl+C to stop the server.");
  console.log();
  const vite = spawn("pnpm", ["dev"], {
    cwd: webDir,
    stdio: "inherit",
    shell: true,
    env: { ...process.env, FORCE_COLOR: "1" }
  });
  vite.on("error", (err) => {
    console.error("\u274C Failed to start dev server:", err.message);
    process.exit(1);
  });
}
program.command("sample").description("Generate a sample visualization for testing").option("-o, --output <file>", "Output HTML file", "visualization.html").option("--open", "Open in browser").action(async (options) => {
  const { writeFileSync } = await import('fs');
  const { exec } = await import('child_process');
  console.log("Generating sample visualization...");
  const graph = createSampleGraph();
  console.log(
    `
Sample graph created with ${graph.nodes.length} nodes and ${graph.edges.length} edges`
  );
  const html = generateHTML(graph);
  const outputPath = resolve(options.output);
  writeFileSync(outputPath, html);
  console.log(`\u2705 HTML saved to: ${outputPath}`);
  if (options.open) {
    const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
    exec(`${opener} "${outputPath}"`);
  }
});
program.command("generate").description("Generate visualization from analysis results").argument("<input>", "Input JSON file with analysis results").option("-o, --output <file>", "Output HTML file", "visualization.html").option("--open", "Open in browser").action(async (input, options) => {
  const { readFileSync, writeFileSync } = await import('fs');
  const { exec } = await import('child_process');
  console.log(`Reading analysis results from: ${input}`);
  const report = JSON.parse(readFileSync(input, "utf-8"));
  const rootDir = process.cwd();
  const graph = GraphBuilder.buildFromReport(report, rootDir);
  console.log(
    `Graph built: ${graph.nodes.length} nodes, ${graph.edges.length} edges`
  );
  const html = generateHTML(graph);
  const outputPath = resolve(options.output);
  writeFileSync(outputPath, html);
  console.log(`\u2705 HTML saved to: ${outputPath}`);
  if (options.open) {
    const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
    exec(`${opener} "${outputPath}"`);
  }
});
var args = process.argv.slice(2);
if (args.length === 0 || args.length === 1 && (args[0] === "--dev" || args[0] === "-d")) {
  startDevServer(process.cwd());
} else {
  program.parse();
}
//# sourceMappingURL=cli.js.map
//# sourceMappingURL=cli.js.map