/**
 * Truncated-JSON repair — ported from reasonix (MIT). Format-agnostic:
 * works on any raw JSON argument string. Common trigger: model hits
 * max_tokens mid-structure; the last useful argument is half-emitted.
 *
 * Local-only — never makes a continuation call. The agent loop owns
 * budgets; this just patches what we have so the dispatcher can either
 * parse it or report a clean fallback.
 */
export function repairTruncatedJson(input) {
    const notes = [];
    if (!input || !input.trim()) {
        return {
            repaired: '{}',
            changed: input !== '{}',
            notes: ['empty input → {}'],
            fallback: false,
        };
    }
    // Fast path: already valid JSON.
    try {
        JSON.parse(input);
        return { repaired: input, changed: false, notes: [], fallback: false };
    }
    catch {
        /* fall through to repair */
    }
    const stack = [];
    let escaped = false;
    let inString = false;
    let lastSignificant = -1;
    for (let i = 0; i < input.length; i++) {
        const c = input[i];
        if (!/\s/.test(c))
            lastSignificant = i;
        if (escaped) {
            escaped = false;
            continue;
        }
        if (inString) {
            if (c === '\\') {
                escaped = true;
                continue;
            }
            if (c === '"') {
                inString = false;
                stack.pop();
            }
            continue;
        }
        if (c === '"') {
            inString = true;
            stack.push('"');
            continue;
        }
        if (c === '{' || c === '[')
            stack.push(c);
        else if (c === '}' || c === ']')
            stack.pop();
    }
    let s = input.slice(0, lastSignificant + 1);
    if (/,$/.test(s)) {
        s = s.replace(/,$/, '');
        notes.push('trimmed trailing comma');
    }
    if (/"\s*:\s*$/.test(s)) {
        s += ' null';
        notes.push('filled dangling key with null');
    }
    if (inString) {
        s += '"';
        stack.pop();
        notes.push('closed unterminated string');
    }
    while (stack.length > 0) {
        const top = stack.pop();
        if (top === '{')
            s += '}';
        else if (top === '[')
            s += ']';
        else if (top === '"')
            s += '"';
    }
    try {
        JSON.parse(s);
        return { repaired: s, changed: s !== input, notes, fallback: false };
    }
    catch (err) {
        const preview = input.length <= 500 ? input : `${input.slice(0, 500)} …[+${input.length - 500} chars]`;
        notes.push(`fallback to {}: ${err.message}`);
        notes.push(`unrecoverable truncation — original args preview: ${preview}`);
        return { repaired: '{}', changed: true, notes, fallback: true };
    }
}
