function toonQuote(value, activeDelimiter = ",") {
  const needsQuoting = value === "" || /^\s|\s$/.test(value) || value === "true" || value === "false" || value === "null" || /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(value) || /[:"\\\[\]{}\u0000-\u001F\u007F-\u009F]/.test(value) || value === "-" || /^-\S/.test(value) || value.includes(activeDelimiter);
  if (!needsQuoting) return value;
  return '"' + value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/[\u0000-\u001F\u007F-\u009F]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`) + '"';
}
function formatNumber(n) {
  if (Object.is(n, -0)) return "0";
  if (isNaN(n) || !isFinite(n)) return "null";
  if (n === 0) return "0";
  const abs = Math.abs(n);
  if (abs >= 1e-6 && abs < 1e21) {
    let s = n.toString();
    if (s.includes("e") || s.includes("E")) {
      s = n.toFixed(20).replace(/\.?0+$/, "");
    }
    return s;
  } else {
    return n.toString().toLowerCase();
  }
}
class ToonExporter {
  store;
  graph;
  constructor(store, graph) {
    this.store = store;
    this.graph = graph;
  }
  export(options) {
    const opt = options || {};
    const budget = opt.tokenBudget || 8192;
    const delimiterName = opt.delimiter || "comma";
    const delimiterMap = { comma: ",", tab: "	", pipe: "|" };
    const delim = delimiterMap[delimiterName];
    let files = this.store.getAllFiles(opt.repo);
    let symbols = this.store.getAllSymbols(opt.repo);
    let edges = this.store.getAllEdges(opt.repo);
    const clusters = this.store.getClusters(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));
    }
    let low = 0;
    let high = symbols.length;
    let bestOutput = "";
    const sortedSymbols = [...symbols].sort((a, b) => {
      const rankA = rankedSymbols.find((rs) => rs.name === a.name && rs.filePath === a.file_path)?.pagerank || 0;
      const rankB = rankedSymbols.find((rs) => rs.name === b.name && rs.filePath === b.file_path)?.pagerank || 0;
      return rankB - rankA;
    });
    while (low <= high) {
      const mid = Math.floor((low + high) / 2);
      const output = this.generateDocument({
        opt,
        delim,
        files,
        rankedFiles,
        symbols: sortedSymbols.slice(0, mid),
        rankedSymbols,
        edges,
        clusters,
        isTruncated: mid < symbols.length,
        truncatedCount: symbols.length - mid,
        originalSymbolsCount: symbols.length
      });
      const estimatedTokens = Math.ceil(Buffer.byteLength(output, "utf8") / 4);
      if (estimatedTokens <= budget) {
        bestOutput = output;
        low = mid + 1;
      } else {
        high = mid - 1;
      }
    }
    if (!bestOutput || Math.ceil(Buffer.byteLength(bestOutput, "utf8") / 4) > budget) {
      let lowEdge = 0;
      let highEdge = edges.length;
      let bestEdgeOutput = "";
      while (lowEdge <= highEdge) {
        const midEdge = Math.floor((lowEdge + highEdge) / 2);
        const output = this.generateDocument({
          opt,
          delim,
          files,
          rankedFiles,
          symbols: [],
          rankedSymbols,
          edges: edges.slice(0, midEdge),
          clusters,
          isTruncated: true,
          truncatedCount: symbols.length,
          originalSymbolsCount: symbols.length,
          isEdgesTruncated: midEdge < edges.length,
          edgesTruncatedCount: edges.length - midEdge
        });
        const estimatedTokens = Math.ceil(Buffer.byteLength(output, "utf8") / 4);
        if (estimatedTokens <= budget) {
          bestEdgeOutput = output;
          lowEdge = midEdge + 1;
        } else {
          highEdge = midEdge - 1;
        }
      }
      if (bestEdgeOutput) {
        bestOutput = bestEdgeOutput;
      } else {
        bestOutput = this.generateDocument({
          opt,
          delim,
          files: [],
          rankedFiles: [],
          symbols: [],
          rankedSymbols: [],
          edges: [],
          clusters: [],
          isTruncated: true,
          truncatedCount: symbols.length,
          originalSymbolsCount: symbols.length
        });
      }
    }
    return bestOutput.endsWith("\n") ? bestOutput.slice(0, -1) : bestOutput;
  }
  generateDocument(params) {
    const {
      opt,
      delim,
      files,
      rankedFiles,
      symbols,
      rankedSymbols,
      edges,
      clusters,
      isTruncated,
      truncatedCount,
      originalSymbolsCount,
      isEdgesTruncated,
      edgesTruncatedCount
    } = params;
    const parts = [];
    parts.push("version: 1");
    parts.push(`generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
    parts.push(`repo: ${opt.repo || "all"}`);
    parts.push(`tokenBudget: ${opt.tokenBudget || 8192}`);
    parts.push("");
    parts.push("summary:");
    parts.push(`  files: ${files.length}`);
    parts.push(`  symbols: ${originalSymbolsCount}`);
    parts.push(`  edges: ${edges.length}`);
    const languages = Array.from(new Set(files.map((f) => f.language))).filter(Boolean).sort();
    if (languages.length > 0) {
      const escapedLangs = languages.map((l) => toonQuote(l, delim)).join(delim);
      parts.push(`  languages[${languages.length}]: ${escapedLangs}`);
    }
    parts.push("");
    const fileRankMap = new Map(rankedFiles.map((f) => [f.path, f.pagerank]));
    const symbolRankMap = new Map(rankedSymbols.map((s) => [`${s.filePath}::${s.name}`, s.pagerank]));
    if (files.length > 0) {
      const sortedFiles = [...files].sort((a, b) => {
        const rA = fileRankMap.get(a.path) || 0;
        const rB = fileRankMap.get(b.path) || 0;
        return rB - rA;
      });
      parts.push(`files[${files.length}]{path,language,symbols,pagerank}:`);
      for (const f of sortedFiles) {
        const path = toonQuote(f.path, delim);
        const language = toonQuote(f.language, delim);
        const symbolsCount = f.lines ? this.store.getSymbolsForFile(f.path).length : 0;
        const rank = formatNumber(parseFloat((fileRankMap.get(f.path) || 0).toFixed(6)));
        parts.push(`  ${path}${delim}${language}${delim}${symbolsCount}${delim}${rank}`);
      }
      parts.push("");
    }
    if (symbols.length > 0) {
      parts.push(`symbols[${symbols.length}]{name,kind,file,scope,pagerank}:`);
      for (const s of symbols) {
        const name = toonQuote(s.name, delim);
        const kind = toonQuote(s.kind, delim);
        const file = toonQuote(s.file_path, delim);
        const scope = toonQuote(s.scope || "", delim);
        const rankKey = `${s.file_path}::${s.name}`;
        const rank = formatNumber(parseFloat((symbolRankMap.get(rankKey) || 0).toFixed(6)));
        parts.push(`  ${name}${delim}${kind}${delim}${file}${delim}${scope}${delim}${rank}`);
      }
      parts.push("");
    }
    const keptSymbolsSet = new Set(symbols.map((s) => `${s.file_path}::${s.name}`));
    const filteredEdges = edges.filter((e) => {
      if (e.source_symbol) {
        const srcKey = `${e.source_file}::${e.source_symbol}`;
        if (!keptSymbolsSet.has(srcKey)) return false;
      }
      if (e.target_symbol) {
        const tgtKey = `${e.target_file}::${e.target_symbol}`;
        if (!keptSymbolsSet.has(tgtKey)) return false;
      }
      return true;
    });
    if (filteredEdges.length > 0) {
      parts.push(`edges[${filteredEdges.length}]{sourceFile,targetFile,edgeType,sourceSymbol,targetSymbol,weight}:`);
      for (const e of filteredEdges) {
        const sourceFile = toonQuote(e.source_file, delim);
        const targetFile = toonQuote(e.target_file || "", delim);
        const edgeType = toonQuote(e.edge_type, delim);
        const sourceSymbol = toonQuote(e.source_symbol || "", delim);
        const targetSymbol = toonQuote(e.target_symbol || "", delim);
        const weight = formatNumber(e.weight || 1);
        parts.push(`  ${sourceFile}${delim}${targetFile}${delim}${edgeType}${delim}${sourceSymbol}${delim}${targetSymbol}${delim}${weight}`);
      }
      parts.push("");
    }
    if (clusters.length > 0) {
      parts.push(`clusters[${clusters.length}]{name,source,parentName,depth,fileCount}:`);
      for (const c of clusters) {
        const name = toonQuote(c.name, delim);
        const source = toonQuote(c.source, delim);
        const parentName = toonQuote(c.parent_name || "", delim);
        const depth = formatNumber(c.depth || 0);
        const fileCount = formatNumber(c.file_count || 0);
        parts.push(`  ${name}${delim}${source}${delim}${parentName}${delim}${depth}${delim}${fileCount}`);
      }
      parts.push("");
    }
    if (isTruncated) {
      parts.push("truncated: true");
      parts.push("truncatedAt: symbols");
      parts.push(`includedSymbols: ${symbols.length}`);
      parts.push(`totalSymbols: ${originalSymbolsCount}`);
      if (isEdgesTruncated) {
        parts.push("edgesTruncated: true");
        parts.push(`includedEdges: ${filteredEdges.length}`);
        parts.push(`totalEdges: ${edges.length}`);
      }
    }
    return parts.join("\n");
  }
}
export {
  ToonExporter,
  formatNumber,
  toonQuote
};
