import { scavengeToolCalls } from './scavenge.js';
import { repairTruncatedJson } from './truncation.js';
export { analyzeSchema, flattenSchema, nestArguments } from './flatten.js';
export { repairTruncatedJson } from './truncation.js';
export { scavengeToolCalls } from './scavenge.js';
/** Boundary-level helper: parse tool-use argument JSON with truncation
 *  recovery. Returns `null` if every attempt fails and the caller should
 *  reject the call (better than dispatching with `{}`).
 *
 *  Usage at the streaming-client boundary:
 *    const args = repairAndParseArgs(jsonAccumulator);
 *    if (args == null) return reject("invalid JSON in tool_use");
 */
export function repairAndParseArgs(raw) {
    const r = repairTruncatedJson(raw);
    if (r.fallback)
        return null;
    try {
        const parsed = JSON.parse(r.repaired);
        if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
            return { input: parsed, repaired: r.changed, notes: r.notes };
        }
        return null;
    }
    catch {
        return null;
    }
}
export class ToolCallRepair {
    opts;
    constructor(opts) {
        this.opts = opts;
    }
    /**
     * Scavenge leaked tool calls from text/reasoning channels and merge
     * into the declared list, deduped.
     *
     * @param declaredCalls  Tool calls the model emitted structurally.
     * @param reasoningText  Optional reasoning_content / thinking text.
     * @param contentText    Optional plain text-channel content.
     */
    process(declaredCalls, reasoningText, contentText = null) {
        const report = { scavenged: 0, duplicatesDropped: 0, notes: [] };
        const combined = [reasoningText ?? '', contentText ?? ''].filter(Boolean).join('\n');
        const scavenged = scavengeToolCalls(combined || null, {
            allowedNames: this.opts.allowedToolNames,
            maxCalls: this.opts.maxScavenge ?? 4,
        });
        const seenSignatures = new Set(declaredCalls.map(signature));
        const merged = [...declaredCalls];
        for (const sc of scavenged.calls) {
            const sig = signature(sc);
            if (seenSignatures.has(sig)) {
                report.duplicatesDropped++;
                continue;
            }
            merged.push(sc);
            report.scavenged++;
            seenSignatures.add(sig);
        }
        report.notes.push(...scavenged.notes);
        return { calls: merged, report };
    }
}
function signature(call) {
    return `${call.name}::${stableStringify(call.input)}`;
}
function stableStringify(value) {
    if (value === null || typeof value !== 'object')
        return JSON.stringify(value);
    if (Array.isArray(value)) {
        return `[${value.map(stableStringify).join(',')}]`;
    }
    const obj = value;
    const keys = Object.keys(obj).sort();
    return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(',')}}`;
}
