import { EventEmitter } from 'node:events';
import { CapabilityAwareStructuredCaller } from '../../../agents/structured-caller.js';
import { createLogger, generateReportDir, isValidReportDirName } from '../../../shared/utils/index.js';
import { buildRunPaths } from '../run/run-paths.js';
import { LoopDetector } from './loop-detector.js';
import { createInitialState, addUserInput as addUserInputToState } from './state-manager.js';
import { CycleDetector } from './cycle-detector.js';
import { runSingleWorkflowIteration, runWorkflowToCompletion } from './WorkflowRunLoop.js';
import { validateWorkflowConfig } from './WorkflowValidator.js';
import { getWorkflowStepKind } from '../step-kind.js';
import { buildWorkflowResumePointEntry } from '../workflow-reference.js';
import { runWithWorkflowSpan } from '../observability/workflowSpans.js';
import { WorkflowEngineStepCoordinator } from './WorkflowEngineStepCoordinator.js';
import { applyRuntimeEnvironment, assertTaskPrefixPair, createSharedRuntime, createWorkflowEngineServices, ensureRunDirsExist, } from './WorkflowEngineSetup.js';
import { createStructuredOutputNormalizerRegistry, } from './structured-output-normalizer.js';
import { runQualityGates } from '../quality-gates/qualityGateRunner.js';
const log = createLogger('workflow-engine');
export { COMPLETE_STEP, ABORT_STEP } from '../constants.js';
const workflowRunExecutors = new WeakMap();
function getWorkflowRunExecutor(engine) {
    const executor = workflowRunExecutors.get(engine);
    if (!executor) {
        throw new Error('WorkflowEngine executor is not registered');
    }
    return executor;
}
export class WorkflowEngine extends EventEmitter {
    state;
    config;
    projectCwd;
    cwd;
    task;
    options;
    maxSteps;
    loopDetector;
    cycleDetector;
    reportDir;
    runPaths;
    abortRequested = false;
    sharedRuntime;
    resumeStackPrefix;
    optionsBuilder;
    stepExecutor;
    parallelRunner;
    arpeggioRunner;
    teamLeaderRunner;
    systemStepExecutor;
    loopMonitorJudgeRunner;
    workflowCallRunner;
    stepCoordinator;
    detectRuleIndex;
    structuredCaller;
    constructor(config, cwd, task, options) {
        super();
        assertTaskPrefixPair(options.taskPrefix, options.taskColorIndex);
        this.config = config;
        this.structuredCaller = options.structuredCaller ?? new CapabilityAwareStructuredCaller();
        this.options = {
            ...options,
            rateLimitFallback: config.rateLimitFallback ?? options.rateLimitFallback,
            structuredCaller: this.structuredCaller,
            structuredOutputNormalizers: options.structuredOutputNormalizers ?? createStructuredOutputNormalizerRegistry([]),
        };
        this.projectCwd = this.options.projectCwd;
        this.cwd = cwd;
        this.task = task;
        this.loopDetector = new LoopDetector(config.loopDetection);
        this.cycleDetector = new CycleDetector(config.loopMonitors ?? []);
        if (this.options.reportDirName !== undefined && !isValidReportDirName(this.options.reportDirName)) {
            throw new Error(`Invalid reportDirName: ${this.options.reportDirName}`);
        }
        const reportDirName = this.options.reportDirName ?? generateReportDir(task);
        const initialMaxSteps = this.options.maxStepsOverride ?? config.maxSteps;
        this.sharedRuntime = this.options.sharedRuntime ?? createSharedRuntime(this.options.resumePoint, initialMaxSteps);
        this.sharedRuntime.maxSteps ??= initialMaxSteps;
        this.maxSteps = this.sharedRuntime.maxSteps;
        this.resumeStackPrefix = this.options.resumeStackPrefix ?? [];
        this.runPaths = buildRunPaths(this.cwd, reportDirName, this.options.runPathNamespace);
        this.reportDir = this.runPaths.reportsRel;
        ensureRunDirsExist(this.runPaths);
        applyRuntimeEnvironment(this.cwd, this.config, 'init');
        validateWorkflowConfig(this.config, options);
        this.state = createInitialState(config, this.options);
        this.detectRuleIndex = this.options.detectRuleIndex ?? (() => {
            throw new Error('detectRuleIndex is required for rule evaluation');
        });
        const services = createWorkflowEngineServices({
            config: this.config,
            state: this.state,
            task: this.task,
            projectCwd: this.projectCwd,
            getCwd: () => this.cwd,
            getReportDir: () => this.reportDir,
            getRunPaths: () => this.runPaths,
            getMaxSteps: () => this.maxSteps,
            options: this.options,
            detectRuleIndex: this.detectRuleIndex,
            structuredCaller: this.structuredCaller,
            sharedRuntime: this.sharedRuntime,
            resumeStackPrefix: this.resumeStackPrefix,
            runPaths: this.runPaths,
            updateMaxSteps: (maxSteps) => {
                this.maxSteps = maxSteps;
                this.sharedRuntime.maxSteps = maxSteps;
            },
            setActiveResumePoint: this.setActiveResumePoint.bind(this),
            updatePersonaSession: this.updatePersonaSession.bind(this),
            resolveNextStepFromDone: this.resolveNextStepFromDone.bind(this),
            resetCycleDetector: () => this.cycleDetector.reset(),
            emitEvent: (event, ...args) => this.emit(event, ...args),
            createEngine: (nestedConfig, nestedCwd, nestedTask, nestedOptions) => {
                const nestedEngine = new WorkflowEngine(nestedConfig, nestedCwd, nestedTask, nestedOptions);
                return {
                    on: nestedEngine.on.bind(nestedEngine),
                    runWithResult: () => getWorkflowRunExecutor(nestedEngine)(),
                };
            },
        });
        this.optionsBuilder = services.optionsBuilder;
        this.stepExecutor = services.stepExecutor;
        this.parallelRunner = services.parallelRunner;
        this.arpeggioRunner = services.arpeggioRunner;
        this.teamLeaderRunner = services.teamLeaderRunner;
        this.systemStepExecutor = services.systemStepExecutor;
        this.loopMonitorJudgeRunner = services.loopMonitorJudgeRunner;
        this.workflowCallRunner = services.workflowCallRunner;
        this.stepCoordinator = new WorkflowEngineStepCoordinator({
            config: this.config,
            state: this.state,
            task: this.task,
            getMaxSteps: () => this.maxSteps,
            getOptions: () => this.options,
            stepExecutor: this.stepExecutor,
            parallelRunner: this.parallelRunner,
            arpeggioRunner: this.arpeggioRunner,
            teamLeaderRunner: this.teamLeaderRunner,
            systemStepExecutor: this.systemStepExecutor,
            loopMonitorJudgeRunner: this.loopMonitorJudgeRunner,
            workflowCallRunner: this.workflowCallRunner,
            updatePersonaSession: this.updatePersonaSession.bind(this),
            emitReport: (step, filePath, fileName) => this.emit('step:report', step, filePath, fileName),
        });
        workflowRunExecutors.set(this, () => this.runWithSystemCleanup(() => runWithWorkflowSpan(this.buildWorkflowSpanParams('full'), () => runWorkflowToCompletion({
            state: this.state,
            options: this.options,
            getWorkflowName: () => this.config.name,
            getCurrentWorkflowStack: () => this.sharedRuntime.activeResumePoint?.stack,
            getCwd: () => this.cwd,
            getMaxSteps: () => this.maxSteps,
            getReportDir: () => this.runPaths.reportsAbs,
            abortRequested: () => this.abortRequested,
            getStep: this.stepCoordinator.getStep.bind(this.stepCoordinator),
            applyRuntimeEnvironment: (stage) => applyRuntimeEnvironment(this.cwd, this.config, stage),
            loopDetectorCheck: (stepName) => {
                const result = this.loopDetector.check(stepName);
                return {
                    shouldWarn: result.shouldWarn ?? false,
                    shouldAbort: result.shouldAbort ?? false,
                    count: result.count,
                    isLoop: result.isLoop,
                };
            },
            cycleDetectorRecordAndCheck: (stepName) => this.cycleDetector.recordAndCheck(stepName),
            resolveDoneTransition: this.stepCoordinator.resolveTransitionFromDone.bind(this.stepCoordinator),
            runLoopMonitorJudge: this.stepCoordinator.runLoopMonitorJudge.bind(this.stepCoordinator),
            runStep: this.stepCoordinator.runStep.bind(this.stepCoordinator),
            runQualityGates,
            persistPreviousResponseSnapshot: this.stepExecutor.persistPreviousResponseSnapshot.bind(this.stepExecutor),
            buildInstruction: this.stepCoordinator.buildInstruction.bind(this.stepCoordinator),
            buildPhase1Instruction: this.stepCoordinator.buildPhase1Instruction.bind(this.stepCoordinator),
            resolveStepProviderModel: (step, runtime) => this.optionsBuilder.resolveStepProviderModel(step, runtime),
            resolveRuntimeForStep: this.stepCoordinator.resolveRuntimeForStep.bind(this.stepCoordinator),
            setActiveStep: this.setActiveResumePoint.bind(this),
            addUserInput: this.addUserInput.bind(this),
            emit: (event, ...args) => this.emit(event, ...args),
            updateMaxSteps: (maxSteps) => {
                this.maxSteps = maxSteps;
                this.sharedRuntime.maxSteps = maxSteps;
            },
        }), (result) => ({
            status: result.state.status,
            abortKind: result.abort?.kind,
            abortReason: result.abort?.reason,
            iterations: result.state.iteration,
        })), () => true));
        log.debug('WorkflowEngine initialized', {
            workflow: config.name,
            steps: config.steps.map((step) => step.name),
            initialStep: config.initialStep,
            maxSteps: config.maxSteps,
            effectiveMaxSteps: this.maxSteps,
        });
    }
    getState() {
        return { ...this.state };
    }
    buildResumePoint(step, iteration) {
        return {
            version: 1,
            stack: [...this.resumeStackPrefix, buildWorkflowResumePointEntry(this.config, step.name, getWorkflowStepKind(step))],
            iteration,
            elapsed_ms: Date.now() - this.sharedRuntime.startedAtMs,
        };
    }
    setActiveResumePoint(step, iteration) {
        this.sharedRuntime.activeResumePoint = this.buildResumePoint(step, iteration);
    }
    getResumePoint() {
        return this.sharedRuntime.activeResumePoint;
    }
    buildResumePointForStepName(stepName) {
        const step = this.config.steps.find((candidate) => candidate.name === stepName);
        return step ? this.buildResumePoint(step, this.state.iteration) : undefined;
    }
    addUserInput(input) {
        addUserInputToState(this.state, input);
    }
    updateCwd(newCwd) { this.cwd = newCwd; }
    getCwd() { return this.cwd; }
    getProjectCwd() { return this.projectCwd; }
    abort() {
        if (this.abortRequested)
            return;
        this.abortRequested = true;
        log.info('Abort requested');
    }
    isAbortRequested() { return this.abortRequested; }
    updatePersonaSession(persona, sessionId) {
        if (!sessionId)
            return;
        const previousSessionId = this.state.personaSessions.get(persona);
        this.state.personaSessions.set(persona, sessionId);
        if (this.options.onSessionUpdate && sessionId !== previousSessionId) {
            this.options.onSessionUpdate(persona, sessionId);
        }
    }
    resolveNextStepFromDone(step, response) {
        return this.stepCoordinator.resolveNextStepFromDone(step, response);
    }
    finalizeSystemStepResources() {
        this.systemStepExecutor.cleanup();
    }
    buildWorkflowSpanParams(runMode) {
        return {
            enabled: this.options.observability?.enabled === true,
            runId: this.options.observabilityRunId,
            workflowName: this.config.name,
            initialStep: this.config.initialStep,
            stepCount: this.config.steps.length,
            maxSteps: this.maxSteps,
            runMode,
            resumeDepth: this.resumeStackPrefix.length,
            sanitizeText: this.options.sanitizeObservabilityText,
        };
    }
    async runWithSystemCleanup(execute, shouldCleanup) {
        let result;
        let error;
        try {
            result = await execute();
            return result;
        }
        catch (caughtError) {
            error = caughtError;
            throw caughtError;
        }
        finally {
            if (shouldCleanup(result, error)) {
                this.finalizeSystemStepResources();
            }
        }
    }
    async run() {
        const result = await getWorkflowRunExecutor(this)();
        return result.state;
    }
    async runSingleIteration() {
        return this.runWithSystemCleanup(() => runWithWorkflowSpan(this.buildWorkflowSpanParams('single_iteration'), () => runSingleWorkflowIteration({
            state: this.state,
            options: this.options,
            getWorkflowName: () => this.config.name,
            getCurrentWorkflowStack: () => this.sharedRuntime.activeResumePoint?.stack,
            getCwd: () => this.cwd,
            getMaxSteps: () => this.maxSteps,
            getReportDir: () => this.runPaths.reportsAbs,
            abortRequested: () => this.abortRequested,
            getStep: this.stepCoordinator.getStep.bind(this.stepCoordinator),
            applyRuntimeEnvironment: (stage) => applyRuntimeEnvironment(this.cwd, this.config, stage),
            loopDetectorCheck: (stepName) => {
                const loopResult = this.loopDetector.check(stepName);
                return {
                    shouldWarn: loopResult.shouldWarn ?? false,
                    shouldAbort: loopResult.shouldAbort ?? false,
                    count: loopResult.count,
                    isLoop: loopResult.isLoop,
                };
            },
            cycleDetectorRecordAndCheck: (stepName) => this.cycleDetector.recordAndCheck(stepName),
            resolveDoneTransition: this.stepCoordinator.resolveTransitionFromDone.bind(this.stepCoordinator),
            runLoopMonitorJudge: this.stepCoordinator.runLoopMonitorJudge.bind(this.stepCoordinator),
            runStep: this.stepCoordinator.runStep.bind(this.stepCoordinator),
            runQualityGates,
            persistPreviousResponseSnapshot: this.stepExecutor.persistPreviousResponseSnapshot.bind(this.stepExecutor),
            buildInstruction: this.stepCoordinator.buildInstruction.bind(this.stepCoordinator),
            buildPhase1Instruction: this.stepCoordinator.buildPhase1Instruction.bind(this.stepCoordinator),
            resolveStepProviderModel: (step, runtime) => this.optionsBuilder.resolveStepProviderModel(step, runtime),
            resolveRuntimeForStep: this.stepCoordinator.resolveRuntimeForStep.bind(this.stepCoordinator),
            setActiveStep: this.setActiveResumePoint.bind(this),
            addUserInput: this.addUserInput.bind(this),
            emit: (event, ...args) => this.emit(event, ...args),
            updateMaxSteps: () => { },
        }), (result) => ({
            status: result.isComplete ? this.state.status : 'running',
            nextStep: result.nextStep,
            iterations: this.state.iteration,
        })), (result, error) => error !== undefined || result?.isComplete === true || this.state.status !== 'running');
    }
}
//# sourceMappingURL=WorkflowEngine.js.map