import { stableValueHash } from "./stable-json.js";
export function repairToolCalls(input) {
    const knownTools = new Set(input.toolContracts?.map((contract) => contract.name) ?? []);
    const allowed = new Set([...input.allowedToolNames].filter((name) => knownTools.size === 0 || knownTools.has(name)));
    const candidates = [
        ...(input.declaredCalls ?? []).flatMap((call) => normalizeRawCall(call, "declared", allowed)),
        ...scavengeToolCalls(input.reasoningContent ?? "", "reasoning", allowed),
        ...scavengeToolCalls(input.visibleContent ?? "", "visible", allowed),
    ];
    const seen = new Set(input.stormState?.seenSignatures ?? []);
    const accepted = [];
    const suppressed = [];
    const ignored = [];
    for (const call of candidates) {
        if (!allowed.has(call.name)) {
            ignored.push(call.name);
            continue;
        }
        if (seen.has(call.signature)) {
            suppressed.push(call.signature);
            continue;
        }
        seen.add(call.signature);
        accepted.push(call);
    }
    return {
        calls: accepted,
        suppressed,
        ignored,
        stormState: { seenSignatures: seen },
    };
}
export function repairTruncatedJson(input) {
    const trimmed = input.trim();
    if (!trimmed)
        return "{}";
    let output = trimmed;
    let inString = false;
    let escaped = false;
    const stack = [];
    for (const char of output) {
        if (escaped) {
            escaped = false;
            continue;
        }
        if (char === "\\") {
            escaped = true;
            continue;
        }
        if (char === "\"") {
            inString = !inString;
            continue;
        }
        if (inString)
            continue;
        if (char === "{")
            stack.push("}");
        if (char === "[")
            stack.push("]");
        if ((char === "}" || char === "]") && stack.at(-1) === char)
            stack.pop();
    }
    if (inString)
        output += "\"";
    while (stack.length > 0)
        output += stack.pop();
    return output;
}
export function reNestFlatArgs(args) {
    const nested = {};
    for (const [key, value] of Object.entries(args)) {
        const parts = key.split(".").filter(Boolean);
        if (parts.length <= 1) {
            nested[key] = value;
            continue;
        }
        let cursor = nested;
        for (const part of parts.slice(0, -1)) {
            const existing = cursor[part];
            if (!isPlainObject(existing)) {
                cursor[part] = {};
            }
            cursor = cursor[part];
        }
        cursor[parts.at(-1) ?? key] = value;
    }
    return nested;
}
function scavengeToolCalls(content, source, allowedToolNames) {
    const calls = [];
    const jsonBlocks = extractJsonObjects(content);
    for (const block of jsonBlocks) {
        const parsed = parseMaybeToolCall(block);
        if (!parsed || !allowedToolNames.has(parsed.name))
            continue;
        calls.push(...normalizeRawCall(parsed, source, allowedToolNames));
    }
    return calls;
}
function extractJsonObjects(content) {
    const blocks = [];
    let start = -1;
    let depth = 0;
    let inString = false;
    let escaped = false;
    for (let index = 0; index < content.length; index += 1) {
        const char = content[index];
        if (escaped) {
            escaped = false;
            continue;
        }
        if (char === "\\") {
            escaped = true;
            continue;
        }
        if (char === "\"") {
            inString = !inString;
            continue;
        }
        if (inString)
            continue;
        if (char === "{") {
            if (depth === 0)
                start = index;
            depth += 1;
            continue;
        }
        if (char === "}" && depth > 0) {
            depth -= 1;
            if (depth === 0 && start >= 0) {
                blocks.push(content.slice(start, index + 1));
                start = -1;
            }
        }
    }
    if (depth > 0 && start >= 0) {
        blocks.push(repairTruncatedJson(content.slice(start)));
    }
    return blocks;
}
function parseMaybeToolCall(value) {
    try {
        const parsed = JSON.parse(repairTruncatedJson(value));
        if (!isPlainObject(parsed))
            return null;
        const name = typeof parsed.name === "string" ? parsed.name : typeof parsed.tool === "string" ? parsed.tool : null;
        if (!name)
            return null;
        const rawArgs = parsed.arguments ?? parsed.args ?? parsed.input ?? {};
        return {
            name,
            arguments: typeof rawArgs === "string" ? rawArgs : JSON.stringify(rawArgs),
        };
    }
    catch {
        return null;
    }
}
function normalizeRawCall(call, source, allowedToolNames) {
    if (!allowedToolNames.has(call.name))
        return [];
    let input;
    try {
        input = JSON.parse(repairTruncatedJson(call.arguments));
    }
    catch {
        input = call.arguments;
    }
    const normalizedInput = isPlainObject(input) ? reNestFlatArgs(input) : input;
    const signature = stableValueHash({ name: call.name, input: normalizedInput });
    return [{
            id: call.id,
            name: call.name,
            input: normalizedInput,
            source,
            signature,
        }];
}
function isPlainObject(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
