/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { createInterface } from 'node:readline';
import { Messages } from '@salesforce/core';
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
import { PHASE_SEQUENCE, PHASE_TO_PROFILE, advanceWorkflow, createWorkflowRun, initPhase, resolvePhaseSequence, } from '../../../services/workflow-engine.js';
import { autoExpandProfiles } from '../../../services/profile-expand.js';
import { getTask } from '../../../services/task-state.js';
import { readWorkspaceConfig } from '../../../services/workspace-config.js';
import { ensureRuntime } from '../../../services/dependency-prompt.js';
import { buildInvocation, collectSubagentCapabilitySignals, detectRuntime, } from '../../../services/workflow-executor.js';
import { addOperationalLesson } from '../../../services/workflow-lessons.js';
import { runDelegatedWorkflow } from '../../../services/workflow-orchestrator.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@jterrats/setup-agents', 'setup-agents.workflow.execute');
const GATE_MODES = ['none', 'phase', 'all'];
const RUNTIME_OPTIONS = ['auto', 'claude', 'cursor', 'codex'];
export default class WorkflowExecute extends SfCommand {
    static summary = messages.getMessage('summary');
    static description = messages.getMessage('description');
    static examples = messages.getMessages('examples');
    static flags = {
        story: Flags.string({
            char: 's',
            summary: messages.getMessage('flags.story.summary'),
            description: messages.getMessage('flags.story.description'),
            required: true,
        }),
        runtime: Flags.string({
            summary: messages.getMessage('flags.runtime.summary'),
            description: messages.getMessage('flags.runtime.description'),
            options: [...RUNTIME_OPTIONS],
            default: 'auto',
        }),
        'from-phase': Flags.string({
            summary: messages.getMessage('flags.from-phase.summary'),
            description: messages.getMessage('flags.from-phase.description'),
            options: [...PHASE_SEQUENCE],
        }),
        'dry-run': Flags.boolean({
            summary: messages.getMessage('flags.dry-run.summary'),
            description: messages.getMessage('flags.dry-run.description'),
            default: false,
        }),
        gates: Flags.string({
            summary: messages.getMessage('flags.gates.summary'),
            description: messages.getMessage('flags.gates.description'),
            options: [...GATE_MODES],
            default: 'phase',
        }),
        'max-iterations': Flags.integer({
            summary: messages.getMessage('flags.max-iterations.summary'),
            description: messages.getMessage('flags.max-iterations.description'),
            default: 5,
            min: 1,
        }),
        'max-phase-minutes': Flags.integer({
            summary: messages.getMessage('flags.max-phase-minutes.summary'),
            description: messages.getMessage('flags.max-phase-minutes.description'),
            default: 60,
            min: 1,
        }),
        'max-agents': Flags.integer({
            summary: messages.getMessage('flags.max-agents.summary'),
            description: messages.getMessage('flags.max-agents.description'),
            default: 5,
            min: 1,
        }),
        'capability-signals': Flags.string({
            summary: messages.getMessage('flags.capability-signals.summary'),
            description: messages.getMessage('flags.capability-signals.description'),
            helpValue: 'qa evidence,release evidence',
        }),
    };
    async run() {
        const { flags } = await this.parse(WorkflowExecute);
        const cwd = process.cwd();
        const storyId = flags.story;
        const gates = flags.gates;
        const fromPhase = flags['from-phase'];
        const isDryRun = flags['dry-run'];
        const maxIterations = flags['max-iterations'];
        const maxPhaseMinutes = flags['max-phase-minutes'];
        const maxAgents = flags['max-agents'];
        const manualCapabilitySignals = parseCapabilitySignals(flags['capability-signals']);
        const resolvedRuntime = await this.resolveRuntime(flags.runtime, isDryRun);
        const config = readWorkspaceConfig(cwd);
        const task = getTask(cwd, storyId);
        this.tryAutoExpandTaskRoles(cwd, config, task);
        const phaseOverrides = task?.phaseOverrides;
        let phaseSequence = config
            ? resolvePhaseSequence(config.profiles, task?.roles, phaseOverrides)
            : [...PHASE_SEQUENCE];
        if (fromPhase && !phaseSequence.includes(fromPhase)) {
            const canonicalIndex = PHASE_SEQUENCE.indexOf(fromPhase);
            if (canonicalIndex >= 0) {
                const insertAt = phaseSequence.findIndex((p) => PHASE_SEQUENCE.indexOf(p) > canonicalIndex);
                if (insertAt >= 0) {
                    phaseSequence = [...phaseSequence.slice(0, insertAt), fromPhase, ...phaseSequence.slice(insertAt)];
                }
                else {
                    phaseSequence = [...phaseSequence, fromPhase];
                }
            }
        }
        const startIndex = fromPhase ? phaseSequence.indexOf(fromPhase) : 0;
        const phases = phaseSequence.slice(startIndex >= 0 ? startIndex : 0);
        if (isDryRun) {
            return this.dryRun(cwd, storyId, resolvedRuntime, phases, gates, maxPhaseMinutes, manualCapabilitySignals);
        }
        return this.execute({
            cwd,
            storyId,
            runtime: resolvedRuntime,
            phases,
            gates,
            maxIterations,
            maxPhaseMinutes,
            maxAgents,
        });
    }
    async resolveRuntime(runtimeFlag, isDryRun) {
        let resolved = runtimeFlag === 'auto'
            ? detectRuntime()
            : runtimeFlag;
        if (!resolved) {
            const installed = await ensureRuntime('workflow execute', { skipPrompt: isDryRun });
            if (installed) {
                resolved = installed;
            }
            else {
                this.error(messages.getMessage('info.no-runtime'));
            }
        }
        return resolved;
    }
    tryAutoExpandTaskRoles(cwd, config, task) {
        if (!config || !task?.roles)
            return;
        const allRoles = [...task.roles.required, ...task.roles.optional];
        const configSet = new Set(config.profiles);
        const missingFromConfig = allRoles.filter((r) => !configSet.has(r));
        if (missingFromConfig.length === 0)
            return;
        const expandResult = autoExpandProfiles(cwd, missingFromConfig, config.tools);
        if (expandResult.expanded.length > 0) {
            this.log(`Auto-expanded profiles: ${expandResult.expanded.join(', ')} (${expandResult.filesWritten.length} files)`);
        }
    }
    dryRun(cwd, storyId, runtime, phases, gates, maxPhaseMinutes, manualCapabilitySignals) {
        this.log(`${messages.getMessage('info.dry-run-header')} [timeout=${maxPhaseMinutes}m]`);
        const results = [];
        for (const phase of phases) {
            const capabilitySignals = runtimeCapabilitySignals(cwd, storyId, manualCapabilitySignals);
            const profile = PHASE_TO_PROFILE[phase];
            const invocation = buildInvocation(runtime, phase, storyId, cwd, { gates, capabilitySignals });
            const ruleStatus = invocation.ruleFile ? '✓' : '✗ (missing)';
            const ruleDisplay = invocation.ruleFile ?? messages.getMessage('warn.rule-file-missing');
            const commandStr = [invocation.command, ...invocation.args].join(' ');
            this.log(messages.getMessage('info.dry-run-phase', [phase, runtime, profile]));
            this.log(messages.getMessage('info.dry-run-rule-file', [ruleDisplay, ruleStatus]));
            this.log(`  Command: ${commandStr}`);
            results.push({ phase, status: 'skipped' });
        }
        return { storyId, runtime, phases: results, dryRun: true, cwd };
    }
    async execute(opts) {
        const { cwd, storyId, runtime, phases, gates, maxIterations, maxPhaseMinutes, maxAgents } = opts;
        if (runtime === 'cursor' && !process.env['CURSOR_API_KEY']) {
            this.warn(messages.getMessage('warn.cursor-no-api-key'));
        }
        const run = createWorkflowRun(cwd, { storyId, gates, maxIterations });
        this.log(`▶  Run ${run.id} created`);
        let currentRun = run;
        const results = [];
        const callbacks = this.buildOrchestratorCallbacks(cwd, storyId, runtime, results, {
            getCurrentRun: () => currentRun,
            setCurrentRun: (r) => {
                currentRun = r;
            },
        });
        const task = getTask(cwd, storyId);
        const goal = task?.summary ?? `Execute workflow for ${storyId}`;
        await runDelegatedWorkflow({
            cwd,
            taskId: storyId,
            runId: run.id,
            goal,
            phases,
            runtime,
            gates,
            maxRetries: maxIterations,
            maxPhaseMinutes,
            maxConcurrentSpawns: maxAgents,
            callbacks,
        });
        return { storyId, runtime, phases: results, dryRun: false, cwd };
    }
    /** Build the orchestrator callbacks, keeping execute() focused on coordination. */
    buildOrchestratorCallbacks(cwd, storyId, runtime, results, runState) {
        return {
            onPhaseStart: (phase, profile) => {
                this.log(messages.getMessage('info.phase-start', [phase, runtime]));
                const phaseRecord = initPhase(cwd, runState.getCurrentRun(), phase);
                runState.setCurrentRun({
                    ...runState.getCurrentRun(),
                    phases: [...runState.getCurrentRun().phases, phaseRecord],
                });
                this.log(`  Profile: ${profile}`);
            },
            onClarification: (question, options) => askClarification(question, options),
            onBlocked: (phase, reason) => {
                this.log(`⛔ Phase ${phase} blocked: ${reason}`);
                const failResult = advanceWorkflow(cwd, runState.getCurrentRun(), phase, {
                    kind: 'qa-fail',
                    notes: `blocked: ${reason}`,
                });
                if (failResult.ok)
                    runState.setCurrentRun(failResult.value);
            },
            onDeliverable: (phase, _content, artifacts) => {
                const artifactStr = artifacts?.length ? ` (artifacts: ${artifacts.join(', ')})` : '';
                this.log(`✓  Phase ${phase} delivered${artifactStr}`);
            },
            onPhaseComplete: (phase, response) => {
                const status = response.type === 'deliverable' ? 'done' : 'failed';
                const advResult = advanceWorkflow(cwd, runState.getCurrentRun(), phase, {
                    kind: status === 'done' ? 'done' : 'qa-fail',
                    notes: `${response.type} via ${runtime}`,
                });
                if (advResult.ok)
                    runState.setCurrentRun(advResult.value);
                if (status === 'failed') {
                    addOperationalLesson(cwd, {
                        operation: 'workflow execute',
                        failedAction: `${phase} phase`,
                        errorSignature: (response.type === 'blocked' ? response.reason : 'unknown error').slice(0, 200),
                        phase,
                        storyId,
                    });
                }
                results.push({ phase, status, exitCode: status === 'done' ? 0 : 1 });
            },
            onGatePause: (completedPhase, nextPhase) => {
                this.log(`⏸  Gate ${completedPhase}→${nextPhase} — resume with: sf setup-agents workflow execute --story ${storyId} --from-phase ${nextPhase}`);
            },
            onLog: (message) => {
                this.log(`  ${message}`);
            },
        };
    }
}
function parseCapabilitySignals(value) {
    return (value
        ?.split(',')
        .map((signal) => signal.trim())
        .filter(Boolean) ?? []);
}
function runtimeCapabilitySignals(cwd, storyId, manualSignals) {
    return [...new Set([...collectSubagentCapabilitySignals(cwd, storyId), ...manualSignals])];
}
function askClarification(question, options) {
    return new Promise((resolve) => {
        const rl = createInterface({ input: process.stdin, output: process.stderr });
        process.stderr.write(`\n❓ Sub-agent clarification:\n   ${question}\n`);
        if (options?.length) {
            for (let i = 0; i < options.length; i++) {
                process.stderr.write(`   ${i + 1}) ${options[i]}\n`);
            }
            process.stderr.write('   Enter number or free text: ');
        }
        else {
            process.stderr.write('   > ');
        }
        rl.once('line', (input) => {
            rl.close();
            const trimmed = input.trim();
            if (options?.length) {
                const idx = parseInt(trimmed, 10) - 1;
                if (idx >= 0 && idx < options.length) {
                    resolve(options[idx]);
                    return;
                }
            }
            resolve(trimmed);
        });
    });
}
//# sourceMappingURL=execute.js.map