"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadCycleCandidates = loadCycleCandidates;
exports.loadAiRunningIssues = loadAiRunningIssues;
exports.selectAiRunningIssues = selectAiRunningIssues;
exports.runPreExecutionPhase = runPreExecutionPhase;
exports.processPullRequestMonitorIssue = processPullRequestMonitorIssue;
exports.processWorkingInPrIssue = processWorkingInPrIssue;
const workflow_update_1 = require("../../comments/workflow-update");
const control_plane_config_1 = require("../../config/control-plane-config");
const catalog_1 = require("../../i18n/catalog");
const constants_1 = require("../../platform/constants");
const types_1 = require("../../platform/ticket-tracker/types");
const workflow_labels_1 = require("../../platform/ticket-tracker/workflow-labels");
const types_2 = require("../../platform/workflow-state/types");
const constants_2 = require("../../worker/constants");
const auto_merge_state_1 = require("../../workflows/auto-merge-state");
const publisher_1 = require("../../workflows/publisher");
const visible_update_builders_1 = require("../../workflows/visible-update-builders");
const publisher_events_1 = require("../../workflows/publisher-events");
const workflow_state_read_support_1 = require("../../workflows/workflow-state-read-support");
const workflow_timeline_scan_1 = require("../../workflows/workflow-timeline-scan");
const constants_3 = require("../constants");
const log_text_1 = require("../log-text");
const effect_log_1 = require("./effect-log");
const effects_1 = require("./effects");
const needs_human_merge_1 = require("./needs-human-merge");
const pr_publication_1 = require("./pr-publication");
const publisher_factory_1 = require("./publisher-factory");
const pull_request_auto_merge_1 = require("./pull-request-auto-merge");
const pull_request_review_necessity_1 = require("./pull-request-review-necessity");
const pull_request_completion_1 = require("./pull-request-completion");
const results_1 = require("./results");
const shared_1 = require("./shared");
const terminal_source_finalization_1 = require("./terminal-source-finalization");
async function loadCycleCandidates(context) {
    return loadCandidateIssues(context.config, context.ticketTracker, context.issueIds, context.currentUserLogin, context.runSource, context.prefetchedIssues);
}
async function loadAiRunningIssues(ticketTracker, issueIds, prefetchedIssues) {
    const issues = await loadOpenIssues(ticketTracker, issueIds, prefetchedIssues);
    return issues.filter((issue) => issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiRunning);
}
function selectAiRunningIssues(issues) {
    return (0, shared_1.collectManagedIssues)(issues).filter((entry) => entry.snapshot.status === types_2.WORKFLOW_STATUSES.aiRunning);
}
async function runPreExecutionPhase(context, issues) {
    const workingInPrSummaries = await runPreExecutionIssueGroup(selectAiWorkingInPrIssues(issues), (entry) => processWorkingInPrIssue({
        context,
        executable: entry,
    }));
    const workingInPrHandledIssueIds = collectSummaryIssueIds(workingInPrSummaries);
    const terminalSourceFinalizationSummaries = await runPreExecutionIssueGroup(selectTerminalSourceFinalizationIssues(issues).filter((issue) => !workingInPrHandledIssueIds.has(issue.id)), (issue) => processTerminalSourceFinalizationIssue({
        context,
        issue,
    }));
    const terminalSourceFinalizationHandledIssueIds = collectSummaryIssueIds([
        ...workingInPrSummaries,
        ...terminalSourceFinalizationSummaries,
    ]);
    const monitoringSummaries = await runPreExecutionIssueGroup(selectAiNeedsHumanIssues(issues).filter((entry) => !terminalSourceFinalizationHandledIssueIds.has(entry.issue.id)), (entry) => processPullRequestMonitorIssue({
        context,
        executable: entry,
    }));
    const monitorHandledIssueIds = collectSummaryIssueIds([
        ...workingInPrSummaries,
        ...terminalSourceFinalizationSummaries,
        ...monitoringSummaries,
    ]);
    const reviewNecessityIssues = (await (0, pull_request_review_necessity_1.selectPullRequestReviewNecessityIssues)(context.ticketTracker, issues)).filter((entry) => !monitorHandledIssueIds.has(entry.issue.id));
    const reviewNecessitySummaries = await runPreExecutionIssueGroup(reviewNecessityIssues, (entry) => (0, pull_request_review_necessity_1.processPullRequestReviewNecessityIssue)({
        context,
        executable: entry,
    }));
    const reviewNecessityHandledIssueIds = collectSummaryIssueIds([
        ...workingInPrSummaries,
        ...terminalSourceFinalizationSummaries,
        ...monitoringSummaries,
        ...reviewNecessitySummaries,
    ]);
    const autoMergeIssues = (await selectAutoMergeMonitorIssues(context.ticketTracker, issues)).filter((entry) => !reviewNecessityHandledIssueIds.has(entry.issue.id));
    const autoMergeSummaries = await runPreExecutionIssueGroup(autoMergeIssues, (entry) => processPullRequestMonitorIssue({
        context,
        executable: entry,
    }));
    const summaries = [
        ...workingInPrSummaries,
        ...terminalSourceFinalizationSummaries,
        ...monitoringSummaries,
        ...reviewNecessitySummaries,
        ...autoMergeSummaries,
    ];
    if (summaries.length === 0 && autoMergeIssues.length === 0) {
        return {
            executionIssues: [...issues],
            summaries,
        };
    }
    const handledIssueIds = new Set([
        ...summaries.map((summary) => summary.issue_id),
        ...autoMergeIssues.map((entry) => entry.issue.id),
    ]);
    const requestedExecutionIssueIds = context.issueIds && handledIssueIds.size > 0
        ? context.issueIds.filter((issueId) => !handledIssueIds.has(issueId))
        : context.issueIds;
    const executionIssues = requestedExecutionIssueIds?.length === 0
        ? []
        : (await loadCandidateIssues(context.config, context.ticketTracker, requestedExecutionIssueIds, context.currentUserLogin, context.runSource)).filter((issue) => !handledIssueIds.has(issue.id));
    return {
        executionIssues,
        summaries,
    };
}
async function runPreExecutionIssueGroup(entries, handler) {
    return (await Promise.all(entries.map(handler))).filter((summary) => summary !== null);
}
function collectSummaryIssueIds(summaries) {
    return new Set(summaries.map((summary) => summary.issue_id));
}
async function processPullRequestMonitorIssue(input) {
    const bootstrap = await (0, shared_1.loadPullRequestMonitorBootstrap)(input);
    if (isAutoMergeMonitorIssue(input.executable)) {
        return handleAutoMergeMonitor({
            executable: input.executable,
            bootstrap,
        });
    }
    return handleNeedsHumanPullRequestMonitor({
        executable: input.executable,
        bootstrap,
    });
}
async function processWorkingInPrIssue(input) {
    const { executable } = input;
    const { comments, issueContext, logger, responseLanguage, pullRequest } = await (0, shared_1.loadWorkingInPrBootstrap)(input);
    if (pullRequest?.state === constants_1.PLATFORM_PULL_REQUEST_STATES.open) {
        return null;
    }
    if (pullRequest?.state === constants_1.PLATFORM_PULL_REQUEST_STATES.merged) {
        return finalizeCompletedWorkingInPrIssue({
            context: issueContext,
            issue: executable.issue,
            comments,
            logger,
            responseLanguage,
            pullRequest,
            completionReason: "merged",
        });
    }
    const sourceWorkflowState = (0, needs_human_merge_1.resolveWorkflowStateForPublication)(comments, responseLanguage);
    const restoredWorkflowState = sourceWorkflowState;
    const trackerTitle = (0, effects_1.resolveRenderedTitleIfChanged)({
        issue: executable.issue,
        workflowState: restoredWorkflowState,
        locale: responseLanguage,
        suppressDoneLabel: true,
        surface: "issue",
        enabled: issueContext.config.ticketTracker.showStepProgressInTitles === true,
    });
    const publisher = (0, publisher_factory_1.createRunWorkflowPublisher)(issueContext);
    const restorePlan = (0, pr_publication_1.buildWorkingInPrRestorePublicationPlan)({
        workflowLabels: issueContext.config.ticketTracker.workflowLabels,
        responseLanguage,
        sourceIssue: executable.issue,
        sourceWorkflowState: restoredWorkflowState,
        sourceNeedsHumanReason: (0, workflow_update_1.findLatestNeedsHumanReason)(comments),
        visibleUpdate: (0, visible_update_builders_1.buildWorkflowVisibleUpdate)({
            event: publisher_events_1.WORKFLOW_VISIBLE_EVENTS.humanAttentionRequested,
            summary: buildWorkingInPrClosedSummary(responseLanguage),
            next: buildWorkingInPrClosedNextStep(responseLanguage),
            workerOutcome: constants_2.WORKER_OUTCOMES.waitHuman,
        }),
        ...(trackerTitle !== null ? { trackerTitle } : {}),
    });
    if (restorePlan.kind !== "working_in_pr_restore") {
        throw new Error("Expected a working-in-PR restore publication plan.");
    }
    const publication = await publisher.publishWorkflow(restorePlan.sourceIssueRequest);
    executable.issue.workflowState = {
        ...(publication.targetWorkflowProjection ?? {
            status: types_2.WORKFLOW_STATUSES.aiNeedsHuman,
            rules: [...executable.snapshot.rules],
        }),
    };
    await (0, effect_log_1.appendEffectResultLog)(logger, [
        (0, log_text_1.issueLogField)("effect", "restore_issue_from_pr"),
        (0, log_text_1.issueLogField)("pr", pullRequest?.url ?? "-"),
    ]);
    const summary = (0, results_1.buildRunCliSummary)({
        issue: executable.issue,
        outcome: constants_3.RUN_ISSUE_OUTCOMES.queuedHuman,
        responseLanguage,
        summary: buildWorkingInPrClosedSummary(responseLanguage),
        commentUrl: (0, publisher_factory_1.resolveRunWorkflowPublicationCommentUrl)(publication),
        prUrl: pullRequest?.url ?? null,
        nextStep: buildWorkingInPrClosedNextStep(responseLanguage),
    });
    return (0, shared_1.returnLoggedSummary)(logger, summary);
}
async function processTerminalSourceFinalizationIssue(input) {
    const { comments, issueContext, logger, responseLanguage, workflowLabels } = await (0, shared_1.loadPreExecutionIssueBootstrap)({
        context: input.context,
        issue: input.issue,
    });
    const workflowState = (0, needs_human_merge_1.resolveWorkflowStateForPublication)(comments, responseLanguage);
    const candidate = await (0, terminal_source_finalization_1.resolveTerminalSourceFinalizationCandidate)({
        config: issueContext.config,
        repositoryHost: issueContext.repositoryHost,
        issue: input.issue,
        workflowState,
    });
    if (candidate === null) {
        return null;
    }
    await logger.appendRunStart({
        runKind: "preflight_handoff",
        issue: input.issue,
        currentLabels: (0, workflow_labels_1.projectWorkflowLabelNames)(workflowLabels, input.issue.workflowState),
        pullRequestUrl: candidate.pullRequest.url,
    });
    return finalizeCompletedWorkingInPrIssue({
        context: issueContext,
        issue: input.issue,
        comments,
        logger,
        responseLanguage,
        pullRequest: candidate.pullRequest,
        completionReason: candidate.completion.reason,
    });
}
async function finalizeCompletedWorkingInPrIssue(input) {
    try {
        const closingTransition = await (0, needs_human_merge_1.publishWorkingInPrMergeFinalization)({
            context: input.context,
            issue: input.issue,
            issueComments: input.comments,
            logger: input.logger,
            pullRequest: input.pullRequest,
            responseLanguage: input.responseLanguage,
        });
        if (closingTransition !== null) {
            return closingTransition;
        }
        await (0, effect_log_1.appendEffectResultLog)(input.logger, [
            (0, log_text_1.issueLogField)("effect", "finalize_issue_after_pr_merge"),
            (0, log_text_1.issueLogField)("pr", input.pullRequest.url),
            (0, log_text_1.issueLogField)("note", input.completionReason),
        ]);
        const summary = (0, results_1.buildRunCliSummary)({
            issue: input.issue,
            outcome: constants_3.RUN_ISSUE_OUTCOMES.queuedHuman,
            responseLanguage: input.responseLanguage,
            summary: buildWorkingInPrMergedSummary(input.responseLanguage),
            commentUrl: null,
            prUrl: input.pullRequest.url,
            nextStep: buildWorkingInPrMergedNextStep(input.responseLanguage),
        });
        return (0, shared_1.returnLoggedSummary)(input.logger, summary);
    }
    catch (error) {
        const isCloseFailure = (0, publisher_1.isWorkflowTerminalActionError)(error, "close");
        const retrySummary = isCloseFailure
            ? buildWorkingInPrMergedCloseRetrySummary(input.responseLanguage)
            : buildWorkingInPrMergedFinalizationRetrySummary(input.responseLanguage);
        const retryNextStep = isCloseFailure
            ? buildWorkingInPrMergedCloseRetryNextStep(input.responseLanguage)
            : buildWorkingInPrMergedFinalizationRetryNextStep(input.responseLanguage);
        const publication = await (0, needs_human_merge_1.publishControlPlaneNeedsHumanUpdate)({
            context: input.context,
            issue: input.issue,
            responseLanguage: input.responseLanguage,
            summary: retrySummary,
            next: retryNextStep,
            needsHumanReason: isCloseFailure
                ? publisher_events_1.WORKFLOW_NEEDS_HUMAN_REASONS.closeFailed
                : publisher_events_1.WORKFLOW_NEEDS_HUMAN_REASONS.systemError,
            eventHookOrigin: "control_plane_error_handoff",
            eventHookLogger: input.logger,
        });
        await (0, effect_log_1.appendEffectFailureLog)(input.logger, [
            (0, log_text_1.issueLogField)("effect", "close_issue_after_pr_merge"),
            (0, log_text_1.issueLogField)("pr", input.pullRequest.url),
            (0, results_1.renderFailureDetails)(error, input.logger.locale),
        ]);
        const summary = (0, results_1.buildRunCliSummary)({
            issue: input.issue,
            outcome: constants_3.RUN_ISSUE_OUTCOMES.queuedHuman,
            responseLanguage: input.responseLanguage,
            summary: retrySummary,
            commentUrl: publication.visibleUpdateComment?.url ?? null,
            prUrl: input.pullRequest.url,
            nextStep: retryNextStep,
        });
        return (0, shared_1.returnLoggedSummary)(input.logger, summary);
    }
}
async function loadCandidateIssues(config, ticketTracker, issueIds, currentUserLogin, runSource, prefetchedIssues) {
    void config;
    const issues = await loadOpenIssues(ticketTracker, issueIds, prefetchedIssues);
    if (runSource !== "service") {
        return issues;
    }
    if (!currentUserLogin) {
        return [];
    }
    return issues.filter((issue) => isServiceAssignableToCurrentUser(issue.assignees, currentUserLogin));
}
async function loadOpenIssues(ticketTracker, issueIds, prefetchedIssues) {
    if (prefetchedIssues) {
        const openPrefetchedIssues = prefetchedIssues.filter((issue) => issue.isOpen);
        if (issueIds === undefined) {
            return [...openPrefetchedIssues];
        }
        const issueIdsSet = new Set(issueIds);
        const matchedIssues = openPrefetchedIssues.filter((issue) => issueIdsSet.has(issue.id));
        if (matchedIssues.length === issueIdsSet.size) {
            return matchedIssues;
        }
    }
    if (issueIds === undefined) {
        return ticketTracker.listIssues({
            state: "open",
        });
    }
    const uniqueIssueIds = [...new Set(issueIds)];
    const issues = await Promise.all(uniqueIssueIds.map((issueId) => ticketTracker.getIssue(issueId)));
    return issues.filter((issue) => issue.isOpen);
}
function isServiceAssignableToCurrentUser(assignees, currentUserLogin) {
    return assignees.length === 1 && assignees[0] === currentUserLogin;
}
function selectAiNeedsHumanIssues(issues) {
    return (0, shared_1.collectManagedIssues)(issues).filter((entry) => entry.snapshot.status === types_2.WORKFLOW_STATUSES.aiNeedsHuman &&
        entry.issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest);
}
function selectAiWorkingInPrIssues(issues) {
    return (0, shared_1.collectManagedIssues)(issues).filter((entry) => entry.snapshot.status === types_2.WORKFLOW_STATUSES.aiWorkingInPr &&
        entry.issue.kind === types_1.TRACKER_ITEM_KINDS.issue);
}
function selectTerminalSourceFinalizationIssues(issues) {
    return (0, shared_1.collectManagedIssues)(issues).flatMap((entry) => {
        if (entry.issue.kind !== types_1.TRACKER_ITEM_KINDS.issue ||
            (entry.snapshot.status !== types_2.WORKFLOW_STATUSES.aiReady &&
                entry.snapshot.status !== types_2.WORKFLOW_STATUSES.aiRunning)) {
            return [];
        }
        return [entry.issue];
    });
}
async function selectAutoMergeMonitorIssues(ticketTracker, issues) {
    const workflowStateByIssueId = await (0, workflow_state_read_support_1.loadPersistedWorkflowStatesForIssues)(ticketTracker, issues);
    return (0, shared_1.collectManagedIssues)(issues).flatMap((entry) => {
        if (!(0, types_2.isRunnableWorkflowState)(entry.snapshot)) {
            return [];
        }
        if (entry.issue.kind !== types_1.TRACKER_ITEM_KINDS.pullRequest) {
            return [];
        }
        const workflowState = workflowStateByIssueId.get(entry.issue.id);
        if (workflowState === undefined ||
            workflowState === null ||
            !(0, auto_merge_state_1.hasActiveAutoMergeState)(workflowState)) {
            return [];
        }
        return [
            {
                issue: entry.issue,
                snapshot: entry.snapshot,
                workflowState,
            },
        ];
    });
}
function isAutoMergeMonitorIssue(executable) {
    return "workflowState" in executable;
}
async function handleNeedsHumanPullRequestMonitor(input) {
    const latestNeedsHumanReason = (0, workflow_update_1.findLatestNeedsHumanReason)(input.bootstrap.comments);
    if (input.executable.issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest &&
        input.bootstrap.pullRequest !== null) {
        const completion = await (0, pull_request_completion_1.resolvePullRequestCompletion)({
            repositoryHost: input.bootstrap.issueContext.repositoryHost,
            pullRequest: input.bootstrap.pullRequest,
            logger: input.bootstrap.logger,
            branchName: input.bootstrap.branchName,
        });
        if (completion.status === "completed") {
            return (0, needs_human_merge_1.retryMergedPullRequestFinalization)(input.bootstrap.issueContext, input.bootstrap.issueForBranch, input.bootstrap.logger, input.bootstrap.branchName, completion.pullRequest, input.bootstrap.responseLanguage, undefined, latestNeedsHumanReason, completion.reason);
        }
    }
    return null;
}
async function handleAutoMergeMonitor(input) {
    const clearAutoMergeMonitorState = async () => {
        const publication = await (0, needs_human_merge_1.publishAutoMergeState)({
            context: input.bootstrap.issueContext,
            issue: input.executable.issue,
            responseLanguage: input.bootstrap.responseLanguage,
            workflowState: (0, needs_human_merge_1.resolveAutoMergeWorkflowState)({
                workflowState: input.executable.workflowState,
                autoMergeState: null,
            }),
        });
        if (publication.targetWorkflowProjection !== null) {
            input.executable.issue.workflowState = {
                ...publication.targetWorkflowProjection,
            };
        }
    };
    if ((0, workflow_timeline_scan_1.hasActionableWorkflowCommentAfterCursor)(input.bootstrap.comments, input.executable.workflowState.lastScannedCommentCursor ?? null)) {
        await clearAutoMergeMonitorState();
        await (0, effect_log_1.appendEffectResultLog)(input.bootstrap.logger, [
            (0, log_text_1.issueLogField)("effect", "cancel_auto_merge_monitor"),
            (0, log_text_1.issueLogField)("reason", "new_actionable_comment"),
        ]);
        return null;
    }
    if (!(0, control_plane_config_1.isIssueCompletionAutoMergeEnabled)(input.bootstrap.issueContext.config)) {
        await clearAutoMergeMonitorState();
        await (0, effect_log_1.appendEffectResultLog)(input.bootstrap.logger, [
            (0, log_text_1.issueLogField)("effect", "cancel_auto_merge_monitor"),
            (0, log_text_1.issueLogField)("reason", "pull_request_merge_policy_changed"),
        ]);
        return null;
    }
    return (0, pull_request_auto_merge_1.continuePullRequestAutoMerge)({
        context: input.bootstrap.issueContext,
        issue: input.bootstrap.issueForBranch,
        logger: input.bootstrap.logger,
        branchName: input.bootstrap.branchName,
        responseLanguage: input.bootstrap.responseLanguage,
        workflowState: input.executable.workflowState,
        publishAutoMergePendingUpdateWhenWaiting: false,
        pullRequest: input.bootstrap.pullRequest,
        automationState: input.bootstrap.automationState,
    });
}
function buildWorkingInPrMergedSummary(responseLanguage) {
    return (0, catalog_1.translate)(responseLanguage, "run.workingInPr.merged.summary");
}
function buildWorkingInPrMergedNextStep(responseLanguage) {
    return (0, catalog_1.translate)(responseLanguage, "run.workingInPr.merged.next");
}
function buildWorkingInPrClosedSummary(responseLanguage) {
    return (0, catalog_1.translate)(responseLanguage, "run.workingInPr.closed.summary");
}
function buildWorkingInPrClosedNextStep(responseLanguage) {
    return (0, catalog_1.translate)(responseLanguage, "run.workingInPr.closed.next");
}
function buildWorkingInPrMergedCloseRetrySummary(responseLanguage) {
    return (0, catalog_1.translate)(responseLanguage, "run.workingInPr.closeRetry.summary");
}
function buildWorkingInPrMergedCloseRetryNextStep(responseLanguage) {
    return (0, catalog_1.translate)(responseLanguage, "run.workingInPr.closeRetry.next");
}
function buildWorkingInPrMergedFinalizationRetrySummary(responseLanguage) {
    return (0, catalog_1.translate)(responseLanguage, "run.workingInPr.finalizationRetry.summary");
}
function buildWorkingInPrMergedFinalizationRetryNextStep(responseLanguage) {
    return (0, catalog_1.translate)(responseLanguage, "run.workingInPr.finalizationRetry.next");
}
