import { ToolRegistry } from '../tools/registry.js';
import { registerAllTools } from '../tools/index.js';
import { registerChannelAgentTools } from '../tools/channel/agent-tools.js';
import { AgentMessageBus } from './message-bus.js';
import { logger } from '../utils/logger.js';
import { summarizeError } from '../utils/error-display.js';
import { FileStateCache } from '../state/file-cache.js';
import { ProjectIndex } from '../state/project-index.js';
import { emitAgentCancelled, emitAgentCompleted, emitAgentFailed, emitAgentProgress, emitAgentRunning, emitAgentStreamDelta, emitOrchestrationNodeCancelled, emitOrchestrationNodeCompleted, emitOrchestrationNodeFailed, emitOrchestrationNodeProgress, } from '../runtime/emitters/index.js';
import { findModelDefinition } from '../providers/registry-models.js';
import { splitModelRegistryKey } from '../providers/registry-helpers.js';
import { runAgentTask } from './orchestrator-runner.js';
export { summarizeToolArgs } from './orchestrator-utils.js';
/**
 * AgentOrchestrator — runs AgentRecord tasks in-process.
 *
 * Each agent gets its own scoped ToolRegistry containing only the tools
 * listed in record.tools. The execution loop itself now lives in
 * `orchestrator-runner.ts`; this class owns shared registry/state wiring.
 */
export class AgentOrchestrator {
    fullRegistry = null;
    fullRegistryChannelVersion = -2;
    toolDeps = null;
    featureFlagManager = null;
    runtimeBus = null;
    channelRegistry;
    messageBus;
    constructor(config = {
        messageBus: new AgentMessageBus(),
    }) {
        this.channelRegistry = config.channelRegistry ?? null;
        this.messageBus = config.messageBus;
    }
    setRuntimeBus(runtimeBus) {
        this.runtimeBus = runtimeBus;
    }
    /** Set the FeatureFlagManager for context-window awareness gating. */
    setFeatureFlagManager(manager) {
        this.featureFlagManager = manager;
    }
    emitterContext(agentId) {
        return {
            sessionId: 'agent-orchestrator',
            traceId: `agent-orchestrator:${agentId}`,
            source: 'agent-orchestrator',
        };
    }
    emitAgentProgress(recordId, progress) {
        if (!this.runtimeBus)
            return;
        emitAgentProgress(this.runtimeBus, this.emitterContext(recordId), {
            agentId: recordId,
            progress,
        });
    }
    emitOrchestrationProgress(record, progress) {
        if (!this.runtimeBus || !record.orchestrationGraphId || !record.orchestrationNodeId)
            return;
        emitOrchestrationNodeProgress(this.runtimeBus, this.emitterContext(record.id), {
            graphId: record.orchestrationGraphId,
            nodeId: record.orchestrationNodeId,
            message: progress,
        });
    }
    emitAgentStarted(recordId) {
        if (!this.runtimeBus)
            return;
        emitAgentRunning(this.runtimeBus, this.emitterContext(recordId), { agentId: recordId });
    }
    emitAgentCancelledEvent(recordId, reason) {
        if (!this.runtimeBus)
            return;
        emitAgentCancelled(this.runtimeBus, this.emitterContext(recordId), {
            agentId: recordId,
            reason,
        });
    }
    emitOrchestrationCancelled(record, reason) {
        if (!this.runtimeBus || !record.orchestrationGraphId || !record.orchestrationNodeId)
            return;
        emitOrchestrationNodeCancelled(this.runtimeBus, this.emitterContext(record.id), {
            graphId: record.orchestrationGraphId,
            nodeId: record.orchestrationNodeId,
            reason,
        });
    }
    emitAgentFailedEvent(recordId, error, durationMs) {
        if (!this.runtimeBus)
            return;
        emitAgentFailed(this.runtimeBus, this.emitterContext(recordId), {
            agentId: recordId,
            error,
            durationMs,
        });
    }
    emitOrchestrationFailed(record, error) {
        if (!this.runtimeBus || !record.orchestrationGraphId || !record.orchestrationNodeId)
            return;
        emitOrchestrationNodeFailed(this.runtimeBus, this.emitterContext(record.id), {
            graphId: record.orchestrationGraphId,
            nodeId: record.orchestrationNodeId,
            error,
        });
    }
    emitAgentCompletedEvent(recordId, durationMs, output, toolCallsMade) {
        if (!this.runtimeBus)
            return;
        emitAgentCompleted(this.runtimeBus, this.emitterContext(recordId), {
            agentId: recordId,
            durationMs,
            output,
            toolCallsMade,
        });
    }
    emitOrchestrationCompleted(record, output) {
        if (!this.runtimeBus || !record.orchestrationGraphId || !record.orchestrationNodeId)
            return;
        emitOrchestrationNodeCompleted(this.runtimeBus, this.emitterContext(record.id), {
            graphId: record.orchestrationGraphId,
            nodeId: record.orchestrationNodeId,
            summary: output.length > 120 ? `${output.slice(0, 117)}...` : output,
        });
    }
    emitStreamDelta(recordId, content, accumulated) {
        if (!this.runtimeBus || !content)
            return;
        emitAgentStreamDelta(this.runtimeBus, this.emitterContext(recordId), {
            agentId: recordId,
            content,
            accumulated,
        });
    }
    /**
     * Inject shared file-cache and project-index so agent tools share state with main session.
     * Call once during application startup, before any agents are spawned.
     */
    setDependencies(toolDeps) {
        this.toolDeps = toolDeps;
        this.fullRegistry = null;
        this.fullRegistryChannelVersion = -2;
    }
    /**
     * Returns the fully-populated ToolRegistry for this orchestrator.
     * Used by companion chat to execute tool calls emitted by the LLM.
     * Delegates to the lazy-initialized internal registry.
     */
    getToolRegistry() {
        return this.getFullRegistry();
    }
    /** Lazily build and cache the full ToolRegistry. */
    getFullRegistry() {
        const channelVersion = this.channelRegistry?.getVersion() ?? -1;
        if (!this.fullRegistry || this.fullRegistryChannelVersion !== channelVersion) {
            if (!this.toolDeps?.configManager || !this.toolDeps?.providerRegistry || !this.toolDeps?.toolLLM) {
                throw new Error('AgentOrchestrator requires configManager, providerRegistry, and toolLLM dependencies before tool registration');
            }
            this.fullRegistry = new ToolRegistry();
            registerAllTools(this.fullRegistry, this.toolDeps);
            registerChannelAgentTools(this.fullRegistry, this.toolDeps?.channelRegistry ?? this.channelRegistry);
            this.fullRegistryChannelVersion = channelVersion;
        }
        return this.fullRegistry;
    }
    /**
     * Build a ToolRegistry containing only the tools whose names appear in
     * the allowedNames list. Filters the provided full registry into a fresh
     * scoped registry.
     */
    buildScopedRegistry(allowedNames, fullRegistry) {
        const allowed = new Set(allowedNames.filter((n) => n !== 'agent'));
        const scopedRegistry = new ToolRegistry();
        for (const tool of fullRegistry.list()) {
            if (allowed.has(tool.definition.name)) {
                scopedRegistry.register(tool);
            }
        }
        return scopedRegistry;
    }
    resolveProviderForRecord(providerRegistry, record, currentModel) {
        const optimizedRoute = this.resolveOptimizedProviderRoute(providerRegistry, record);
        if (optimizedRoute)
            return optimizedRoute;
        const routing = this.resolveProviderRouting(record, currentModel, providerRegistry.listModels());
        const scopedModelId = this.normalizeRequestedModelId(routing.requestedModelId);
        try {
            return {
                provider: providerRegistry.getForModel(scopedModelId, routing.providerOverride),
                modelId: this.resolveChatModelId(providerRegistry, scopedModelId, routing.providerOverride),
                requestedModelId: scopedModelId,
            };
        }
        catch (err) {
            throw new Error(`Cannot resolve provider for model '${scopedModelId}': ${summarizeError(err)}`);
        }
    }
    resolveOptimizedProviderRoute(providerRegistry, record) {
        const optimizer = this.toolDeps?.providerOptimizer;
        if (!optimizer?.enabled || optimizer.mode === 'manual')
            return null;
        if (record.model || record.provider)
            return null;
        if (record.routing?.providerSelection === 'concrete' || record.routing?.providerSelection === 'synthetic') {
            return null;
        }
        const decision = optimizer.selectRoute(this.buildOptimizerRequestProfile(record));
        if (!decision?.explanation.accepted)
            return null;
        try {
            return {
                provider: providerRegistry.getForModel(decision.modelId, decision.providerId),
                modelId: this.resolveChatModelId(providerRegistry, decision.modelId, decision.providerId),
                requestedModelId: `${decision.providerId}:${decision.modelId}`,
            };
        }
        catch (error) {
            logger.warn('[AgentOrchestrator] provider optimizer selected an unresolved route; using default model routing', {
                agentId: record.id,
                providerId: decision.providerId,
                modelId: decision.modelId,
                error: summarizeError(error),
            });
            return null;
        }
    }
    buildOptimizerRequestProfile(record) {
        return {
            requiresToolCalling: record.tools.length > 0,
            requiresReasoningControls: record.reasoningEffort !== undefined,
        };
    }
    resolveProviderRouting(record, currentModel, modelRegistry) {
        const requestedModelId = this.normalizeRequestedModelId(record.model ?? currentModel.registryKey);
        if (record.model)
            this.assertProviderQualifiedModel(record.model, 'Agent model overrides');
        const providerFromRegistryKey = record.model
            ? findModelDefinition(record.model, modelRegistry)?.provider
            : undefined;
        if (record.provider && !record.model) {
            throw new Error('Agent provider routing requires a provider-qualified model when provider is supplied.');
        }
        if (record.provider && providerFromRegistryKey && providerFromRegistryKey !== record.provider) {
            throw new Error(`Agent model override '${record.model}' conflicts with provider '${record.provider}'.`);
        }
        const fallbackModels = (record.routing?.fallbackModels
            ?? record.fallbackModels
            ?? [])
            .filter((model) => typeof model === 'string' && model.trim().length > 0)
            .map((model) => model.trim());
        const providerSelection = record.routing?.providerSelection ?? (record.provider === 'synthetic'
            ? 'synthetic'
            : record.provider
                ? 'concrete'
                : 'inherit-current');
        const providerId = record.provider;
        const effectiveProviderId = providerSelection === 'synthetic'
            ? undefined
            : providerSelection === 'concrete'
                ? (providerId ?? providerFromRegistryKey ?? currentModel.provider)
                : (providerFromRegistryKey ?? currentModel.provider);
        const providerOverride = effectiveProviderId !== 'synthetic'
            ? effectiveProviderId
            : undefined;
        const providerFailurePolicy = record.routing?.providerFailurePolicy ?? (fallbackModels.length > 0
            ? 'ordered-fallbacks'
            : 'fail');
        if (providerFailurePolicy === 'ordered-fallbacks' && fallbackModels.length === 0) {
            throw new Error('Agent ordered fallback routing requires at least one provider-qualified fallback model.');
        }
        if (providerFailurePolicy === 'fail' && fallbackModels.length > 0) {
            throw new Error('Agent fail routing cannot include fallback models; use ordered-fallbacks to enable model failover.');
        }
        return {
            requestedModelId,
            providerSelection,
            providerFailurePolicy,
            providerId,
            providerOverride,
            fallbackModels,
        };
    }
    normalizeRequestedModelId(requestedModelId) {
        return requestedModelId.trim();
    }
    assertProviderQualifiedModel(modelId, label) {
        try {
            splitModelRegistryKey(modelId.trim());
        }
        catch {
            throw new Error(`${label} must be provider-qualified registry keys; received '${modelId}'.`);
        }
    }
    resolveChatModelId(providerRegistry, requestedModelId, providerOverride) {
        const registry = providerRegistry.listModels();
        const def = providerOverride
            ? registry.find((model) => model.provider === providerOverride &&
                (model.registryKey === requestedModelId || model.id === requestedModelId))
            : registry.find((model) => model.registryKey === requestedModelId);
        if (def)
            return def.id;
        throw new Error(`Model '${requestedModelId}' is not in registry.`);
    }
    resolveFallbackModelRoutes(providerRegistry, record, currentModel, primaryRequestedModelId) {
        const routing = this.resolveProviderRouting(record, currentModel, providerRegistry.listModels());
        if (routing.providerFailurePolicy !== 'ordered-fallbacks' || routing.fallbackModels.length === 0) {
            return [];
        }
        const seen = new Set([primaryRequestedModelId]);
        const routes = [];
        const modelRegistry = providerRegistry.listModels();
        for (const rawFallback of routing.fallbackModels) {
            const requestedModelId = this.normalizeRequestedModelId(rawFallback);
            if (!requestedModelId || seen.has(requestedModelId))
                continue;
            seen.add(requestedModelId);
            this.assertProviderQualifiedModel(requestedModelId, 'Agent fallback models');
            const fallbackDef = findModelDefinition(requestedModelId, modelRegistry);
            if (!fallbackDef) {
                throw new Error(`Agent fallback model '${requestedModelId}' is not in registry.`);
            }
            routes.push({
                provider: providerRegistry.getForModel(requestedModelId, fallbackDef.provider),
                modelId: this.resolveChatModelId(providerRegistry, requestedModelId, fallbackDef.provider),
                requestedModelId,
            });
        }
        return routes;
    }
    createRunContext() {
        return {
            workingDirectory: this.toolDeps?.workingDirectory ?? '',
            surfaceRoot: this.toolDeps?.surfaceRoot ?? '',
            runtimeBus: this.runtimeBus,
            featureFlagManager: this.featureFlagManager,
            emitterContext: (agentId) => this.emitterContext(agentId),
            emitAgentProgress: (recordId, progress) => this.emitAgentProgress(recordId, progress),
            emitOrchestrationProgress: (record, progress) => this.emitOrchestrationProgress(record, progress),
            emitAgentStarted: (recordId) => this.emitAgentStarted(recordId),
            emitAgentCancelledEvent: (recordId, reason) => this.emitAgentCancelledEvent(recordId, reason),
            emitOrchestrationCancelled: (record, reason) => this.emitOrchestrationCancelled(record, reason),
            emitAgentFailedEvent: (recordId, error, durationMs) => this.emitAgentFailedEvent(recordId, error, durationMs),
            emitOrchestrationFailed: (record, error) => this.emitOrchestrationFailed(record, error),
            emitAgentCompletedEvent: (recordId, durationMs, output, toolCallsMade) => this.emitAgentCompletedEvent(recordId, durationMs, output, toolCallsMade),
            emitOrchestrationCompleted: (record, output) => this.emitOrchestrationCompleted(record, output),
            emitStreamDelta: (recordId, content, accumulated) => this.emitStreamDelta(recordId, content, accumulated),
            processManager: this.toolDeps?.processManager,
            messageBus: this.messageBus,
            knowledgeService: this.toolDeps?.knowledgeService,
            memoryRegistry: this.toolDeps?.memoryRegistry,
            archetypeLoader: this.toolDeps?.archetypeLoader,
            providerOptimizer: this.toolDeps?.providerOptimizer,
            providerRegistry: this.toolDeps.providerRegistry,
            getFullRegistry: () => this.getFullRegistry(),
            buildScopedRegistry: (allowedNames, fullRegistry) => this.buildScopedRegistry(allowedNames, fullRegistry),
            resolveProviderForRecord: (providerRegistry, record, currentModel) => this.resolveProviderForRecord(providerRegistry, record, currentModel),
            resolveFallbackModelRoutes: (providerRegistry, record, currentModel, primaryRequestedModelId) => this.resolveFallbackModelRoutes(providerRegistry, record, currentModel, primaryRequestedModelId),
        };
    }
    /** Run an agent task described by the given record. */
    async runAgent(record) {
        await runAgentTask(this.createRunContext(), record);
    }
}
