/*
 * 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 { randomBytes } from 'node:crypto';
import { join } from 'node:path';
import { err, ok } from '../types/index.js';
import { appendJsonlFile, dedupeById, readJsonlFile } from './file-writer.js';
import { claimTask, createTask } from './task-state.js';
import { PHASE_SEQUENCE, PHASE_TO_PROFILE } from './workflow-instructions.js';
export function workflowRunPath(cwd) {
    return join(cwd, '.setup-agents', 'state', 'workflow-runs.jsonl');
}
export function createWorkflowRun(cwd, opts) {
    const now = new Date().toISOString();
    const run = {
        id: `wf-${Date.now()}-${randomBytes(3).toString('hex')}`,
        storyId: opts.storyId,
        gates: opts.gates,
        maxIterations: opts.maxIterations,
        qaIterations: 0,
        phases: [],
        status: 'running',
        createdAt: now,
        updatedAt: now,
    };
    persistRun(cwd, run);
    return run;
}
export function readWorkflowRun(cwd, id) {
    return dedupeById(readJsonlFile(workflowRunPath(cwd))).find((r) => r.id === id);
}
export function advanceWorkflow(cwd, run, phase, outcome) {
    const now = new Date().toISOString();
    const phaseRecord = run.phases.find((p) => p.phase === phase);
    if (!phaseRecord)
        return err({ code: 'phase-not-found', message: `Phase '${phase}' not found in run '${run.id}'` });
    if (outcome.kind === 'qa-fail') {
        phaseRecord.status = 'blocked';
        phaseRecord.notes = outcome.notes;
        phaseRecord.completedAt = now;
        const updated = { ...run, qaIterations: run.qaIterations + 1, updatedAt: now };
        updated.phases = run.phases.map((p) => (p.phase === phase ? phaseRecord : p));
        persistRun(cwd, updated);
        return ok(updated);
    }
    if (outcome.kind === 'reject') {
        phaseRecord.status = 'blocked';
        phaseRecord.notes = `Rejected → ${outcome.targetPhase}: ${outcome.findings}`;
        phaseRecord.completedAt = now;
        const targetIndex = PHASE_SEQUENCE.indexOf(outcome.targetPhase);
        if (targetIndex === -1) {
            return err({ code: 'phase-not-found', message: `Target phase '${outcome.targetPhase}' is not valid` });
        }
        const updated = {
            ...run,
            qaIterations: run.qaIterations + 1,
            updatedAt: now,
        };
        updated.phases = run.phases.map((p) => (p.phase === phase ? phaseRecord : p));
        persistRun(cwd, updated);
        return ok(updated);
    }
    if (outcome.kind === 'decompose') {
        phaseRecord.status = 'blocked';
        phaseRecord.notes = `Decompose (${outcome.voter}): ${outcome.reason}`;
        phaseRecord.completedAt = now;
        const updated = { ...run, status: 'paused', updatedAt: now };
        updated.phases = run.phases.map((p) => (p.phase === phase ? phaseRecord : p));
        persistRun(cwd, updated);
        return ok(updated);
    }
    if (outcome.kind === 'gate-pause') {
        phaseRecord.status = 'blocked';
        phaseRecord.reviewIds = [...(phaseRecord.reviewIds ?? []), outcome.reviewId];
        phaseRecord.notes = `Gate pause — review ${outcome.reviewId}`;
        phaseRecord.completedAt = now;
        const updated = { ...run, status: 'paused', updatedAt: now };
        updated.phases = run.phases.map((p) => (p.phase === phase ? phaseRecord : p));
        persistRun(cwd, updated);
        return ok(updated);
    }
    // outcome.kind === 'done'
    phaseRecord.status = 'done';
    phaseRecord.notes = outcome.notes;
    phaseRecord.completedAt = now;
    const updated = { ...run, updatedAt: now };
    updated.phases = run.phases.map((p) => (p.phase === phase ? phaseRecord : p));
    const allDone = updated.phases.length > 0 && updated.phases.every((p) => p.status === 'done' || p.status === 'skipped');
    if (allDone)
        updated.status = 'done';
    persistRun(cwd, updated);
    return ok(updated);
}
export function initPhase(cwd, run, phase, rejectionContext) {
    const profile = PHASE_TO_PROFILE[phase];
    const now = new Date().toISOString();
    const iteration = run.phases.filter((p) => p.phase === phase).length;
    const suffix = iteration > 0 ? ` (retry ${iteration})` : '';
    const task = createTask(cwd, {
        summary: `[${phase}] ${run.storyId}${suffix}`,
        profile,
        mode: 'project',
    });
    claimTask(cwd, { id: task.id, profile });
    const phaseRecord = {
        phase,
        status: 'running',
        taskId: task.id,
        startedAt: now,
        ...(rejectionContext ? { notes: `Rejection findings: ${rejectionContext}` } : {}),
    };
    return phaseRecord;
}
export function listWorkflowRuns(cwd) {
    return dedupeById(readJsonlFile(workflowRunPath(cwd)));
}
export function pausedPhaseAndNext(run, cwd) {
    const latestReviews = cwd ? latestReviewMap(cwd) : undefined;
    for (let i = 0; i < PHASE_SEQUENCE.length - 1; i++) {
        const phase = PHASE_SEQUENCE[i];
        const next = PHASE_SEQUENCE[i + 1];
        const record = run.phases.find((p) => p.phase === phase);
        if (record?.status !== 'blocked' || !record.reviewIds?.length)
            continue;
        // Find the latest pending review for this phase
        const pendingReviewId = record.reviewIds.find((id) => {
            if (!latestReviews)
                return true; // no cwd: trust the run record
            const review = latestReviews.get(id);
            return !review || review.status === 'pending';
        });
        if (pendingReviewId) {
            return { pausedPhase: phase, nextPhase: next, reviewId: pendingReviewId };
        }
    }
    return undefined;
}
export function detectCorrectionReturn(cwd, run) {
    const reviews = latestReviewMap(cwd);
    for (const phaseRecord of run.phases) {
        if (phaseRecord.status !== 'blocked' || !phaseRecord.reviewIds?.length)
            continue;
        for (const reviewId of phaseRecord.reviewIds) {
            const review = reviews.get(reviewId);
            if (!review)
                continue;
            if ((review.status === 'blocked' || review.status === 'changes') && review.returnTarget) {
                return {
                    reviewId: review.id,
                    returnTarget: review.returnTarget,
                    findings: review.findings,
                    correctionIteration: review.correctionIteration ?? 1,
                };
            }
        }
    }
    return undefined;
}
function latestReviewMap(cwd) {
    const map = new Map();
    for (const r of readJsonlFile(join(cwd, '.setup-agents', 'state', 'reviews.jsonl'))) {
        map.set(r.id, r); // last write wins — approved record supersedes pending
    }
    return map;
}
export function midPhaseGates(run) {
    const result = [];
    for (const record of run.phases) {
        if (record.status === 'running' && record.reviewIds && record.reviewIds.length > 0) {
            for (const reviewId of record.reviewIds) {
                result.push({ phase: record.phase, reviewId });
            }
        }
    }
    return result;
}
export function updatePhaseStatus(cwd, run, phase, status, extra) {
    const now = new Date().toISOString();
    const phases = run.phases.map((p) => {
        if (p.phase !== phase)
            return p;
        return {
            ...p,
            status,
            ...(extra?.clarificationIds
                ? { clarificationIds: [...(p.clarificationIds ?? []), ...extra.clarificationIds] }
                : {}),
        };
    });
    const updated = { ...run, phases, updatedAt: now };
    persistRun(cwd, updated);
    return updated;
}
export function addMidPhaseGate(cwd, run, phase, reviewId) {
    const now = new Date().toISOString();
    const phases = run.phases.map((p) => {
        if (p.phase !== phase)
            return p;
        return { ...p, reviewIds: [...(p.reviewIds ?? []), reviewId] };
    });
    const updated = { ...run, phases, updatedAt: now };
    persistRun(cwd, updated);
    return updated;
}
/**
 * Roll back a run to `targetPhase` (inclusive).
 *
 * All phase records from `targetPhase` onward are removed so the run can
 * be re-executed from that phase via `workflow run --resume`. The run
 * status is reset to `running` and the rollback reason is recorded.
 */
export function rollbackWorkflowRun(cwd, run, targetPhase, reason) {
    const targetIndex = PHASE_SEQUENCE.indexOf(targetPhase);
    if (targetIndex === -1) {
        return err({ code: 'phase-not-found', message: `Phase '${targetPhase}' is not a valid workflow phase` });
    }
    const phasesToKeep = PHASE_SEQUENCE.slice(0, targetIndex);
    const updated = {
        ...run,
        phases: run.phases.filter((p) => phasesToKeep.includes(p.phase)),
        status: 'running',
        updatedAt: new Date().toISOString(),
        rollbackReason: reason,
    };
    persistRun(cwd, updated);
    return ok(updated);
}
function persistRun(cwd, run) {
    appendJsonlFile(workflowRunPath(cwd), run);
}
//# sourceMappingURL=workflow-state.js.map