const TOKEN_CHARS_PER_LINE = 4;
class LLMExporter {
  store;
  graph;
  constructor(store, graph) {
    this.store = store;
    this.graph = graph;
  }
  export(options) {
    const opt = options || {};
    const budget = opt.tokenBudget || 8192;
    const parts = [];
    let files = this.store.getAllFiles(opt.repo);
    let symbols = this.store.getAllSymbols(opt.repo);
    let edges = this.store.getAllEdges(opt.repo);
    let rankedFiles = this.graph.getRankedFiles();
    let rankedSymbols = this.graph.getRankedSymbols();
    if (opt.files) {
      const allowed = new Set(opt.files);
      files = files.filter((f) => allowed.has(f.path));
      symbols = symbols.filter((s) => allowed.has(s.file_path));
      edges = edges.filter((e) => allowed.has(e.source_file) && allowed.has(e.target_file));
      rankedFiles = rankedFiles.filter((f) => allowed.has(f.path));
      rankedSymbols = rankedSymbols.filter((s) => allowed.has(s.filePath));
    }
    const repoName = opt.repo || "project";
    parts.push(`# Mapx: ${repoName}`);
    parts.push("");
    const structureSection = this.buildStructureSection(opt.repo);
    if (structureSection) {
      parts.push(structureSection);
    }
    const symsByFile = /* @__PURE__ */ new Map();
    for (const s of symbols) {
      const filePath = s.file_path;
      if (!symsByFile.has(filePath)) symsByFile.set(filePath, []);
      symsByFile.get(filePath).push(s);
    }
    const fileSection = this.buildFileSection(files, rankedFiles, edges, symsByFile);
    parts.push(fileSection);
    const symbolSection = this.buildSymbolSection(rankedSymbols, budget);
    parts.push(symbolSection);
    if (edges.length > 0) {
      const depSection = this.buildDependencySection(edges);
      parts.push(depSection);
    }
    let result = parts.join("\n");
    const estimatedTokens = Math.ceil(result.length / TOKEN_CHARS_PER_LINE);
    if (estimatedTokens > budget) {
      result = this.truncateToFit(result, budget);
    }
    return result;
  }
  buildStructureSection(repo) {
    const clusters = this.store.getClusters(repo);
    if (clusters.length === 0) return "";
    const lines = [];
    lines.push("## Structure");
    const roots = [];
    const childrenMap = /* @__PURE__ */ new Map();
    for (const c of clusters) {
      if (!c.parent_name) {
        roots.push(c);
      } else {
        const parentName = c.parent_name;
        if (!childrenMap.has(parentName)) {
          childrenMap.set(parentName, []);
        }
        childrenMap.get(parentName).push(c);
      }
    }
    for (const list of childrenMap.values()) {
      list.sort((a, b) => a.name.localeCompare(b.name));
    }
    roots.sort((a, b) => a.name.localeCompare(b.name));
    const printTree = (node, indent) => {
      const padding = "  ".repeat(indent);
      const namePart = node.name;
      const sourcePart = `(${node.source})`;
      const filesPart = `[${node.file_count} files]`;
      lines.push(`${padding}${namePart} ${sourcePart} ${filesPart}`);
      const children = childrenMap.get(node.name) || [];
      for (const child of children) {
        printTree(child, indent + 1);
      }
    };
    for (const root of roots) {
      printTree(root, 0);
    }
    lines.push("");
    return lines.join("\n");
  }
  getFileHeuristicSummary(fileSymbols) {
    if (!fileSymbols || fileSymbols.length === 0) return "empty file";
    const counts = {};
    for (const s of fileSymbols) {
      const kind = s.kind.toLowerCase();
      counts[kind] = (counts[kind] || 0) + 1;
    }
    const sortedKinds = Object.entries(counts).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
    const parts = [];
    for (const [kind, count] of sortedKinds.slice(0, 3)) {
      parts.push(`${count} ${kind}${count > 1 ? kind.endsWith("s") || kind.endsWith("ch") ? "es" : "s" : ""}`);
    }
    const dominant = sortedKinds[0] ? sortedKinds[0][0] : "";
    const dominantStr = dominant ? `, dominant: ${dominant}` : "";
    return `contains ${parts.join(", ")}${dominantStr}`;
  }
  buildFileSection(files, rankedFiles, edges, symsByFile) {
    const lines = [];
    lines.push(`## Files (${files.length})`);
    const rankMap = new Map(rankedFiles.map((f) => [f.path, f.pagerank]));
    const depMap = /* @__PURE__ */ new Map();
    for (const edge of edges) {
      const src = edge.source_file;
      const tgt = edge.target_file;
      const type = edge.edge_type;
      if (!depMap.has(src)) depMap.set(src, []);
      const infSuffix = edge.verifiability === "inferred" ? " [inferred]" : "";
      depMap.get(src).push(`${tgt} (${type})${infSuffix}`);
    }
    const sorted = [...files].sort((a, b) => {
      const ra = rankMap.get(a.path) || 0;
      const rb = rankMap.get(b.path) || 0;
      return rb - ra;
    });
    for (const file of sorted) {
      const path = file.path;
      const lang = file.language;
      const deps = depMap.get(path);
      const depStr = deps ? ` \u2192 ${deps.join(", ")}` : "";
      const fileSyms = symsByFile.get(path) || [];
      const summary = this.getFileHeuristicSummary(fileSyms);
      lines.push(`- ${path} [${lang}] - ${summary}${depStr}`);
    }
    lines.push("");
    return lines.join("\n");
  }
  buildSymbolSection(rankedSymbols, budget) {
    const lines = [];
    lines.push(`## Symbols (${rankedSymbols.length})`);
    const byFile = /* @__PURE__ */ new Map();
    for (const sym of rankedSymbols) {
      if (!byFile.has(sym.filePath)) byFile.set(sym.filePath, []);
      byFile.get(sym.filePath).push(sym);
    }
    const dbSymbols = this.store.getAllSymbols();
    const sigMap = /* @__PURE__ */ new Map();
    for (const s of dbSymbols) {
      const key = `${s.file_path}::${s.name}`;
      sigMap.set(key, s.signature);
    }
    const maxLines = Math.floor(budget * TOKEN_CHARS_PER_LINE / 40);
    let lineCount = 0;
    for (const [filePath, syms] of byFile) {
      if (lineCount > maxLines) break;
      const topLevel = syms.filter((s) => !s.scope);
      for (const sym of topLevel) {
        if (lineCount > maxLines) break;
        const sig = sigMap.get(`${sym.filePath}::${sym.name}`) || sym.name;
        lines.push(`- ${sym.kind} ${sig} @ ${sym.filePath}:${sym.startLine}`);
        const children = syms.filter((s) => s.scope === sym.name);
        for (const child of children) {
          if (lineCount > maxLines) break;
          const childSig = sigMap.get(`${child.filePath}::${child.name}`) || child.name;
          lines.push(`  - ${child.kind} ${childSig} @ :${child.startLine}`);
          lineCount++;
        }
        lineCount++;
      }
    }
    lines.push("");
    return lines.join("\n");
  }
  buildDependencySection(edges) {
    const lines = [];
    lines.push("## Dependencies");
    const routeGroups = /* @__PURE__ */ new Map();
    const otherEdges = [];
    for (const edge of edges) {
      if (edge.edge_type === "route") {
        const src = edge.source_file;
        const controller = edge.target_symbol || edge.target_file.split("/").pop() || "Controller";
        const key = `${src}->${controller}`;
        let meta = {};
        if (edge.metadata) {
          try {
            meta = typeof edge.metadata === "string" ? JSON.parse(edge.metadata) : edge.metadata;
          } catch {
          }
        }
        const verb = meta.httpVerb || "GET";
        const uri = meta.uri || "/";
        if (!routeGroups.has(key)) {
          routeGroups.set(key, { source: src, controller, routes: [] });
        }
        routeGroups.get(key).routes.push({ verb, uri });
      } else {
        otherEdges.push(edge);
      }
    }
    for (const group of routeGroups.values()) {
      const uriToVerbs = /* @__PURE__ */ new Map();
      for (const r of group.routes) {
        if (!uriToVerbs.has(r.uri)) {
          uriToVerbs.set(r.uri, /* @__PURE__ */ new Set());
        }
        uriToVerbs.get(r.uri).add(r.verb);
      }
      const descParts = [];
      for (const [uri, verbs] of uriToVerbs.entries()) {
        const verbsStr = Array.from(verbs).sort().join("/");
        descParts.push(`${verbsStr} ${uri}`);
      }
      lines.push(`- ${group.source} \u2192 ${group.controller} (${group.routes.length} routes: ${descParts.join(", ")})`);
    }
    const unique = /* @__PURE__ */ new Map();
    for (const edge of otherEdges) {
      const key = `${edge.source_file}->${edge.target_file}:${edge.edge_type}`;
      if (!unique.has(key)) {
        unique.set(key, {
          source: edge.source_file,
          target: edge.target_file,
          type: edge.edge_type,
          verifiability: edge.verifiability || "verified",
          targetRepo: edge.target_repo
        });
      }
    }
    for (const dep of unique.values()) {
      const infSuffix = dep.verifiability === "inferred" ? " [inferred]" : "";
      const targetRepoSuffix = dep.targetRepo ? ` [repo: ${dep.targetRepo}]` : "";
      lines.push(`- ${dep.source} \u2192 ${dep.target}${targetRepoSuffix} (${dep.type})${infSuffix}`);
    }
    lines.push("");
    return lines.join("\n");
  }
  truncateToFit(output, budget) {
    const maxChars = budget * TOKEN_CHARS_PER_LINE;
    if (output.length <= maxChars) return output;
    const truncated = output.substring(0, maxChars);
    const lastNewline = truncated.lastIndexOf("\n");
    return truncated.substring(0, lastNewline) + "\n\n[... truncated to fit token budget]\n";
  }
}
export {
  LLMExporter
};
