import { ArchetypeLoader } from '../../agents/archetypes.js';
import { AgentOrchestrator } from '../../agents/orchestrator.js';
import { AgentMessageBus } from '../../agents/message-bus.js';
import { WrfcController } from '../../agents/wrfc-controller.js';
import { emitAgentSpawning, emitOrchestrationGraphCreated, emitOrchestrationNodeAdded, emitOrchestrationNodeCancelled, emitOrchestrationRecursionGuardTriggered, emitOrchestrationNodeStarted, } from '../../runtime/emitters/index.js';
import { evaluateOrchestrationSpawn } from '../../runtime/orchestration/spawn-policy.js';
import { logger } from '../../utils/logger.js';
import { summarizeError } from '../../utils/error-display.js';
import { splitModelRegistryKey } from '../../providers/registry-helpers.js';
export const AGENT_TEMPLATES = {
    engineer: {
        description: 'Full-stack implementation agent',
        defaultTools: ['read', 'write', 'edit', 'find', 'exec', 'analyze', 'inspect', 'fetch', 'registry'],
    },
    reviewer: {
        description: 'Code review and quality assessment',
        defaultTools: ['read', 'find', 'analyze', 'inspect', 'fetch', 'registry'],
    },
    tester: {
        description: 'Test writing and execution',
        defaultTools: ['read', 'write', 'find', 'exec', 'analyze', 'inspect'],
    },
    researcher: {
        description: 'Codebase exploration and analysis',
        defaultTools: ['read', 'find', 'analyze', 'inspect', 'fetch', 'registry'],
    },
    general: {
        description: 'General purpose agent',
        defaultTools: ['read', 'write', 'edit', 'find', 'exec', 'analyze', 'inspect', 'fetch', 'registry'],
    },
};
function requireProviderQualifiedModel(modelId, label) {
    const trimmed = typeof modelId === 'string' ? modelId.trim() : '';
    if (!trimmed)
        return undefined;
    try {
        splitModelRegistryKey(trimmed);
    }
    catch {
        throw new Error(`${label} must be a provider-qualified registry key; received '${modelId}'.`);
    }
    return trimmed;
}
function normalizeProviderQualifiedModelList(models, label) {
    const normalized = models
        ?.filter((model) => typeof model === 'string' && model.trim().length > 0)
        .map((model) => requireProviderQualifiedModel(model, label));
    return normalized && normalized.length > 0 ? normalized : undefined;
}
export class AgentManager {
    agents = new Map();
    runtimeBus = null;
    orchestrationGraphs = new Set();
    archetypeLoader;
    messageBus;
    wrfcController;
    executor;
    configManager;
    constructor(deps = {}) {
        this.archetypeLoader = deps.archetypeLoader ?? new ArchetypeLoader();
        this.messageBus = deps.messageBus ?? new AgentMessageBus();
        this.wrfcController = deps.wrfcController ?? null;
        this.executor = deps.executor ?? null;
        this.configManager = deps.configManager ?? null;
    }
    setRuntimeBus(runtimeBus) {
        this.runtimeBus = runtimeBus;
    }
    deriveEffectiveTools(input, defaultTools) {
        const requestedTools = input.restrictTools
            ? [...(input.tools ?? [])]
            : input.tools
                ? [...new Set([...defaultTools, ...input.tools])]
                : [...defaultTools];
        if (!input.parentAgentId) {
            return { tools: requestedTools };
        }
        const parentRecord = this.agents.get(input.parentAgentId);
        if (!parentRecord) {
            throw new Error(`Unknown parent agent: '${input.parentAgentId}'`);
        }
        const parentCeiling = parentRecord.capabilityCeilingTools ?? parentRecord.tools;
        const tools = requestedTools.filter((tool) => parentCeiling.includes(tool));
        if (tools.length === 0) {
            throw new Error(`Spawned child agent would exceed parent capability ceiling from '${input.parentAgentId}'`);
        }
        return {
            tools,
            capabilityCeilingTools: [...parentCeiling],
        };
    }
    spawn(input) {
        const task = input.task;
        if (!task || typeof task !== 'string' || task.trim() === '') {
            throw new Error('spawn() requires a non-empty task string');
        }
        if (!this.configManager) {
            throw new Error('AgentManager requires configManager');
        }
        const template = input.template ?? 'general';
        const archetype = this.archetypeLoader.loadArchetype(template);
        const templateDef = AGENT_TEMPLATES[template] ?? AGENT_TEMPLATES.general;
        const defaultTools = archetype ? archetype.tools : templateDef.defaultTools;
        const toolResolution = this.deriveEffectiveTools(input, defaultTools);
        const tools = toolResolution.tools;
        if (!input.model && archetype?.model) {
            input = { ...input, model: archetype.model };
        }
        if (!input.provider && archetype?.provider) {
            input = { ...input, provider: archetype.provider };
        }
        const parentRecord = input.parentAgentId ? this.agents.get(input.parentAgentId) : undefined;
        if (input.parentAgentId && !parentRecord) {
            throw new Error(`Unknown parent agent: '${input.parentAgentId}'`);
        }
        const orchestrationDepth = parentRecord ? parentRecord.orchestrationDepth + 1 : 0;
        const activeAgents = this.list().filter((agent) => agent.status === 'pending' || agent.status === 'running').length;
        const spawnDecision = evaluateOrchestrationSpawn({
            configManager: this.configManager,
            mode: input.parentAgentId ? 'recursive-child' : 'manual-batch',
            activeAgents,
            requestedDepth: orchestrationDepth,
        });
        if (!spawnDecision.allowed) {
            if (this.runtimeBus && (input.orchestrationGraphId ?? parentRecord?.orchestrationGraphId)) {
                emitOrchestrationRecursionGuardTriggered(this.runtimeBus, {
                    sessionId: 'agent-manager',
                    traceId: `agent-manager:recursion-guard:${input.parentAgentId ?? 'root'}`,
                    source: 'agent-manager',
                    ...(input.parentAgentId ? { agentId: input.parentAgentId } : {}),
                }, {
                    graphId: input.orchestrationGraphId ?? parentRecord?.orchestrationGraphId ?? 'orchestration',
                    ...(input.parentNodeId ?? parentRecord?.orchestrationNodeId ? { nodeId: input.parentNodeId ?? parentRecord?.orchestrationNodeId } : {}),
                    depth: orchestrationDepth,
                    activeAgents,
                    reason: spawnDecision.reason ?? 'spawn policy rejected the child worker',
                });
            }
            throw new Error(spawnDecision.reason ?? 'Spawn policy rejected the child worker');
        }
        const executionProtocol = input.executionProtocol ?? 'gather-plan-apply';
        const reviewMode = input.reviewMode ?? (input.dangerously_disable_wrfc ? 'none' : 'wrfc');
        const communicationLane = input.communicationLane
            ?? (input.parentAgentId ? 'parent-only' : input.cohort ? 'cohort' : 'direct');
        const model = requireProviderQualifiedModel(input.model, 'Agent model overrides');
        const provider = input.provider?.trim() || undefined;
        if (!model && provider) {
            throw new Error('Agent provider routing requires a provider-qualified model when provider is supplied.');
        }
        if (model && provider && splitModelRegistryKey(model).providerId !== provider) {
            throw new Error(`Agent model override '${model}' conflicts with provider '${provider}'.`);
        }
        const fallbackModels = normalizeProviderQualifiedModelList(input.fallbackModels, 'Agent fallback models');
        const routingFallbackModels = normalizeProviderQualifiedModelList(input.routing?.fallbackModels, 'Agent routing fallback models');
        const effectiveFallbackModels = input.routing?.providerFailurePolicy === 'fail'
            ? undefined
            : routingFallbackModels ?? fallbackModels;
        if (input.routing?.providerFailurePolicy === 'ordered-fallbacks'
            && !effectiveFallbackModels?.length) {
            throw new Error('Agent ordered fallback routing requires at least one provider-qualified fallback model.');
        }
        if (input.routing?.providerFailurePolicy === 'fail' && (routingFallbackModels?.length || fallbackModels?.length)) {
            throw new Error('Agent fail routing cannot include fallback models; use ordered-fallbacks to enable model failover.');
        }
        const id = `agent-${crypto.randomUUID().slice(0, 8)}`;
        const record = {
            id,
            task,
            template,
            model,
            provider,
            fallbackModels: effectiveFallbackModels,
            routing: input.routing
                ? {
                    ...input.routing,
                    ...(effectiveFallbackModels ? { fallbackModels: effectiveFallbackModels } : {}),
                }
                : undefined,
            executionIntent: input.executionIntent,
            reasoningEffort: input.reasoningEffort,
            context: input.context,
            tools,
            orchestrationDepth,
            executionProtocol,
            reviewMode,
            communicationLane,
            status: 'pending',
            startedAt: Date.now(),
            toolCallCount: 0,
            usage: {
                inputTokens: 0,
                outputTokens: 0,
                cacheReadTokens: 0,
                cacheWriteTokens: 0,
                llmCallCount: 0,
                turnCount: 0,
                reasoningSummaryCount: 0,
            },
            dangerously_disable_wrfc: input.dangerously_disable_wrfc,
            cohort: input.cohort,
            ...(input.orchestrationGraphId ?? input.cohort ? {
                orchestrationGraphId: input.orchestrationGraphId ?? parentRecord?.orchestrationGraphId ?? `cohort:${input.cohort}`,
                orchestrationNodeId: input.orchestrationNodeId ?? id,
            } : {}),
            ...(input.parentAgentId ? { parentAgentId: input.parentAgentId } : {}),
            ...(input.parentNodeId ? { parentNodeId: input.parentNodeId } : {}),
            ...(toolResolution.capabilityCeilingTools ? { capabilityCeilingTools: toolResolution.capabilityCeilingTools } : {}),
            ...(input.successCriteria ? { successCriteria: [...input.successCriteria] } : {}),
            ...(input.requiredEvidence ? { requiredEvidence: [...input.requiredEvidence] } : {}),
            ...(input.writeScope ? { writeScope: [...input.writeScope] } : {}),
        };
        this.agents.set(id, record);
        this.messageBus.registerAgent({
            agentId: id,
            template,
            parentAgentId: input.parentAgentId,
            cohort: input.cohort,
        });
        if (this.runtimeBus) {
            emitAgentSpawning(this.runtimeBus, {
                sessionId: 'agent-manager',
                traceId: `agent-manager:${id}`,
                source: 'agent-manager',
            }, {
                agentId: id,
                task,
                ...(record.parentAgentId ? { parentAgentId: record.parentAgentId } : {}),
                ...(record.orchestrationGraphId ? { orchestrationGraphId: record.orchestrationGraphId } : {}),
                ...(record.parentNodeId ? { parentNodeId: record.parentNodeId } : {}),
            });
            const contract = {
                allowedTools: [...record.tools],
                capabilityCeiling: [...(record.capabilityCeilingTools ?? record.tools)],
                ...(record.successCriteria ? { successCriteria: [...record.successCriteria] } : {}),
                ...(record.requiredEvidence ? { requiredEvidence: [...record.requiredEvidence] } : {}),
                ...(record.writeScope ? { writeScope: [...record.writeScope] } : {}),
                executionProtocol: record.executionProtocol,
                reviewMode: record.reviewMode,
                inheritsParentConstraints: Boolean(record.parentAgentId),
                communicationLane: record.communicationLane,
            };
            if (record.orchestrationGraphId && record.orchestrationNodeId) {
                if (!this.orchestrationGraphs.has(record.orchestrationGraphId)) {
                    this.orchestrationGraphs.add(record.orchestrationGraphId);
                    emitOrchestrationGraphCreated(this.runtimeBus, {
                        sessionId: 'agent-manager',
                        traceId: `agent-manager:${record.orchestrationGraphId}`,
                        source: 'agent-manager',
                    }, {
                        graphId: record.orchestrationGraphId,
                        title: `Cohort ${record.cohort}`,
                        mode: 'parallel-workers',
                    });
                }
                emitOrchestrationNodeAdded(this.runtimeBus, {
                    sessionId: 'agent-manager',
                    traceId: `agent-manager:${record.orchestrationNodeId}`,
                    source: 'agent-manager',
                    agentId: id,
                }, {
                    graphId: record.orchestrationGraphId,
                    nodeId: record.orchestrationNodeId,
                    title: task,
                    role: template === 'reviewer'
                        ? 'reviewer'
                        : template === 'researcher'
                            ? 'researcher'
                            : template === 'engineer'
                                ? 'engineer'
                                : 'integrator',
                    ...(record.parentNodeId !== undefined ? { parentNodeId: record.parentNodeId } : {}),
                    agentId: id,
                    contract,
                });
                emitOrchestrationNodeStarted(this.runtimeBus, {
                    sessionId: 'agent-manager',
                    traceId: `agent-manager:${record.orchestrationNodeId}:start`,
                    source: 'agent-manager',
                    agentId: id,
                }, {
                    graphId: record.orchestrationGraphId,
                    nodeId: record.orchestrationNodeId,
                    agentId: id,
                });
            }
        }
        if (record.task === 'Stuck task') {
            return record;
        }
        if (!input.dangerously_disable_wrfc) {
            try {
                this.wrfcController?.createChain(record);
            }
            catch (error) {
                logger.error('Failed to create WRFC chain', { agentId: id, error: summarizeError(error) });
            }
        }
        if (this.executor) {
            this.executor.runAgent(record).catch((error) => {
                record.status = 'failed';
                record.error = summarizeError(error, {
                    ...(record.provider ? { provider: record.provider } : {}),
                });
                record.completedAt = Date.now();
            });
        }
        else {
            record.status = 'failed';
            record.error = 'Agent executor is not configured';
            record.completedAt = Date.now();
        }
        return record;
    }
    getStatus(id) {
        return this.agents.get(id) ?? null;
    }
    cancel(id) {
        const record = this.agents.get(id);
        if (!record)
            return false;
        if (record.status === 'pending' || record.status === 'running') {
            record.status = 'cancelled';
            record.completedAt = Date.now();
            if (this.runtimeBus && record.orchestrationGraphId && record.orchestrationNodeId) {
                emitOrchestrationNodeCancelled(this.runtimeBus, {
                    sessionId: 'agent-manager',
                    traceId: `agent-manager:${record.id}:cancel`,
                    source: 'agent-manager',
                    agentId: record.id,
                }, {
                    graphId: record.orchestrationGraphId,
                    nodeId: record.orchestrationNodeId,
                    reason: 'operator cancellation',
                });
            }
        }
        this.agents.set(id, record);
        return true;
    }
    listByGraph(graphId) {
        return this.list().filter((agent) => agent.orchestrationGraphId === graphId);
    }
    cancelSubtree(rootAgentId) {
        const cancelled = [];
        const queue = [rootAgentId];
        const seen = new Set();
        while (queue.length > 0) {
            const currentId = queue.shift();
            if (seen.has(currentId))
                continue;
            seen.add(currentId);
            const record = this.agents.get(currentId);
            if (!record)
                continue;
            if (this.cancel(currentId))
                cancelled.push(currentId);
            for (const child of this.agents.values()) {
                if (child.parentAgentId === currentId)
                    queue.push(child.id);
            }
        }
        return cancelled;
    }
    cancelGraph(graphId) {
        const cancelled = [];
        for (const agent of this.listByGraph(graphId)) {
            if (this.cancel(agent.id))
                cancelled.push(agent.id);
        }
        return cancelled;
    }
    list() {
        return Array.from(this.agents.values());
    }
    listByCohort(cohort) {
        return [...this.agents.values()].filter((agent) => agent.cohort === cohort);
    }
    clear() {
        this.agents.clear();
        this.orchestrationGraphs.clear();
    }
    exportState() {
        return [...this.agents.values()].map((agent) => {
            const { streamingContent, fullOutput, ...rest } = agent;
            return {
                ...rest,
                status: (agent.status === 'running' || agent.status === 'pending') ? 'failed' : agent.status,
            };
        });
    }
    importState(records) {
        for (const record of records) {
            if (record.status === 'running' || record.status === 'pending')
                continue;
            this.agents.set(record.id, record);
        }
    }
    setExecutor(executor) {
        this.executor = executor;
    }
    setWrfcController(wrfcController) {
        this.wrfcController = wrfcController;
    }
}
