import { findToolByName, parseToolInput } from "../../Tool.js";
import { actionType } from "../../protocol/actions.js";
import { isAbortError, throwIfAborted } from "../../utils/abort.js";
export async function executeToolPlan(tools, actions, context, options = {}) {
    throwIfAborted(context.abortSignal);
    const results = [];
    const batches = partitionActions(tools, actions, context);
    for (const batch of batches) {
        throwIfAborted(context.abortSignal);
        if (batch.parallel) {
            const batchResults = await Promise.all(batch.items.map((item) => runOneTool(item.tool, item.input, context, options)));
            results.push(...batchResults);
        }
        else {
            for (const item of batch.items) {
                throwIfAborted(context.abortSignal);
                results.push(await runOneTool(item.tool, item.input, context, options));
            }
        }
    }
    return results;
}
function partitionActions(tools, actions, context) {
    const batches = [];
    let readBatch = [];
    const flushReadBatch = () => {
        if (readBatch.length > 0) {
            batches.push({ parallel: true, items: readBatch });
            readBatch = [];
        }
    };
    for (const action of actions) {
        const tool = findToolByName(tools, actionType(action));
        if (!tool) {
            flushReadBatch();
            batches.push({
                parallel: false,
                items: [unknownToolAction(action)],
            });
            continue;
        }
        const parsed = parseToolInput(tool, action);
        const canParallel = tool.isEnabled(context) &&
            tool.isReadOnly(parsed) &&
            tool.isConcurrencySafe(parsed) &&
            tool.checkPermissions(parsed, context).behavior === "allow";
        if (canParallel) {
            readBatch.push({ tool, input: parsed });
        }
        else {
            flushReadBatch();
            batches.push({ parallel: false, items: [{ tool, input: parsed }] });
        }
    }
    flushReadBatch();
    return batches;
}
async function runOneTool(tool, input, context, options) {
    const startedAtMs = Date.now();
    try {
        throwIfAborted(context.abortSignal);
        const preflight = await options.onEvent?.({ action: input, phase: "start", startedAtMs });
        throwIfAborted(context.abortSignal);
        if (preflight)
            return finish(input, preflight, options, startedAtMs);
        if (!tool.isEnabled(context)) {
            return finish(input, {
                action_type: actionType(input),
                status: "failed",
                message: `${tool.name} is disabled`,
            }, options, startedAtMs);
        }
        const beforeTool = await options.onBeforeTool?.(tool, input, context);
        throwIfAborted(context.abortSignal);
        if (beforeTool)
            return finish(input, beforeTool, options, startedAtMs);
        const permission = tool.checkPermissions(input, context);
        if (permission.behavior === "deny") {
            return finish(input, {
                action_type: actionType(input),
                status: "failed",
                message: permission.message ?? `${tool.name} denied by policy`,
            }, options, startedAtMs);
        }
        throwIfAborted(context.abortSignal);
        const output = await tool.run(input, context);
        throwIfAborted(context.abortSignal);
        return finish(input, output.result, options, startedAtMs);
    }
    catch (error) {
        if (isAbortError(error, context.abortSignal))
            throw error;
        return finish(input, {
            action_type: actionType(input),
            status: "failed",
            message: error instanceof Error ? error.message : String(error),
        }, options, startedAtMs);
    }
}
async function finish(action, result, options, startedAtMs) {
    const finishedAtMs = Date.now();
    await options.onEvent?.({
        action,
        phase: "finish",
        result,
        startedAtMs,
        finishedAtMs,
        durationMs: finishedAtMs - startedAtMs,
    });
    return result;
}
function unknownToolAction(action) {
    return {
        input: action,
        tool: {
            name: actionType(action),
            displayName: actionType(action),
            description: "Unknown tool placeholder.",
            inputSchema: { parse: (value) => value },
            isEnabled: () => true,
            isReadOnly: () => false,
            isConcurrencySafe: () => false,
            isDestructive: () => false,
            checkPermissions: () => ({ behavior: "allow" }),
            async run() {
                return {
                    result: {
                        action_type: actionType(action),
                        status: "failed",
                        message: `Unknown action type: ${actionType(action)}`,
                    },
                };
            },
        },
    };
}
