/**
 * Agent Batch Tool — Fan-out Operations
 *
 * Run the same tool on multiple workers in parallel.
 * Local workers: dispatched via gRPC streaming (same as agent_send).
 * Peer workers: dispatched via remote master's task API, which internally uses gRPC.
 */
import { log } from '../utils/logger.js';
import { AgentRegistryManager } from '../core/agent-registry.js';
import { checkAgentHealth } from '../utils/agent-utils.js';
import { agentMaster } from '../core/agent-master.js';
import { v4 as uuidv4 } from 'uuid';
import { withGrpcWorkerStreaming } from '../utils/grpc-streaming-tool-helper.js';
function extractReadableResult(value) {
    if (!value)
        return '';
    if (typeof value === 'string')
        return value;
    if (Array.isArray(value?.content)) {
        return value.content
            .filter((item) => item?.type === 'text' && typeof item.text === 'string')
            .map((item) => item.text)
            .join('\n');
    }
    if (typeof value?.output === 'string')
        return value.output;
    if (value?.result)
        return extractReadableResult(value.result);
    return '';
}
/**
 * Dispatch to a local worker via gRPC streaming (same path as agent_send).
 */
async function dispatchToLocalWorker(agent, tool, taskParams, context) {
    const masterServer = agentMaster.getServer();
    const masterConfig = agentMaster.getConfig();
    const grpcServer = masterServer?.grpcServer;
    if (!grpcServer?.sendTask || !masterConfig) {
        return { worker: agent.name, status: 'failed', error: 'No gRPC server available' };
    }
    const taskId = uuidv4();
    const registry = new AgentRegistryManager();
    const sanitizedContext = { ...context, __transport: 'grpc' };
    registry.addTask({
        id: taskId,
        agentId: agent.id,
        type: 'execute',
        payload: { tool, params: taskParams, context: sanitizedContext },
        status: 'pending',
        created: Date.now(),
    });
    registry.save();
    try {
        const streamingResult = await withGrpcWorkerStreaming({
            agentId: agent.id,
            agentName: agent.name,
            operationId: taskId,
            operationType: 'task',
        }, async () => {
            const sent = grpcServer.sendTask(masterConfig.id, agent.id, taskId, tool, taskParams, sanitizedContext);
            if (!sent)
                throw new Error('gRPC send failed');
            registry.updateTask(taskId, { status: 'running', started: Date.now() });
            registry.save();
            return { status: 'accepted', taskId };
        });
        const { events } = streamingResult;
        let finalStatus = 'running';
        let finalResult = null;
        for (const event of events) {
            if (event.type === 'task_completed') {
                finalStatus = 'completed';
                finalResult = event.result;
                break;
            }
            else if (event.type === 'task_failed') {
                finalStatus = 'failed';
                finalResult = { error: event.error };
                break;
            }
            else if (event.type === 'task_cancelled') {
                finalStatus = 'cancelled';
                break;
            }
        }
        registry.updateTask(taskId, {
            status: finalStatus,
            completed: Date.now(),
            result: finalResult,
        });
        registry.save();
        return {
            worker: agent.name,
            status: finalStatus,
            result: finalResult,
            error: finalStatus === 'failed' ? finalResult?.error : undefined,
        };
    }
    catch (error) {
        return {
            worker: agent.name,
            status: 'failed',
            error: error instanceof Error ? error.message : String(error),
        };
    }
}
/**
 * Dispatch to a peer worker via the remote master's task API.
 * The remote master internally uses gRPC to reach its workers.
 */
async function dispatchToPeerWorker(agent, peerEndpoint, peerName, tool, taskParams) {
    // If dispatching to the peer master itself (no separate worker), use its name directly
    const isMasterDirect = agent.type === 'remote';
    const label = isMasterDirect ? peerName : `${peerName} -> ${agent.name}`;
    try {
        const body = {
            tool,
            params: taskParams,
            context: { __transport: 'peer_grpc' },
        };
        // Only set targetAgentId when dispatching to a specific worker behind the peer
        if (!isMasterDirect) {
            body.targetAgentId = agent.id;
        }
        const res = await fetch(`${peerEndpoint}/task`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'x-clawx-peer-source': 'agent-batch' },
            body: JSON.stringify(body),
        });
        if (!res.ok) {
            return { worker: label, status: 'failed', error: `Peer rejected: ${res.status}` };
        }
        const accepted = await res.json();
        const taskId = accepted.taskId || accepted.id;
        if (!taskId) {
            return { worker: label, status: 'failed', error: 'No task ID returned' };
        }
        // Wait for remote gRPC execution to complete (query task status)
        const { waitForPeerTaskResult } = await import('./agentPeerTaskHelpers.js');
        const { status, result } = await waitForPeerTaskResult(peerEndpoint, taskId, 60000);
        return {
            worker: label,
            status,
            result,
            error: status === 'failed' ? (result?.error || 'Unknown error') : undefined,
        };
    }
    catch (error) {
        return { worker: label, status: 'failed', error: error instanceof Error ? error.message : String(error) };
    }
}
export const agentBatchTool = {
    name: 'agent_batch',
    label: 'Batch Run (Fan-out)',
    description: 'Run the same tool on all available workers and peer masters in parallel and collect results',
    parameters: {
        type: 'object',
        properties: {
            tool: {
                type: 'string',
                description: 'Tool name to execute on all workers',
            },
            params: {
                type: 'object',
                description: 'Tool parameters (same for all workers)',
                default: {},
            },
            workers: {
                type: 'array',
                items: { type: 'string' },
                description: 'Specific worker names to target (empty = all available)',
                default: [],
            },
            local_only: {
                type: 'boolean',
                description: 'Only target local workers',
                default: false,
            },
        },
        required: ['tool'],
    },
    async execute(_toolCallId, params, signal, onUpdate, context) {
        const tool = params.tool;
        const taskParams = params.params || {};
        const workerFilter = params.workers || [];
        const localOnly = params.local_only === true;
        try {
            if (!agentMaster.isServing()) {
                return {
                    content: [{ type: 'text', text: 'Not serving as master. Run `agent_serve` first.' }],
                    isError: true,
                };
            }
            const registry = new AgentRegistryManager();
            registry.markOfflineAgents();
            const agents = registry.getAgents();
            // Collect targets
            const targets = [];
            // Local workers
            for (const agent of agents) {
                if (agent.type !== 'local')
                    continue;
                if (agent.status === 'offline' || agent.status === 'error')
                    continue;
                if (!agent.endpoint)
                    continue;
                if (workerFilter.length > 0 && !workerFilter.includes(agent.name))
                    continue;
                targets.push({ agent, location: 'local' });
            }
            // Peer masters and their workers
            if (!localOnly) {
                const peers = agents.filter(a => a.type === 'remote' && a.endpoint);
                for (const peer of peers) {
                    if (workerFilter.length > 0 && !workerFilter.includes(peer.name))
                        continue;
                    let peerWorkerCount = 0;
                    try {
                        const controller = new AbortController();
                        const timeoutId = setTimeout(() => controller.abort(), 3000);
                        const res = await fetch(`${peer.endpoint}/agents`, { signal: controller.signal });
                        clearTimeout(timeoutId);
                        if (res.ok) {
                            const peerAgents = await res.json();
                            for (const pa of peerAgents) {
                                if (pa.type !== 'local' || pa.status === 'offline' || pa.status === 'error')
                                    continue;
                                if (workerFilter.length > 0 && !workerFilter.includes(pa.name))
                                    continue;
                                targets.push({
                                    agent: pa,
                                    location: 'peer',
                                    peerName: peer.name,
                                    peerEndpoint: peer.endpoint,
                                });
                                peerWorkerCount++;
                            }
                        }
                    }
                    catch { }
                    // If peer has no workers, target the peer master itself
                    if (peerWorkerCount === 0) {
                        targets.push({
                            agent: peer,
                            location: 'peer',
                            peerName: peer.name,
                            peerEndpoint: peer.endpoint,
                        });
                    }
                }
            }
            if (targets.length === 0) {
                return {
                    content: [{ type: 'text', text: 'No available workers found for batch operation.' }],
                    isError: true,
                };
            }
            // Health-check all targets in parallel
            const healthChecks = await Promise.all(targets.map(async (t) => {
                const endpoint = t.agent.endpoint;
                const healthy = await checkAgentHealth(endpoint, 3000);
                return { target: t, healthy };
            }));
            const healthyTargets = healthChecks.filter(h => h.healthy).map(h => h.target);
            if (healthyTargets.length === 0) {
                return {
                    content: [{ type: 'text', text: `Found ${targets.length} worker(s) but none responded to health check.` }],
                    isError: true,
                };
            }
            // Notify start
            const workerNames = healthyTargets.map(t => t.location === 'peer' ? `${t.peerName}->${t.agent.name}` : t.agent.name);
            onUpdate?.({
                content: [{ type: 'text', text: `Dispatching ${tool} to ${healthyTargets.length} workers: ${workerNames.join(', ')}` }],
            });
            const sanitizedContext = {
                cwd: context?.cwd,
                workerWorkspace: context?.workerWorkspace,
                masterWorkspace: context?.masterWorkspace,
            };
            // Dispatch all in parallel via gRPC
            const results = await Promise.all(healthyTargets.map(t => {
                if (t.location === 'peer') {
                    return dispatchToPeerWorker(t.agent, t.peerEndpoint, t.peerName, tool, taskParams);
                }
                else {
                    return dispatchToLocalWorker(t.agent, tool, taskParams, sanitizedContext);
                }
            }));
            // Format results
            const succeeded = results.filter(r => r.status === 'completed');
            const failed = results.filter(r => r.status !== 'completed');
            let output = `# Batch Results: ${tool}\n\n`;
            output += `Workers: ${results.length} | Succeeded: ${succeeded.length} | Failed: ${failed.length}\n\n`;
            for (const r of results) {
                const icon = r.status === 'completed' ? '+' : '-';
                const readable = extractReadableResult(r.result).trim();
                const preview = readable
                    ? readable.length > 200 ? `${readable.substring(0, 200)}...` : readable
                    : '';
                output += `[${icon}] ${r.worker} (${r.status})${r.error ? ` — ${r.error}` : ''}\n`;
                if (preview) {
                    output += `    ${preview.replace(/\n/g, '\n    ')}\n`;
                }
                output += '\n';
            }
            return {
                content: [{ type: 'text', text: output }],
                details: {
                    tool,
                    total: results.length,
                    succeeded: succeeded.length,
                    failed: failed.length,
                    results,
                },
                isError: failed.length > 0 && succeeded.length === 0,
            };
        }
        catch (error) {
            log.error('agent_batch failed:', error);
            return {
                content: [{ type: 'text', text: `Batch failed: ${error instanceof Error ? error.message : String(error)}` }],
                isError: true,
            };
        }
    },
};
//# sourceMappingURL=agentBatch.js.map