"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.patchCanonicalWorkflowStates = patchCanonicalWorkflowStates;
exports.formatStatePatchOutcomeForIssue = formatStatePatchOutcomeForIssue;
exports.formatStatePatchSummary = formatStatePatchSummary;
const canonical_workflow_state_1 = require("../comments/canonical-workflow-state");
const registry_1 = require("../platform/ticket-tracker/registry");
const state_1 = require("../workflows/state");
const patches_1 = require("../workflows/state/patches");
const types_1 = require("../workflows/types");
const state_backup_1 = require("./state-backup");
const state_canonical_comment_1 = require("./state-canonical-comment");
async function patchCanonicalWorkflowStates(input) {
    const now = input.now ?? new Date();
    const runId = (0, state_backup_1.createStatePatchRunId)(now);
    const backupFilePath = (0, state_backup_1.resolveStatePatchBackupFilePath)({
        config: input.config,
        runId,
        ...(input.homeDirectory ? { homeDirectory: input.homeDirectory } : {}),
    });
    const summary = createStatePatchSummary();
    const outcomes = [];
    let sequence = 0;
    let completed = true;
    for (const issue of input.issues) {
        if ((await input.shouldStop?.()) === true) {
            completed = false;
            break;
        }
        const outcome = await patchIssueCanonicalState({
            cwd: input.cwd,
            issue,
            dryRun: input.dryRun,
            config: input.config,
            ticketTracker: input.ticketTracker,
            deps: input.deps ?? {},
            runId,
            backupFilePath,
            now,
            nextSequence: () => {
                sequence += 1;
                return sequence;
            },
        });
        recordPatchOutcome(summary, outcome);
        outcomes.push({ issue, outcome });
    }
    return { summary, outcomes, backupFilePath, completed };
}
function formatStatePatchOutcomeForIssue(issue, outcome) {
    switch (outcome.kind) {
        case "updated":
            return `${issue.displayId} updated`;
        case "noop":
            return `${issue.displayId} noop`;
        case "missing":
            return `${issue.displayId} missing canonical workflow state`;
        case "future":
        case "skipped":
        case "failed":
            return `${issue.displayId} ${outcome.kind}: ${outcome.reason}`;
    }
}
function formatStatePatchSummary(summary) {
    return `state patch summary: updated=${summary.updated} noop=${summary.noop} missing=${summary.missing} future=${summary.future} skipped=${summary.skipped} failed=${summary.failed}`;
}
async function patchIssueCanonicalState(input) {
    try {
        const comments = await input.ticketTracker.listComments(input.issue.id, {
            timeline: "issue-only",
        });
        const resolved = resolveLatestCanonicalRawWorkflowState(comments);
        if (resolved.kind === "missing") {
            return { kind: "missing" };
        }
        if (resolved.kind === "invalid") {
            return { kind: "skipped", reason: resolved.reason };
        }
        if (resolved.version > types_1.WORKFLOW_STATE_VERSION) {
            return {
                kind: "future",
                version: resolved.version,
                reason: `Workflow state version ${resolved.version} is newer than this AlpacaLoop build supports v${types_1.WORKFLOW_STATE_VERSION}. Update AlpacaLoop before processing this repo.`,
            };
        }
        if (resolved.version === types_1.WORKFLOW_STATE_VERSION) {
            return { kind: "noop" };
        }
        const applied = await applyWorkflowStatePatches(resolved.rawState, input.deps);
        const workflowState = (0, state_1.coercePersistedWorkflowStateValue)(applied.state);
        if (workflowState === null) {
            return {
                kind: "failed",
                reason: "patch engine produced invalid workflow state",
                scope: "issue",
            };
        }
        if (workflowState.version !== types_1.WORKFLOW_STATE_VERSION) {
            return {
                kind: "failed",
                reason: `patch engine produced version ${workflowState.version}`,
                scope: "repo",
            };
        }
        const patchedBody = await renderWorkflowStateComment({
            workflowState,
            issueId: input.issue.id,
            cwd: input.cwd,
            config: input.config,
            deps: input.deps,
            now: input.now,
        });
        if (patchedBody === resolved.comment.body) {
            return { kind: "noop" };
        }
        if (input.dryRun) {
            return { kind: "updated" };
        }
        const originalHash = (0, state_backup_1.sha256Hex)(resolved.comment.body);
        await (0, state_backup_1.appendStateBackupRecord)(input.backupFilePath, {
            type: "comment_backup",
            schemaVersion: 1,
            runId: input.runId,
            sequence: input.nextSequence(),
            createdAt: input.now.toISOString(),
            issue: {
                id: input.issue.id,
                displayId: input.issue.displayId,
                url: input.issue.url,
            },
            comment: {
                id: resolved.comment.id,
                url: resolved.comment.url,
                updatedAt: resolved.comment.updatedAt ?? null,
            },
            state: {
                fromVersion: resolved.version,
                toVersion: workflowState.version,
                appliedPatchVersions: applied.appliedPatchVersions,
            },
            hashes: {
                originalBodySha256: originalHash,
                patchedBodySha256: (0, state_backup_1.sha256Hex)(patchedBody),
            },
            originalBody: resolved.comment.body,
            patchedBody,
        });
        const precondition = await (0, state_canonical_comment_1.verifyLatestCanonicalCommentUnchanged)({
            ticketTracker: input.ticketTracker,
            issueId: input.issue.id,
            expectedCommentId: resolved.comment.id,
            expectedBodySha256: originalHash,
            changedReason: "canonical workflow state comment changed before update",
        });
        if (precondition.kind === "skipped") {
            return precondition;
        }
        await input.ticketTracker.updateComment(precondition.comment.id, patchedBody);
        return { kind: "updated" };
    }
    catch (error) {
        const reason = formatError(error);
        return {
            kind: "failed",
            reason,
            scope: resolvePatchFailureScope(reason),
        };
    }
}
function resolvePatchFailureScope(reason) {
    return reason.startsWith("Workflow state patch result is invalid for v")
        ? "issue"
        : "repo";
}
async function renderWorkflowStateComment(input) {
    if (input.deps.renderWorkflowStateComment) {
        return input.deps.renderWorkflowStateComment({
            workflowState: input.workflowState,
            issueId: input.issueId,
            cwd: input.cwd,
            config: input.config,
            now: input.now,
        });
    }
    return (0, canonical_workflow_state_1.formatCanonicalWorkflowStateCommentForRepo)(input.workflowState, input.cwd, {
        flavor: (0, registry_1.resolveTicketTrackerWorkflowStatePresentationFlavor)(input.config.ticketTracker.kind),
        locale: input.config.i18n.defaultLanguage,
        displayTimeZone: input.config.i18n.displayTimeZone,
        lastUpdated: input.now.toISOString(),
        issueId: input.issueId,
    });
}
async function applyWorkflowStatePatches(state, deps) {
    if (deps.applyWorkflowStatePatches) {
        return deps.applyWorkflowStatePatches(state);
    }
    return (0, patches_1.applyWorkflowStatePatches)({ state });
}
function resolveLatestCanonicalRawWorkflowState(comments) {
    for (const comment of [...comments].reverse()) {
        const extraction = (0, canonical_workflow_state_1.extractRawCanonicalWorkflowMachineState)(comment.body);
        if (extraction.kind === "missing") {
            continue;
        }
        if (extraction.kind !== "found") {
            return {
                kind: "invalid",
                comment,
                reason: extraction.summary,
            };
        }
        const version = readRawWorkflowStateVersion(extraction.value);
        if (version === null) {
            return {
                comment,
                kind: "invalid",
                reason: "canonical workflow state version missing",
            };
        }
        return {
            kind: "found",
            comment,
            rawState: extraction.value,
            version,
        };
    }
    return { kind: "missing" };
}
function readRawWorkflowStateVersion(state) {
    const candidate = asVersionedWorkflowStateCandidate(state);
    if (candidate === null || typeof candidate.version !== "number") {
        return null;
    }
    return candidate.version;
}
function asVersionedWorkflowStateCandidate(state) {
    if (typeof state !== "object" || state === null) {
        return null;
    }
    return state;
}
function createStatePatchSummary() {
    return {
        updated: 0,
        noop: 0,
        missing: 0,
        future: 0,
        skipped: 0,
        failed: 0,
    };
}
function recordPatchOutcome(summary, outcome) {
    summary[outcome.kind] += 1;
}
function formatError(error) {
    return error instanceof Error ? error.message : String(error);
}
