"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.runOnceCycle = runOnceCycle;
exports.unlockAiRunningIssues = unlockAiRunningIssues;
const network_recovery_state_1 = require("../../core/network-recovery-state");
const tracked_project_1 = require("../../core/tracked-project");
const provider_resolution_1 = require("../../platform/provider-resolution");
const workflow_labels_1 = require("../../platform/ticket-tracker/workflow-labels");
const types_1 = require("../../platform/workflow-state/types");
const publisher_shared_1 = require("../../workflows/publisher-shared");
const constants_1 = require("../constants");
const execution_ownership_1 = require("../execution-ownership");
const log_text_1 = require("../log-text");
const worker_slot_budget_1 = require("../worker-slot-budget");
const plan_progress_1 = require("../../workflows/plan-progress");
const recurring_1 = require("../../workflows/recurring");
const workflow_state_read_support_1 = require("../../workflows/workflow-state-read-support");
const workflow_setup_1 = require("./workflow-setup");
const issue_execution_1 = require("./issue-execution");
const pending_publication_recovery_1 = require("./pending-publication-recovery");
const pre_execution_1 = require("./pre-execution");
const presentation_1 = require("./presentation");
const publisher_factory_1 = require("./publisher-factory");
const selection_1 = require("./selection");
const shared_1 = require("./shared");
const workflow_template_root_1 = require("./workflow-template-root");
async function runOnceCycle(context) {
    await (0, workflow_template_root_1.refreshWorkflowTemplateRepoRoot)(context);
    const pendingPublicationSummaries = await (0, pending_publication_recovery_1.recoverPendingWaitHumanPublicationsForRun)(context);
    if (pendingPublicationSummaries.length > 0) {
        return finalizeCycleResult(context, pendingPublicationSummaries);
    }
    const cycleCandidates = await (0, pre_execution_1.loadCycleCandidates)(context);
    const workflowSetup = await (0, workflow_setup_1.runWorkflowSetupPhase)(context, cycleCandidates);
    const preExecution = await (0, pre_execution_1.runPreExecutionPhase)(context, workflowSetup.issues);
    const executionFrontier = await (0, selection_1.selectExecutionFrontier)(context, preExecution.executionIssues);
    const executionSummaries = await runExecutionPhase(context, executionFrontier);
    return finalizeCycleResult(context, [
        ...workflowSetup.summaries,
        ...preExecution.summaries,
        ...executionSummaries,
    ]);
}
async function unlockAiRunningIssues(ticketTracker, options) {
    const workflowLabels = options.trackedProject.ticketTracker.workflowLabels;
    const publisher = (0, publisher_factory_1.createRunWorkflowPublisher)({
        repoRoot: options.repoRoot ?? process.cwd(),
        displayTimeZone: options.displayTimeZone ?? "UTC",
        config: options.trackedProject,
        ticketTracker,
    });
    const candidateIssues = await loadUnlockCandidateIssues(ticketTracker, options.issueIds);
    const workflowStateByIssueId = await (0, workflow_state_read_support_1.loadPersistedWorkflowStatesForIssues)(ticketTracker, candidateIssues);
    const issues = selectUnlockCandidates(candidateIssues, workflowStateByIssueId, options.issueIds !== undefined);
    const trackedProjectKey = (0, provider_resolution_1.getTrackedProjectKey)((0, tracked_project_1.toTrackedProjectRef)(options.trackedProject));
    const unlockedIssueIds = new Set();
    let unlockedIssueCount = 0;
    for (const { issue, snapshot, workflowState } of issues) {
        const { logger } = (0, shared_1.buildIssueLogContext)(options.trackedProject, issue, options.logHomeDirectory);
        const nextSnapshot = (0, shared_1.cloneWorkflowStateSnapshot)(snapshot, types_1.WORKFLOW_STATUSES.aiReady);
        const nextWorkflowState = workflowState === null ? null : (0, execution_ownership_1.stripExecutionOwnership)(workflowState);
        await logger.appendRunStart({
            runKind: "unlock",
            issue,
            currentLabels: (0, workflow_labels_1.projectWorkflowLabelNames)(workflowLabels, issue.workflowState),
            pullRequestUrl: null,
        });
        await logger.appendSection((0, log_text_1.issueLogSection)("unlockReason"), [
            log_text_1.ISSUE_LOG_TEXT.unlockExplicitRelease,
            (0, log_text_1.issueLogField)("workflowBeforeUnlock", (0, presentation_1.formatWorkflowState)(workflowLabels, issue.workflowState)),
        ].join("\n"));
        await logger.appendSection((0, log_text_1.issueLogSection)("resolvedTransition"), (0, presentation_1.renderStatusTransitionSection)({
            workflowLabels,
            from: snapshot.status,
            to: nextSnapshot.status,
            facts: shared_1.EMPTY_FACTS,
            note: "unlock_requested",
            locale: logger.locale,
        }));
        await publisher.publishWorkflow({
            target: {
                kind: "existing_issue",
                issueId: issue.id,
                preloadedIssue: issue,
            },
            workflowState: nextWorkflowState,
            workflowProjection: {
                previous: issue.workflowState,
                next: nextSnapshot,
            },
            commentCursorPolicy: {
                mode: publisher_shared_1.WORKFLOW_COMMENT_CURSOR_POLICY_MODES.preserve,
            },
            responseLanguage: logger.locale,
        });
        await logger.appendSection((0, log_text_1.issueLogSection)("finalLabelsCloseResult"), (0, presentation_1.renderFinalWorkflowStateSection)(nextSnapshot, false, workflowLabels, logger.locale));
        await (0, network_recovery_state_1.removeNetworkRecoveryEntry)(trackedProjectKey, issue.id, undefined, options.logHomeDirectory);
        unlockedIssueIds.add(issue.id);
        unlockedIssueCount += 1;
    }
    for (const issueId of options.issueIds ?? []) {
        if (unlockedIssueIds.has(issueId)) {
            continue;
        }
        await (0, network_recovery_state_1.removeNetworkRecoveryEntry)(trackedProjectKey, issueId, undefined, options.logHomeDirectory);
    }
    return unlockedIssueCount;
}
async function loadUnlockCandidateIssues(ticketTracker, issueIds) {
    if (issueIds === undefined) {
        return (0, pre_execution_1.loadAiRunningIssues)(ticketTracker);
    }
    const issues = await Promise.all([...new Set(issueIds)].map((issueId) => ticketTracker.getIssue(issueId)));
    return issues.filter((issue) => issue.isOpen);
}
function selectUnlockCandidates(issues, workflowStateByIssueId, includeExplicitStaleOwnership) {
    return issues.flatMap((issue) => {
        const workflowState = workflowStateByIssueId.get(issue.id) ?? null;
        if (issue.workflowState.status !== types_1.WORKFLOW_STATUSES.aiRunning &&
            (!includeExplicitStaleOwnership ||
                workflowState?.executionOwnership === undefined)) {
            return [];
        }
        return [
            {
                issue,
                snapshot: issue.workflowState,
                workflowState,
            },
        ];
    });
}
async function runExecutionPhase(context, executionFrontier) {
    if (executionFrontier.length === 0) {
        return [];
    }
    if (context.workerSlotBudget === 0) {
        return Promise.all(executionFrontier.map((entry) => (0, issue_execution_1.executeFrontierIssue)({
            ...context,
            workerSlotBudget: 0,
        }, entry)));
    }
    const workerSlotRequirements = await resolveExecutionFrontierWorkerSlotCounts(context, executionFrontier);
    const zeroSlotFrontier = executionFrontier.filter((_entry, index) => workerSlotRequirements[index]?.workerSlotCount === 0);
    const workerFrontier = executionFrontier.filter((_entry, index) => (workerSlotRequirements[index]?.workerSlotCount ?? 1) > 0);
    const workerSlotDemands = workerSlotRequirements.flatMap((requirement) => requirement.workerSlotCount > 0 ? [requirement.workerSlotDemand] : []);
    const workerSlotBudgets = resolveExecutionFrontierWorkerSlotBudgets(context, workerFrontier.length, workerSlotDemands);
    const effectiveWorkerFrontier = workerFrontier.slice(0, workerSlotBudgets.length);
    const workerSlotBudgetByIssueId = new Map([
        ...zeroSlotFrontier.map((entry) => [entry.issue.id, 0]),
        ...effectiveWorkerFrontier.map((entry, index) => [entry.issue.id, workerSlotBudgets[index] ?? 1]),
    ]);
    return Promise.all(executionFrontier.flatMap((entry) => {
        const workerSlotBudget = workerSlotBudgetByIssueId.get(entry.issue.id);
        return workerSlotBudget === undefined
            ? []
            : [(0, issue_execution_1.executeFrontierIssue)({ ...context, workerSlotBudget }, entry)];
    }));
}
async function resolveExecutionFrontierWorkerSlotCounts(context, executionFrontier) {
    const workflowStateByIssueId = await (0, workflow_state_read_support_1.loadPersistedWorkflowStatesForIssues)(context.ticketTracker, executionFrontier.map((entry) => entry.issue));
    return executionFrontier.map((entry) => {
        const workflowState = workflowStateByIssueId.get(entry.issue.id) ?? null;
        const workerSlotCount = workflowState?.pendingPlanningIntent === undefined &&
            ((0, plan_progress_1.hasControllerChildIssueControlPlaneAction)(workflowState) ||
                (0, recurring_1.isRecurringParentWorkflowState)(workflowState))
            ? 0
            : 1;
        return {
            workerSlotCount,
            workerSlotDemand: workerSlotCount > 0
                ? (0, plan_progress_1.resolveWorkflowWorkerSlotDemand)(workflowState)
                : 0,
        };
    });
}
function resolveExecutionFrontierWorkerSlotBudgets(context, issueCount, issueDemands) {
    return (0, worker_slot_budget_1.distributeWorkerSlots)({
        issueCount,
        maxIssues: context.workerSlotBudget === undefined
            ? issueCount
            : context.config.parallelism.maxIssues,
        maxWorkers: context.workerSlotBudget ?? issueCount,
        mode: context.config.parallelism.mode,
        issueDemands,
    });
}
async function finalizeCycleResult(context, summaries) {
    const trackedProjectKey = (0, provider_resolution_1.getTrackedProjectKey)((0, tracked_project_1.toTrackedProjectRef)(context.config));
    await Promise.all(summaries
        .filter((summary) => summary.deferred_reason === null)
        .map((summary) => (0, network_recovery_state_1.removeNetworkRecoveryEntry)(trackedProjectKey, summary.issue_id, undefined, context.logHomeDirectory)));
    if (summaries.length === 0) {
        return {
            status: constants_1.RUN_CYCLE_STATUSES.noOp,
            // biome-ignore lint/style/useNamingConvention: CLI summary contract uses snake_case.
            processed_issue_count: 0,
            summaries: [],
        };
    }
    return {
        status: summaries.some((summary) => summary.outcome === constants_1.RUN_ISSUE_OUTCOMES.failed)
            ? constants_1.RUN_CYCLE_STATUSES.failed
            : constants_1.RUN_CYCLE_STATUSES.processed,
        // biome-ignore lint/style/useNamingConvention: CLI summary contract uses snake_case.
        processed_issue_count: summaries.length,
        summaries,
    };
}
