import { loadConfig } from "../config.js";
import { raceWallClock } from "../utils/wall-clock.js";
import { SubQuerySchema, } from "./retrieval-schemas.js";
/**
 * End-to-end wall-clock cap on a single codebase_retrieval call. Per-sub-query
 * timeout is already 25s; this protects against a batch of slow queries
 * stacking up behind bounded concurrency. Telemetry: max=907s. Configurable
 * via CODESIFT_CODEBASE_RETRIEVAL_CAP_MS.
 */
const CODEBASE_RETRIEVAL_WALL_CLOCK_MS = (() => {
    const env = process.env["CODESIFT_CODEBASE_RETRIEVAL_CAP_MS"];
    const parsed = env ? Number(env) : NaN;
    return Number.isFinite(parsed) && parsed > 0 ? parsed : 30_000;
})();
import { estimateTokens } from "./retrieval-utils.js";
import { handleSemanticQuery, handleHybridQuery } from "./semantic-handlers.js";
import { MAX_QUERIES, MIN_TRUNCATION_TOKENS, CHARS_PER_TOKEN, DEFAULT_SOURCE_CHARS, } from "./retrieval-constants.js";
import { formatSearchSymbols, formatFileTree, formatFileOutline, formatCallTree, formatImpactAnalysis, formatAssembleContext, formatKnowledgeMap, formatConversations, } from "../formatters.js";
import { formatRefsCompact } from "../tools/symbol-tools.js";
// Re-export for backward compatibility (tests + server import from here)
export { estimateTokens, decomposeQuery } from "./retrieval-utils.js";
// ---------------------------------------------------------------------------
// Sub-query dispatcher — thin switch, delegates to typed handlers
// ---------------------------------------------------------------------------
async function executeSubQuery(repo, query) {
    switch (query.type) {
        case "symbols": {
            const { searchSymbols } = await import("../tools/search-tools.js");
            const results = await searchSymbols(repo, query.query, {
                kind: query.kind,
                file_pattern: query.file_pattern,
                include_source: true,
                top_k: query.top_k ?? 5,
                source_chars: query.source_chars ?? DEFAULT_SOURCE_CHARS,
            });
            const text = formatSearchSymbols(results);
            return { type: query.type, data: text, tokens: estimateTokens(text) };
        }
        case "text": {
            const { searchText } = await import("../tools/search-tools.js");
            const results = await searchText(repo, query.query, {
                regex: query.regex,
                context_lines: query.context_lines ?? 0,
                file_pattern: query.file_pattern,
                auto_group: true,
            });
            const text = typeof results === "string" ? results : JSON.stringify(results);
            return { type: query.type, data: text, tokens: estimateTokens(text) };
        }
        case "file_tree": {
            const { getFileTree } = await import("../tools/outline-tools.js");
            const result = await getFileTree(repo, {
                path_prefix: query.path ?? query.path_prefix,
                name_pattern: query.name_pattern,
                depth: query.depth,
                compact: query.compact ?? true,
                min_symbols: query.min_symbols,
            });
            const text = formatFileTree(result);
            return { type: query.type, data: text, tokens: estimateTokens(text) };
        }
        case "outline": {
            const { getFileOutline } = await import("../tools/outline-tools.js");
            const result = await getFileOutline(repo, query.file_path);
            const text = formatFileOutline(result);
            return { type: query.type, data: text, tokens: estimateTokens(text) };
        }
        case "references": {
            const { findReferences } = await import("../tools/symbol-tools.js");
            const results = await findReferences(repo, query.symbol_name);
            const text = formatRefsCompact(results);
            return { type: query.type, data: text, tokens: estimateTokens(text) };
        }
        case "call_chain": {
            const { traceCallChain } = await import("../tools/graph-tools.js");
            const result = await traceCallChain(repo, query.symbol_name, query.direction ?? "callers", {
                depth: query.depth,
                include_source: query.include_source ?? false,
            });
            const text = formatCallTree(result);
            return { type: query.type, data: text, tokens: estimateTokens(text) };
        }
        case "impact": {
            const { impactAnalysis } = await import("../tools/impact-tools.js");
            const result = await impactAnalysis(repo, query.since, {
                depth: query.depth,
                until: query.until,
                include_source: query.include_source ?? false,
            });
            const text = formatImpactAnalysis(result);
            return { type: query.type, data: text, tokens: estimateTokens(text) };
        }
        case "context": {
            const { assembleContext } = await import("../tools/context-tools.js");
            const result = await assembleContext(repo, query.query, query.max_tokens);
            const text = formatAssembleContext(result);
            return { type: query.type, data: text, tokens: estimateTokens(text) };
        }
        case "knowledge_map": {
            const { getKnowledgeMap } = await import("../tools/context-tools.js");
            const result = await getKnowledgeMap(repo, query.focus, query.depth);
            const text = formatKnowledgeMap(result);
            return { type: query.type, data: text, tokens: estimateTokens(text) };
        }
        case "semantic":
            return handleSemanticQuery(repo, query);
        case "hybrid":
            return handleHybridQuery(repo, query);
        case "conversation": {
            const { searchConversations } = await import("../tools/conversation-tools.js");
            const result = await searchConversations(query.query, query.project, query.limit);
            const text = formatConversations(result);
            return { type: query.type, data: text, tokens: estimateTokens(text) };
        }
    }
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export async function codebaseRetrieval(repo, queries, tokenBudget) {
    return raceWallClock(codebaseRetrievalInner(repo, queries, tokenBudget), CODEBASE_RETRIEVAL_WALL_CLOCK_MS, () => ({
        results: [],
        total_tokens: 0,
        truncated: true,
        query_count: queries.length,
        wall_clock_truncated: true,
        hint: `codebase_retrieval exceeded ${CODEBASE_RETRIEVAL_WALL_CLOCK_MS}ms — narrow scope (file_pattern), split into smaller batches, or use targeted tools (search_text/search_symbols)`,
    }));
}
async function codebaseRetrievalInner(repo, queries, tokenBudget) {
    const config = loadConfig();
    const budget = tokenBudget ?? config.defaultTokenBudget;
    const limited = queries.slice(0, MAX_QUERIES);
    // Execute sub-queries with bounded concurrency (max 3 parallel)
    // to balance speed vs memory (full parallel on 600+ files = OOM)
    const MAX_CONCURRENCY = 3;
    // Per-sub-query timeout: telemetry showed codebase_retrieval p99=62s where
    // a single slow semantic/embedding call stalled the entire batch. Bound
    // each sub-query at 25s and surface a typed timeout error so siblings
    // still complete.
    const SUBQUERY_TIMEOUT_MS = 25_000;
    const subResults = new Array(limited.length);
    const tasks = limited.map((raw, idx) => async () => {
        const parsed = SubQuerySchema.safeParse(raw);
        if (!parsed.success) {
            const message = `Invalid sub-query: ${parsed.error.issues.map((i) => i.message).join(", ")}`;
            subResults[idx] = {
                type: raw?.type ?? "unknown",
                data: { error: message },
                tokens: estimateTokens(message),
            };
            return;
        }
        try {
            subResults[idx] = await Promise.race([
                executeSubQuery(repo, parsed.data),
                new Promise((_, reject) => setTimeout(() => reject(new Error(`sub-query timed out after ${SUBQUERY_TIMEOUT_MS}ms`)), SUBQUERY_TIMEOUT_MS)),
            ]);
        }
        catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            subResults[idx] = {
                type: parsed.data.type,
                data: { error: message },
                tokens: estimateTokens(message),
            };
        }
    });
    // Simple bounded concurrency without external deps
    for (let i = 0; i < tasks.length; i += MAX_CONCURRENCY) {
        await Promise.all(tasks.slice(i, i + MAX_CONCURRENCY).map((t) => t()));
    }
    // Enforce token budget — include results until budget is exceeded
    const results = [];
    let totalTokens = 0;
    let truncated = false;
    for (const result of subResults) {
        if (totalTokens + result.tokens > budget) {
            truncated = true;
            const remaining = budget - totalTokens;
            if (remaining > MIN_TRUNCATION_TOKENS) {
                // Data is typically a formatted string after OPT-2; slice directly
                const dataStr = typeof result.data === "string" ? result.data : JSON.stringify(result.data);
                const truncatedData = dataStr.slice(0, remaining * CHARS_PER_TOKEN) + "\n(truncated)";
                results.push({
                    type: result.type,
                    data: truncatedData,
                    tokens: remaining,
                });
                totalTokens += remaining;
            }
            break;
        }
        results.push(result);
        totalTokens += result.tokens;
    }
    return {
        results,
        total_tokens: totalTokens,
        truncated,
        query_count: limited.length,
    };
}
//# sourceMappingURL=codebase-retrieval.js.map