/*
 * 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 { Messages } from '@salesforce/core';
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
import { resolveWorkspaceRoot } from '../../../services/workspace-root.js';
import { workspaceFlags } from '../../../util/task-flags.js';
import { runReleaseChecks } from '../../../services/release-check.js';
import { DEFAULT_INSTRUCTIONS_DIR, PHASE_SEQUENCE, PHASE_TO_PROFILE, advanceWorkflow, closePhaseWithHandoff, createWorkflowRun, detectCorrectionReturn, initPhase, readWorkflowRun, resolveImplementorProfile, resolvePhaseInstructions, resolvePhaseSequence, workflowRunPath, } from '../../../services/workflow-engine.js';
import { notifyGatePause } from '../../../services/notifications.js';
import { scorePlaybooksForPhase } from '../../../services/skill-scoring.js';
import { addEvidenceRecord } from '../../../services/orchestration-records.js';
import { pushToGitHub, pushToJira } from '../../../services/write-back.js';
import { getTask } from '../../../services/task-state.js';
import { readWorkspaceConfig } from '../../../services/workspace-config.js';
import { autoExpandProfiles } from '../../../services/profile-expand.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@jterrats/setup-agents', 'setup-agents.workflow.run');
const GATE_MODES = ['none', 'phase', 'all'];
const PHASE_SUMMARIES = {
    pm: 'Product framing, priority, and success metrics',
    ba: 'Backlog refinement and acceptance criteria',
    architect: 'Technical tasking, size estimation, and design decisions',
    ux: 'UX / design review',
    developer: 'Implementation against acceptance criteria',
    'security-review': 'Security review',
    qa: 'Verification against user acceptance criteria',
    release: 'Release candidate validation and PR creation',
};
export default class WorkflowRun extends SfCommand {
    static summary = messages.getMessage('summary');
    static description = messages.getMessage('description');
    static examples = messages.getMessages('examples');
    static flags = {
        ...workspaceFlags,
        story: Flags.string({
            char: 's',
            summary: messages.getMessage('flags.story.summary'),
            description: messages.getMessage('flags.story.description'),
            required: true,
        }),
        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,
        }),
        profiles: Flags.string({
            summary: messages.getMessage('flags.profiles.summary'),
            description: messages.getMessage('flags.profiles.description'),
        }),
        resume: Flags.string({
            summary: messages.getMessage('flags.resume.summary'),
            description: messages.getMessage('flags.resume.description'),
        }),
        'from-phase': Flags.string({
            summary: messages.getMessage('flags.from-phase.summary'),
            description: 'Creates a new run with upstream phases marked as skipped and begins execution at the specified phase. ' +
                'Use this when work is already in progress (e.g. implementation started) to avoid duplicating upstream tasks.',
            options: [...PHASE_SEQUENCE],
        }),
        'qa-result': Flags.string({
            summary: messages.getMessage('flags.qa-result.summary'),
            description: messages.getMessage('flags.qa-result.description'),
            options: ['pass', 'fail'],
        }),
        'dry-run': Flags.boolean({
            summary: messages.getMessage('flags.dry-run.summary'),
            description: messages.getMessage('flags.dry-run.description'),
            default: false,
        }),
        'instructions-dir': Flags.string({
            summary: messages.getMessage('flags.instructions-dir.summary'),
            description: messages.getMessage('flags.instructions-dir.description'),
            default: DEFAULT_INSTRUCTIONS_DIR,
        }),
        'reject-to': Flags.string({
            summary: messages.getMessage('flags.reject-to.summary'),
            description: messages.getMessage('flags.reject-to.description'),
            options: [...PHASE_SEQUENCE],
        }),
        findings: Flags.string({
            summary: messages.getMessage('flags.findings.summary'),
            description: messages.getMessage('flags.findings.description'),
        }),
        'push-to': Flags.string({
            summary: messages.getMessage('flags.push-to.summary'),
            options: ['github', 'jira'],
        }),
        ticket: Flags.string({
            summary: messages.getMessage('flags.ticket.summary'),
        }),
    };
    async run() {
        const { flags } = await this.parse(WorkflowRun);
        const cwd = resolveWorkspaceRoot(flags);
        const file = workflowRunPath(cwd);
        const gates = flags.gates;
        const maxIterations = flags['max-iterations'];
        const storyId = flags.story;
        const pushTo = flags['push-to'];
        const ticket = flags.ticket;
        const instructionsDir = flags['instructions-dir'];
        const config = readWorkspaceConfig(cwd);
        const task = getTask(cwd, storyId);
        const taskRoles = task?.roles;
        const phaseOverrides = task?.phaseOverrides;
        const declaredPhases = task?.declaredPhases;
        this.tryAutoExpand(cwd, config, taskRoles);
        const phaseSequence = config
            ? resolvePhaseSequence(config.profiles, taskRoles, phaseOverrides, declaredPhases)
            : [...PHASE_SEQUENCE];
        if (flags['dry-run']) {
            this.log(`Resolved phase sequence: ${phaseSequence.join(' → ')}`);
            return this.dryRun(cwd, storyId, gates, maxIterations, instructionsDir, file, phaseSequence);
        }
        if (flags.resume) {
            return this.handleResume({
                cwd,
                flags,
                config,
                instructionsDir,
                file,
                maxIterations,
                pushTo,
                ticket,
                phaseSequence,
            });
        }
        if (!config)
            this.warn(messages.getMessage('warn.no-config'));
        const activeProfiles = config?.profiles ?? [];
        const implementorProfile = resolveImplementorProfile(activeProfiles);
        const run = createWorkflowRun(cwd, { storyId, gates, maxIterations });
        this.log(messages.getMessage('info.starting', [storyId, gates, maxIterations]));
        // --from-phase: skip upstream phases and start at the specified phase
        const fromPhase = flags['from-phase'];
        if (fromPhase) {
            if (!phaseSequence.includes(fromPhase)) {
                throw new Error(`Phase '${fromPhase}' is not in the resolved phase sequence for this workspace. Active sequence: ${phaseSequence.join(' → ')}`);
            }
            const skipped = skipUpstreamPhases(run, fromPhase, phaseSequence);
            const skippedNames = phaseSequence.slice(0, phaseSequence.indexOf(fromPhase));
            if (skippedNames.length > 0) {
                this.log(`Skipping upstream phases: ${skippedNames.join(', ')} → starting at ${fromPhase}`);
            }
            return this.executeFrom({
                cwd,
                run: skipped,
                instructionsDir,
                file,
                startPhase: fromPhase,
                pushTo,
                ticket,
                implementorProfile,
                phaseSequence,
            });
        }
        const firstPhase = phaseSequence[0] ?? 'developer';
        return this.executeFrom({
            cwd,
            run,
            instructionsDir,
            file,
            startPhase: firstPhase,
            pushTo,
            ticket,
            implementorProfile,
            phaseSequence,
        });
    }
    async handleResume(opts) {
        const { cwd, flags, config, instructionsDir, file, maxIterations, pushTo, ticket, phaseSequence } = opts;
        const existing = readWorkflowRun(cwd, flags.resume);
        if (!existing)
            throw new Error(messages.getMessage('errors.run-not-found', [flags.resume]));
        const rejectTo = flags['reject-to'];
        if (rejectTo) {
            return this.handleRejection({
                cwd,
                existing,
                rejectTo,
                findingsFlag: flags.findings,
                config,
                instructionsDir,
                file,
                pushTo,
                ticket,
                phaseSequence,
            });
        }
        const correction = detectCorrectionReturn(cwd, existing);
        if (correction && !flags['from-phase']) {
            return this.handleCorrectionLoop({
                cwd,
                existing,
                correction,
                maxIterations,
                config,
                instructionsDir,
                file,
                pushTo,
                ticket,
                phaseSequence,
            });
        }
        const qaResult = flags['qa-result'];
        const resumeProfiles = config?.profiles ?? [];
        const resumeImplementorProfile = resolveImplementorProfile(resumeProfiles);
        const startPhase = flags['from-phase'] ?? resumePhase(existing, phaseSequence);
        return this.executeFrom({
            cwd,
            run: existing,
            instructionsDir,
            file,
            startPhase,
            qaResult,
            pushTo,
            ticket,
            implementorProfile: resumeImplementorProfile,
            phaseSequence,
        });
    }
    async handleRejection(opts) {
        const { cwd, existing, rejectTo, findingsFlag, config, instructionsDir, file, pushTo, ticket, phaseSequence } = opts;
        const findings = findingsFlag ?? 'Rejected by reviewer';
        const rejectingPhase = findActivePhase(existing);
        if (!rejectingPhase)
            throw new Error('No active phase found to reject from');
        const rejectResult = advanceWorkflow(cwd, existing, rejectingPhase, {
            kind: 'reject',
            targetPhase: rejectTo,
            findings,
        });
        if (!rejectResult.ok)
            throw new Error(rejectResult.error.message);
        this.log(`Phase ${rejectingPhase} rejected → returning to ${rejectTo}: ${findings}`);
        const resumeImplementorProfile = resolveImplementorProfile(config?.profiles ?? []);
        return this.executeFrom({
            cwd,
            run: rejectResult.value,
            instructionsDir,
            file,
            startPhase: rejectTo,
            pushTo,
            ticket,
            implementorProfile: resumeImplementorProfile,
            phaseSequence,
            rejectionContext: findings,
        });
    }
    async handleCorrectionLoop(opts) {
        const { cwd, existing, correction, maxIterations, config, instructionsDir, file, pushTo, ticket, phaseSequence } = opts;
        if (correction.correctionIteration > maxIterations) {
            throw new Error(`Max correction iterations (${maxIterations}) exceeded for review ${correction.reviewId}`);
        }
        this.log(`Correction loop detected (iteration ${correction.correctionIteration}): ` +
            `returning to ${correction.returnTarget} — ${correction.findings ?? 'no findings'}`);
        const resumeImplementorProfile = resolveImplementorProfile(config?.profiles ?? []);
        return this.executeFrom({
            cwd,
            run: existing,
            instructionsDir,
            file,
            startPhase: correction.returnTarget,
            pushTo,
            ticket,
            implementorProfile: resumeImplementorProfile,
            phaseSequence,
            rejectionContext: correction.findings,
        });
    }
    tryAutoExpand(cwd, config, taskRoles) {
        if (!config || !taskRoles)
            return;
        const allRoles = [...taskRoles.required, ...taskRoles.optional];
        const configSet = new Set(config.profiles);
        const missingFromConfig = allRoles.filter((r) => !configSet.has(r));
        if (missingFromConfig.length === 0)
            return;
        const result = autoExpandProfiles(cwd, missingFromConfig, config.tools);
        if (result.expanded.length > 0) {
            this.log(`Auto-expanded profiles: ${result.expanded.join(', ')} (${result.filesWritten.length} files)`);
        }
        if (result.invalid.length > 0) {
            this.warn(`Unknown roles ignored: ${result.invalid.join(', ')}`);
        }
    }
    async executeFrom(opts) {
        const { cwd, run, instructionsDir, file, startPhase, qaResult, pushTo, ticket, implementorProfile, phaseSequence = [...PHASE_SEQUENCE], rejectionContext, } = opts;
        let current = run;
        const task = getTask(cwd, run.storyId);
        const startIndex = phaseSequence.indexOf(startPhase);
        const sequence = startIndex >= 0 ? phaseSequence.slice(startIndex) : phaseSequence;
        let pendingNotify = null;
        let pendingQaResult = qaResult;
        for (let i = 0; i < sequence.length; i++) {
            const phase = sequence[i];
            const nextPhase = sequence[i + 1];
            const context = i === 0 ? rejectionContext : undefined;
            const phaseRecord = this.initOrSkipPhase(cwd, current, phase, context, instructionsDir, task);
            if (!phaseRecord)
                continue;
            if (phase === 'qa') {
                const qaOutcome = this.handleQaPhase({
                    cwd,
                    current,
                    pendingQaResult,
                    instructionsDir,
                    file,
                    pushTo,
                    ticket,
                    implementorProfile,
                    phaseSequence,
                });
                if (qaOutcome.earlyReturn)
                    return qaOutcome.earlyReturn;
                if (qaOutcome.recursiveCall)
                    return qaOutcome.recursiveCall;
                current = qaOutcome.run;
                pendingQaResult = undefined;
            }
            if (!nextPhase) {
                // eslint-disable-next-line no-await-in-loop
                return this.handleFinalPhase(cwd, current, phase, phaseRecord, file, pushTo, ticket);
            }
            // eslint-disable-next-line no-await-in-loop
            const gateResult = await this.advanceToNextPhase(cwd, current, phase, nextPhase, implementorProfile, pushTo, ticket);
            current = gateResult.run;
            if (gateResult.paused) {
                pendingNotify = gateResult.notifyPromise ?? null;
                break;
            }
        }
        if (pendingNotify)
            await pendingNotify;
        return { run: current, file, cwd };
    }
    initOrSkipPhase(cwd, current, phase, context, instructionsDir, task) {
        const existingRunning = current.phases.find((p) => p.phase === phase && p.status === 'running');
        if (existingRunning) {
            this.log(messages.getMessage('info.phase-start', [phase, existingRunning.taskId]));
            return null;
        }
        const phaseRecord = initPhase(cwd, current, phase, context);
        current.phases.push(phaseRecord);
        this.log(messages.getMessage('info.phase-start', [phase, phaseRecord.taskId]));
        this.logPhaseInstructions(cwd, phase, instructionsDir, task);
        return phaseRecord;
    }
    logPhaseInstructions(cwd, phase, instructionsDir, task) {
        const instructions = resolvePhaseInstructions(cwd, phase, instructionsDir);
        for (const p of instructions.playbooks.filter((pb) => pb.exists)) {
            this.log(messages.getMessage('info.playbook-found', [p.filename, p.path]));
        }
        for (const p of instructions.playbooks.filter((pb) => !pb.exists)) {
            this.warn(messages.getMessage('warn.playbook-missing', [p.filename, instructions.instructionsDir]));
        }
        if (task) {
            const scoring = scorePlaybooksForPhase(phase, { summary: task.summary, roles: task.roles, owner: task.owner });
            if (scoring.selected.length > 0) {
                const skillList = scoring.selected.map((s) => `${s.filename}(${s.score})`).join(', ');
                this.log(`  Skills: ${skillList}`);
            }
        }
    }
    handleQaPhase(opts) {
        const { cwd, current, pendingQaResult, instructionsDir, file, pushTo, ticket, implementorProfile, phaseSequence = [...PHASE_SEQUENCE], } = opts;
        if (pendingQaResult === undefined) {
            this.log(messages.getMessage('info.qa-pause', [current.storyId, current.id, current.storyId, current.id]));
            return { earlyReturn: { run: current, file, cwd }, run: current };
        }
        const result = this.runQaLoop(cwd, current, pendingQaResult);
        if (result.blocked) {
            return { earlyReturn: { run: result.run, file, cwd }, run: result.run };
        }
        if (result.returnToDeveloper) {
            this.log(messages.getMessage('info.qa-fail', [result.run.qaIterations, result.run.maxIterations]));
            return {
                recursiveCall: this.executeFrom({
                    cwd,
                    run: result.run,
                    instructionsDir,
                    file,
                    startPhase: 'developer',
                    pushTo,
                    ticket,
                    implementorProfile,
                    phaseSequence,
                }),
                run: result.run,
            };
        }
        return { run: result.run };
    }
    async advanceToNextPhase(cwd, current, phase, nextPhase, implementorProfile, pushTo, ticket) {
        const toOverride = phase === 'architect' && nextPhase === 'developer' ? implementorProfile : undefined;
        const { handoffId, reviewId } = closePhaseWithHandoff(cwd, current, phase, nextPhase, PHASE_SUMMARIES[phase], [], toOverride);
        this.log(messages.getMessage('info.phase-done', [phase, handoffId]));
        await this.tryWriteBack(pushTo, ticket, current, phase, false);
        if (reviewId) {
            const outcome = { kind: 'gate-pause', reviewId };
            const gatePauseResult = advanceWorkflow(cwd, current, phase, outcome);
            if (!gatePauseResult.ok)
                throw new Error(gatePauseResult.error.message);
            const updated = gatePauseResult.value;
            this.log(messages.getMessage('info.gate-pause', [
                phase,
                nextPhase,
                reviewId,
                reviewId,
                toOverride ?? PHASE_TO_PROFILE[nextPhase],
                updated.storyId,
                updated.id,
            ]));
            const notifyPromise = notifyGatePause(cwd, {
                runId: updated.id,
                storyId: updated.storyId,
                phase,
                kind: 'transition',
                fromRole: phase,
                toRole: nextPhase,
                reviewId,
                resumeCommand: `sf setup-agents workflow run --story ${updated.storyId} --resume ${updated.id}`,
            });
            return { run: updated, paused: true, notifyPromise };
        }
        const loopDoneResult = advanceWorkflow(cwd, current, phase, { kind: 'done', notes: PHASE_SUMMARIES[phase] });
        if (!loopDoneResult.ok)
            throw new Error(loopDoneResult.error.message);
        return { run: loopDoneResult.value, paused: false };
    }
    async tryWriteBack(pushTo, ticket, run, phase, dryRun) {
        if (!pushTo)
            return;
        const issueNumber = resolveWorkflowIssueNumber(ticket, run.storyId);
        if (!issueNumber) {
            this.warn(`--ticket is required when using --push-to. Skipping write-back for phase ${phase}.`);
            return;
        }
        const phaseRecord = run.phases.find((p) => p.phase === phase);
        const handoffSummary = phaseRecord?.notes ?? PHASE_SUMMARIES[phase];
        const body = [
            `## [setup-agents] Phase ${phase} complete — story ${run.storyId}`,
            '',
            `**Run ID:** ${run.id}`,
            `**Phase:** ${phase}`,
            '**Status:** done',
            `**Handoff summary:** ${handoffSummary}`,
        ].join('\n');
        const payload = {
            title: `[setup-agents] Phase ${phase} complete — ${run.storyId}`,
            body,
        };
        let result;
        try {
            if (pushTo === 'github') {
                result = await pushToGitHub(issueNumber, payload, dryRun);
            }
            else {
                result = await pushToJira(issueNumber, payload, dryRun);
            }
            if (result.dryRun) {
                this.log(messages.getMessage('info.writeBackDryRun', [phase, pushTo]));
            }
            else if (result.url) {
                this.log(messages.getMessage('info.writeBackPushed', [phase, pushTo, result.url]));
            }
        }
        catch (err) {
            this.warn(`Write-back failed for phase ${phase}: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    async handleFinalPhase(cwd, run, phase, phaseRecord, file, pushTo, ticket) {
        let current = run;
        if (phase === 'release') {
            const releaseConfig = readWorkspaceConfig(cwd);
            const enabledChecks = releaseConfig?.releaseCheck?.checks;
            const releaseReport = await runReleaseChecks(cwd, phaseRecord.taskId, enabledChecks);
            if (!releaseReport.overallPass) {
                const failedNames = releaseReport.checks
                    .filter((c) => c.blocking && c.status === 'fail')
                    .map((c) => c.name)
                    .join(', ');
                const advanceResult = advanceWorkflow(cwd, current, phase, {
                    kind: 'done',
                    notes: `Release-check failed: ${failedNames}`,
                });
                const failed = {
                    ...(advanceResult.ok ? advanceResult.value : current),
                    status: 'failed',
                };
                recordFailureLesson(cwd, phase, phaseRecord.taskId, `Release checks failed: ${failedNames}`);
                this.log(messages.getMessage('info.release-check-fail', [failedNames]));
                return { run: failed, file, cwd };
            }
            this.log(messages.getMessage('info.release-check-pass'));
        }
        const outcome = { kind: 'done', notes: PHASE_SUMMARIES[phase] };
        const doneResult = advanceWorkflow(cwd, current, phase, outcome);
        if (!doneResult.ok)
            throw new Error(doneResult.error.message);
        current = doneResult.value;
        this.log(messages.getMessage('info.done', [current.storyId, current.id]));
        await this.tryWriteBack(pushTo, ticket, current, phase, false);
        return { run: current, file, cwd };
    }
    runQaLoop(cwd, run, qaResult) {
        const phaseRecord = run.phases.find((p) => p.phase === 'qa');
        if (!phaseRecord)
            return { run, blocked: false, returnToDeveloper: false };
        // Hard stop: max iterations exceeded
        if (run.qaIterations >= run.maxIterations) {
            this.log(messages.getMessage('info.qa-max', [run.maxIterations]));
            const blockedResult = advanceWorkflow(cwd, run, 'qa', { kind: 'qa-fail', notes: 'Max iterations reached' });
            if (!blockedResult.ok)
                throw new Error(blockedResult.error.message);
            recordFailureLesson(cwd, 'qa', phaseRecord.taskId, `Max QA iterations (${run.maxIterations}) exceeded`);
            return { run: blockedResult.value, blocked: true, returnToDeveloper: false };
        }
        // Explicit fail from agent: increment iteration, return to developer
        if (qaResult === 'fail') {
            const updatedResult = advanceWorkflow(cwd, run, 'qa', {
                kind: 'qa-fail',
                notes: `QA iteration ${run.qaIterations + 1} failed — returning to developer`,
            });
            if (!updatedResult.ok)
                throw new Error(updatedResult.error.message);
            return { run: updatedResult.value, blocked: false, returnToDeveloper: true };
        }
        // Pass (explicit or default on first run): proceed to release
        return { run, blocked: false, returnToDeveloper: false };
    }
    dryRun(cwd, storyId, gates, maxIterations, instructionsDir, file, phaseSequence) {
        this.log(messages.getMessage('info.dry-run-header'));
        const gateTransitions = new Set(['ba→architect', 'qa→release']);
        for (const phase of phaseSequence) {
            const idx = phaseSequence.indexOf(phase);
            const next = phaseSequence[idx + 1];
            const transitionKey = next ? `${phase}→${next}` : '';
            const isGated = gates === 'all' || (gates === 'phase' && gateTransitions.has(transitionKey));
            const gateLabel = isGated ? 'yes' : 'no';
            this.log(messages.getMessage('info.dry-run-phase', [phase, PHASE_TO_PROFILE[phase], gateLabel]));
            const instructions = resolvePhaseInstructions(cwd, phase, instructionsDir);
            for (const p of instructions.playbooks) {
                const status = p.exists ? '✓' : '✗';
                this.log(messages.getMessage('info.dry-run-playbook', [status, p.filename]));
            }
        }
        const mockRun = {
            id: 'dry-run',
            storyId,
            gates,
            maxIterations,
            qaIterations: 0,
            phases: [],
            status: 'running',
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
        };
        return { run: mockRun, file, cwd };
    }
}
function findActivePhase(run) {
    const running = run.phases.find((p) => p.status === 'running');
    if (running)
        return running.phase;
    const blocked = run.phases.find((p) => p.status === 'blocked');
    return blocked?.phase;
}
function skipUpstreamPhases(run, fromPhase, phaseSequence) {
    const upstreamPhases = phaseSequence.slice(0, phaseSequence.indexOf(fromPhase));
    const now = new Date().toISOString();
    const skippedRecords = upstreamPhases.map((phase) => ({
        phase,
        status: 'skipped',
        taskId: `skipped-${phase}`,
        startedAt: now,
        completedAt: now,
        notes: 'Skipped via --from-phase',
    }));
    return { ...run, phases: skippedRecords };
}
function resumePhase(run, phaseSequence) {
    for (let i = 0; i < phaseSequence.length; i++) {
        const result = resolvePhaseResumption(run, phaseSequence, i);
        if (result === 'skip')
            continue;
        if (result !== undefined)
            return result;
    }
    return 'release';
}
/** Returns the phase to resume at, 'skip' to continue searching, or undefined if unresolved. */
function resolvePhaseResumption(run, phaseSequence, index) {
    const phase = phaseSequence[index];
    const record = run.phases.find((p) => p.phase === phase);
    if (!record) {
        return shouldSkipAbsentPhase(run, phaseSequence, index) ? 'skip' : phase;
    }
    if (record.status === 'done' || record.status === 'skipped')
        return 'skip';
    if (record.status === 'running')
        return phase;
    const gateResult = resolveGatePause(run, phaseSequence, index, record);
    if (gateResult !== undefined) {
        return gateResult ?? 'skip';
    }
    if (record.status === 'blocked')
        return phase;
    return undefined;
}
function shouldSkipAbsentPhase(run, phaseSequence, currentIndex) {
    const nextRunPhase = phaseSequence.slice(currentIndex + 1).find((p) => run.phases.some((r) => r.phase === p));
    if (!nextRunPhase)
        return false;
    const nextRecord = run.phases.find((p) => p.phase === nextRunPhase);
    return isTerminalOrGated(nextRecord);
}
function resolveGatePause(run, phaseSequence, currentIndex, record) {
    if (!(record.status === 'blocked' && record.reviewIds?.length))
        return undefined;
    const nextPhase = phaseSequence[currentIndex + 1];
    if (!nextPhase)
        return 'release';
    const nextRecord = run.phases.find((p) => p.phase === nextPhase);
    if (nextRecord && (nextRecord.status === 'done' || nextRecord.status === 'skipped'))
        return null;
    return nextPhase;
}
function isTerminalOrGated(record) {
    if (!record)
        return false;
    if (record.status === 'done' || record.status === 'skipped')
        return true;
    return record.status === 'blocked' && (record.reviewIds?.length ?? 0) > 0;
}
function resolveWorkflowIssueNumber(ticket, storyId) {
    if (ticket)
        return ticket;
    // If storyId is already a plain number, use it directly
    if (/^\d+$/.test(storyId))
        return storyId;
    // Try to parse issue number from env var pattern: SA-116 → GITHUB_ISSUE_116
    const match = /[A-Z]+-(\d+)/.exec(storyId);
    if (match) {
        const envVar = `GITHUB_ISSUE_${match[1]}`;
        const envValue = process.env[envVar];
        if (envValue)
            return envValue;
    }
    return null;
}
function recordFailureLesson(cwd, phase, taskId, reason) {
    addEvidenceRecord(cwd, {
        taskIds: [taskId],
        profile: PHASE_TO_PROFILE[phase],
        evidenceKind: 'report',
        summary: `Lesson learned (${phase} failure): ${reason}`,
    });
}
//# sourceMappingURL=run.js.map