import { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools/registry';
import { registerAllTools } from '../tools/index.js';
import { registerChannelAgentTools } from '../tools/channel/agent-tools.js';
import { AgentMessageBus } from './message-bus.js';
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
import { FileStateCache } from '@pellux/goodvibes-sdk/platform/state/file-cache';
import { ProjectIndex } from '@pellux/goodvibes-sdk/platform/state/project-index';
import { emitAgentCancelled, emitAgentCompleted, emitAgentFailed, emitAgentProgress, emitAgentRunning, emitAgentStreamDelta, emitOrchestrationNodeCancelled, emitOrchestrationNodeCompleted, emitOrchestrationNodeFailed, emitOrchestrationNodeProgress, } from '../runtime/emitters/index.js';
import { splitModelRegistryKey } from '../providers/registry-helpers.js';
import { runAgentTask } from './orchestrator-runner.js';
export { summarizeToolArgs } from '@pellux/goodvibes-sdk/platform/agents/orchestrator-utils';
/**
 * 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;
    }
    /** 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 routing = this.resolveProviderRouting(record, currentModel);
        const scopedModelId = this.normalizeRequestedModelId(routing.requestedModelId, routing.providerOverride);
        try {
            return {
                provider: providerRegistry.getForModel(scopedModelId, routing.providerOverride),
                modelId: this.resolveChatModelId(providerRegistry, scopedModelId, routing.providerOverride),
                requestedModelId: scopedModelId,
            };
        }
        catch (err) {
            if (routing.requestedModelId !== currentModel.id && routing.unresolvedModelPolicy === 'fallback-to-current') {
                logger.debug(`[AgentOrchestrator] Requested model '${routing.requestedModelId}' not found, falling back to '${currentModel.id}'`);
                try {
                    return {
                        provider: providerRegistry.getForModel(currentModel.id, currentModel.provider),
                        modelId: this.resolveChatModelId(providerRegistry, currentModel.id, currentModel.provider),
                        requestedModelId: currentModel.id,
                    };
                }
                catch (fallbackErr) {
                    throw new Error(`Cannot resolve provider for model '${routing.requestedModelId}' (${summarizeError(err)}) or fallback '${currentModel.id}' (${summarizeError(fallbackErr)})`);
                }
            }
            throw new Error(`Cannot resolve provider for model '${scopedModelId}': ${summarizeError(err)}`);
        }
    }
    resolveProviderRouting(record, currentModel) {
        const requestedModelId = record.model ?? currentModel.id;
        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 ?? currentModel.provider)
                : currentModel.provider;
        const providerOverride = effectiveProviderId !== 'synthetic'
            ? effectiveProviderId
            : undefined;
        const unresolvedModelPolicy = record.routing?.unresolvedModelPolicy ?? (providerOverride && providerOverride !== currentModel.provider
            ? 'fail'
            : 'fallback-to-current');
        return {
            requestedModelId,
            providerSelection,
            unresolvedModelPolicy,
            providerFailurePolicy: record.routing?.providerFailurePolicy ?? (fallbackModels.length > 0
                ? 'ordered-fallbacks'
                : 'fail'),
            providerId,
            providerOverride,
            fallbackModels,
        };
    }
    normalizeRequestedModelId(requestedModelId, providerOverride) {
        if (!providerOverride || !requestedModelId.includes(':')) {
            return requestedModelId;
        }
        return splitModelRegistryKey(requestedModelId).resolvedModelId;
    }
    resolveChatModelId(providerRegistry, requestedModelId, providerOverride) {
        const registry = providerRegistry.listModels();
        const def = requestedModelId.includes(':')
            ? providerOverride
                ? registry.find((model) => model.registryKey === requestedModelId && model.provider === providerOverride)
                    ?? registry.find((model) => model.id === splitModelRegistryKey(requestedModelId).resolvedModelId && model.provider === providerOverride)
                : registry.find((model) => model.registryKey === requestedModelId)
                    ?? registry.find((model) => model.id === requestedModelId)
                    ?? registry.find((model) => model.id === splitModelRegistryKey(requestedModelId).resolvedModelId)
            : providerOverride
                ? registry.find((model) => model.id === requestedModelId && model.provider === providerOverride)
                : registry.find((model) => model.id === requestedModelId);
        if (def)
            return def.id;
        return requestedModelId;
    }
    resolveFallbackModelRoutes(providerRegistry, record, currentModel, primaryRequestedModelId) {
        const routing = this.resolveProviderRouting(record, currentModel);
        if (routing.providerFailurePolicy !== 'ordered-fallbacks' || routing.fallbackModels.length === 0) {
            return [];
        }
        const seen = new Set([primaryRequestedModelId]);
        const routes = [];
        for (const rawFallback of routing.fallbackModels) {
            const requestedModelId = this.normalizeRequestedModelId(rawFallback, routing.providerOverride);
            if (!requestedModelId || seen.has(requestedModelId))
                continue;
            seen.add(requestedModelId);
            try {
                routes.push({
                    provider: providerRegistry.getForModel(requestedModelId, routing.providerOverride),
                    modelId: this.resolveChatModelId(providerRegistry, requestedModelId, routing.providerOverride),
                    requestedModelId,
                });
            }
            catch (error) {
                logger.warn('[AgentOrchestrator] Ignoring unresolved fallback model', {
                    agentId: record.id,
                    modelId: requestedModelId,
                    error: summarizeError(error),
                });
            }
        }
        return routes;
    }
    createRunContext() {
        return {
            workingDirectory: this.toolDeps.workingDirectory,
            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);
    }
}
