"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handOffIssueToHuman = handOffIssueToHuman;
exports.handOffDependencyIssueToHuman = handOffDependencyIssueToHuman;
exports.handOffWorkspaceSyncBlockedIssueToHuman = handOffWorkspaceSyncBlockedIssueToHuman;
exports.handOffClaimedIssueToHuman = handOffClaimedIssueToHuman;
exports.deferIssueForCooldown = deferIssueForCooldown;
const workflow_labels_1 = require("../../platform/ticket-tracker/workflow-labels");
const types_1 = require("../../platform/workflow-state/types");
const template_bindings_1 = require("../../workflows/template-bindings");
const scheduled_run_1 = require("../../workflows/scheduled-run");
const workflow_projection_support_1 = require("../../workflows/workflow-projection-support");
const default_issue_workspace_manager_1 = require("../../workspaces/default-issue-workspace-manager");
const constants_1 = require("../constants");
const execution_ownership_1 = require("../execution-ownership");
const issue_logger_1 = require("../issue-logger");
const log_text_1 = require("../log-text");
const effects_1 = require("./effects");
const issue_branch_target_1 = require("./issue-branch-target");
const run_state_helpers_1 = require("./run-state-helpers");
const presentation_1 = require("./presentation");
const owned_workflow_state_publication_1 = require("./owned-workflow-state-publication");
const event_hook_publication_1 = require("./event-hook-publication");
const results_1 = require("./results");
const shared_1 = require("./shared");
const transitions_1 = require("./transitions");
const types_2 = require("./types");
const workflow_template_root_1 = require("./workflow-template-root");
async function handOffIssueToHuman(input) {
    const { context, executable, logger, gate, workflowLabels, dependencies } = input;
    return publishWaitHumanTransition({
        context,
        issue: gate.latestIssue,
        previousSnapshot: executable.snapshot,
        readySnapshot: executable.snapshot,
        runMode: gate.workflow?.mode ?? (0, run_state_helpers_1.inferWorkflowRunMode)(gate.workflowState),
        summary: gate.summary,
        needsHumanReason: gate.needsHumanReason,
        invalidWorkflowStatePolicy: gate.invalidWorkflowStatePolicy,
        workflowTemplateName: gate.workflowTemplateName,
        workflowState: gate.workflowState,
        consecutiveAiRuns: gate.currentConsecutiveAiRuns,
        totalAiRunHours: gate.currentTotalAiRunHours,
        responseLanguage: context.config.i18n.defaultLanguage,
        logger,
        workflowLabels,
        dependencies,
        recordsRunOutcome: false,
        incrementsConsecutiveAiRuns: false,
    });
}
async function handOffDependencyIssueToHuman(input) {
    const comments = await (0, shared_1.loadSortedIssueComments)(input.context.ticketTracker, input.executable.issue.id);
    const { logger, responseLanguage } = (0, shared_1.buildIssueLogContext)(input.context.config, input.executable.issue, input.context.logHomeDirectory);
    await logger.appendRunStart({
        runKind: "preflight_handoff",
        issue: input.executable.issue,
        currentLabels: (0, workflow_labels_1.projectWorkflowLabelNames)(input.context.config.ticketTracker.workflowLabels, input.executable.issue.workflowState),
        pullRequestUrl: null,
    });
    const aiRunAccounting = (0, shared_1.resolveCurrentAiRunAccounting)(comments);
    return publishWaitHumanTransition({
        context: input.context,
        issue: input.executable.issue,
        previousSnapshot: input.executable.snapshot,
        readySnapshot: input.executable.snapshot,
        runMode: (0, run_state_helpers_1.inferWorkflowRunMode)(input.workflowState),
        summary: input.summary,
        needsHumanReason: types_2.WORKER_NEEDS_HUMAN_REASONS.question,
        workflowTemplateName: input.workflowState === null
            ? null
            : (0, template_bindings_1.getWorkflowTemplateName)((0, template_bindings_1.getActiveWorkflowTemplateBinding)(input.workflowState)),
        workflowState: input.workflowState,
        consecutiveAiRuns: aiRunAccounting.consecutiveAiRuns,
        totalAiRunHours: aiRunAccounting.totalAiRunHours,
        responseLanguage,
        logger,
        workflowLabels: input.context.config.ticketTracker.workflowLabels,
        dependencies: input.dependencies,
    });
}
async function handOffWorkspaceSyncBlockedIssueToHuman(input) {
    const responseLanguage = input.context.config.i18n.defaultLanguage;
    return handOffClaimedIssueToHuman({
        context: input.context,
        issue: input.claimedIssue.originalIssue,
        claimedIssue: input.claimedIssue,
        summary: (0, run_state_helpers_1.buildWorkspaceSyncBlockedSummary)({
            responseLanguage,
            reason: input.blockedError.reason,
            conflictedFiles: input.blockedError.conflictedFiles ?? [],
        }),
        needsHumanReason: types_2.WORKER_NEEDS_HUMAN_REASONS.question,
        responseLanguage,
        logger: input.logger,
        failureSeed: input.failureSeed,
        workflowLabels: input.workflowLabels,
        dependencies: input.dependencies,
    });
}
async function handOffClaimedIssueToHuman(input) {
    const summary = await publishWaitHumanTransition({
        context: input.context,
        issue: input.issue,
        previousSnapshot: input.claimedIssue.aiRunningSnapshot,
        readySnapshot: input.claimedIssue.aiReadySnapshot,
        runMode: input.failureSeed.runMode ??
            (0, run_state_helpers_1.inferWorkflowRunMode)(input.failureSeed.workflowState),
        summary: input.summary,
        needsHumanReason: input.needsHumanReason,
        workflowTemplateName: input.failureSeed.workflowTemplateName,
        workflowState: input.failureSeed.workflowState === null
            ? null
            : (0, execution_ownership_1.stripExecutionOwnership)(input.failureSeed.workflowState),
        assigneeResolution: input.failureSeed.assigneeResolution ?? null,
        consecutiveAiRuns: input.failureSeed.consecutiveAiRuns,
        totalAiRunHours: input.failureSeed.totalAiRunHours,
        responseLanguage: input.responseLanguage,
        logger: input.logger,
        workflowLabels: input.workflowLabels,
        dependencies: input.dependencies,
        ...(input.recordsRunOutcome !== undefined
            ? { recordsRunOutcome: input.recordsRunOutcome }
            : {}),
        ...(input.incrementsConsecutiveAiRuns !== undefined
            ? { incrementsConsecutiveAiRuns: input.incrementsConsecutiveAiRuns }
            : {}),
        ...(input.publicationOrigin !== undefined
            ? { publicationOrigin: input.publicationOrigin }
            : {}),
    });
    return summary;
}
async function publishWaitHumanTransition(input) {
    const branchName = input.issue.sourceBranchName ??
        (0, default_issue_workspace_manager_1.resolveIssueBranchName)(input.context.config.branchPrefix, input.issue);
    const baseBranch = input.workflowState === null
        ? input.context.config.repositoryHost.baseBranch
        : (0, issue_branch_target_1.resolveIssueRunBaseBranch)(input.workflowState);
    const routing = await (0, effects_1.collectExistingPullRequestResolution)(input.context.ticketTracker, input.context.repositoryHost, branchName, baseBranch);
    const transitionState = (0, transitions_1.buildWaitHumanTransitionState)({
        config: input.context.config,
        previousSnapshot: input.previousSnapshot,
        readySnapshot: input.readySnapshot,
        issue: input.issue,
        runMode: input.runMode,
        summary: input.summary,
        needsHumanReason: input.needsHumanReason,
        ...(input.invalidWorkflowStatePolicy !== undefined
            ? { invalidWorkflowStatePolicy: input.invalidWorkflowStatePolicy }
            : {}),
        facts: routing.facts,
        branchName,
        baseBranch,
        pullRequestThreadId: routing.pullRequestThreadId,
        pullRequestUrl: routing.pullRequestUrl,
        workflowTemplateName: input.workflowTemplateName,
        workflowState: input.workflowState,
        assigneeResolution: input.assigneeResolution ?? null,
        consecutiveAiRuns: input.consecutiveAiRuns,
        ...(input.totalAiRunHours !== undefined
            ? { totalAiRunHours: input.totalAiRunHours }
            : {}),
        responseLanguage: input.responseLanguage,
    });
    if (input.recordsRunOutcome !== undefined) {
        transitionState.recordsRunOutcome = input.recordsRunOutcome;
    }
    if (input.incrementsConsecutiveAiRuns !== undefined) {
        transitionState.incrementsConsecutiveAiRuns =
            input.incrementsConsecutiveAiRuns;
    }
    return input.dependencies.publishTransitionSummary({
        repoRoot: (0, workflow_template_root_1.getWorkflowTemplateRepoRoot)(input.context),
        config: input.context.config,
        currentUserLogin: input.context.currentUserLogin,
        repositoryHost: input.context.repositoryHost,
        ticketTracker: input.context.ticketTracker,
        issueId: input.issue.id,
        transitionState,
        logger: input.logger,
        workflowLabels: input.workflowLabels,
        fromStatus: input.previousSnapshot.status,
        resolvedFacts: routing.facts,
        ...(input.publicationOrigin !== undefined
            ? { publicationOrigin: input.publicationOrigin }
            : {}),
        worker: input.context.worker,
        ...(input.context.logHomeDirectory !== undefined
            ? { homeDirectory: input.context.logHomeDirectory }
            : {}),
    });
}
async function deferIssueForCooldown(context, claimedIssue, logger, deferred) {
    const routing = await (0, effects_1.collectExistingPullRequestResolution)(context.ticketTracker, context.repositoryHost, deferred.branchName, (0, issue_branch_target_1.resolveIssueRunBaseBranch)(deferred.workflowState));
    const cooldownSchedule = resolveDeferredCooldownSchedule(context, deferred, new Date());
    const presentation = (0, presentation_1.buildDeferredPresentation)(deferred.responseLanguage, cooldownSchedule.runAt, deferred.triedProfiles);
    const runSummary = {
        runMode: null,
        workerOutcome: null,
        needsHumanReason: null,
        summary: presentation.summary,
        nextStep: presentation.next,
        workflowTemplateName: null,
    };
    let finalSnapshot = claimedIssue.aiRunningSnapshot;
    await logger.appendSection((0, log_text_1.issueLogSection)("resolvedTransition"), [
        (0, presentation_1.renderStatusTransitionSection)({
            workflowLabels: context.config.ticketTracker.workflowLabels,
            from: claimedIssue.aiRunningSnapshot.status,
            to: types_1.WORKFLOW_STATUSES.aiScheduled,
            facts: routing.facts,
            note: deferred.deferredReason,
            locale: logger.locale,
        }),
        (0, log_text_1.issueLogField)("triedSignIns", (0, issue_logger_1.formatList)(deferred.triedProfiles)),
        (0, log_text_1.issueLogField)("cooldownUntil", (0, issue_logger_1.formatNullable)(cooldownSchedule.runAt)),
    ].join("\n"));
    await (0, owned_workflow_state_publication_1.publishOwnedIssueWorkflowState)({
        context,
        claimedIssue,
        issue: deferred.issue,
        responseLanguage: deferred.responseLanguage,
        buildWorkflowState: (workflowState) => resolveDeferredWorkflowState(workflowState, deferred, cooldownSchedule),
        buildWorkflowProjection: ({ currentWorkflowState, nextWorkflowState }) => {
            finalSnapshot = (0, scheduled_run_1.resolveScheduledRunProjection)({
                snapshot: claimedIssue.aiRunningSnapshot,
                workflowState: nextWorkflowState,
            }).snapshot;
            return (0, workflow_projection_support_1.buildWorkflowProjectionWithAuxiliaryLabels)({
                workflowLabels: context.config.ticketTracker.workflowLabels,
                previous: claimedIssue.aiRunningSnapshot,
                next: finalSnapshot,
                previousWorkflowState: currentWorkflowState,
                nextWorkflowState,
                previousNeedsHumanReason: currentWorkflowState.needsHumanReason ?? null,
                nextNeedsHumanReason: nextWorkflowState.needsHumanReason ?? null,
            });
        },
        afterPublish: ({ request, result }) => (0, event_hook_publication_1.runEventHooksForRunPublication)({
            context,
            request,
            result,
            run: runSummary,
            logger,
        }),
    });
    await logger.appendSection((0, log_text_1.issueLogSection)("finalLabelsCloseResult"), (0, presentation_1.renderFinalWorkflowStateSection)(finalSnapshot, false, context.config.ticketTracker.workflowLabels, logger.locale));
    return (0, results_1.buildRunCliSummary)({
        issue: deferred.issue,
        outcome: constants_1.RUN_ISSUE_OUTCOMES.deferred,
        responseLanguage: deferred.responseLanguage,
        summary: presentation.summary,
        commentUrl: null,
        prUrl: routing.pullRequestUrl,
        nextStep: presentation.next,
        deferredReason: deferred.deferredReason,
        triedProfiles: [...deferred.triedProfiles],
        cooldownUntil: cooldownSchedule.runAt,
    });
}
function resolveDeferredCooldownSchedule(context, deferred, now) {
    const selectedCooldown = selectDeferredCooldown(deferred);
    const reason = selectedCooldown
        ? resolveCooldownReasonFromLastReason(selectedCooldown.lastReason)
        : resolveDeferredCooldownReason(deferred);
    const fallbackCooldownUntil = new Date(now.getTime() +
        context.config.cooldownDefaultSecs[reason === "capacity" ? "capacity" : "quotaExhausted"] *
            1_000).toISOString();
    const selectedCooldownUntil = normalizeCooldownRunAt(selectedCooldown?.cooldownUntil ?? null);
    const deferredCooldownUntil = normalizeCooldownRunAt(deferred.cooldownUntil);
    return {
        runAt: selectedCooldownUntil ?? deferredCooldownUntil ?? fallbackCooldownUntil,
        reason,
    };
}
function selectDeferredCooldown(deferred) {
    const cooldownsWithRunAt = deferred.cooldowns
        .map((cooldown) => ({
        cooldown,
        runAtMs: cooldown.cooldownUntil === null
            ? Number.POSITIVE_INFINITY
            : Date.parse(cooldown.cooldownUntil),
    }))
        .filter(({ runAtMs }) => Number.isFinite(runAtMs));
    if (cooldownsWithRunAt.length === 0) {
        return null;
    }
    const targetRunAtMs = deferred.cooldownUntil === null
        ? Number.NaN
        : Date.parse(deferred.cooldownUntil);
    if (Number.isFinite(targetRunAtMs)) {
        const matchingCooldown = cooldownsWithRunAt.find(({ runAtMs }) => runAtMs === targetRunAtMs);
        if (matchingCooldown) {
            return matchingCooldown.cooldown;
        }
    }
    cooldownsWithRunAt.sort((left, right) => left.runAtMs - right.runAtMs);
    return cooldownsWithRunAt[0]?.cooldown ?? null;
}
function normalizeCooldownRunAt(value) {
    if (value === null) {
        return null;
    }
    const runAtMs = Date.parse(value);
    return Number.isFinite(runAtMs) ? new Date(runAtMs).toISOString() : null;
}
function resolveDeferredWorkflowState(workflowState, deferred, cooldownSchedule) {
    const base = (0, execution_ownership_1.stripExecutionOwnership)(workflowState);
    const scheduledRun = (0, scheduled_run_1.createCooldownScheduledRun)({
        runAt: cooldownSchedule.runAt,
        reason: cooldownSchedule.reason,
    }) ??
        (0, scheduled_run_1.createCooldownScheduledRun)({
            runAt: new Date().toISOString(),
            reason: cooldownSchedule.reason,
        });
    if (scheduledRun === null) {
        throw new Error("Failed to create cooldown scheduled run.");
    }
    if (!deferred.attemptedWorkerExecution) {
        return {
            ...base,
            scheduledRun,
        };
    }
    return {
        ...base,
        scheduledRun,
        consecutiveAiRuns: deferred.consecutiveAiRuns,
        totalAiRunHours: deferred.totalAiRunHours,
    };
}
function resolveDeferredCooldownReason(deferred) {
    if (deferred.cooldowns.some((cooldown) => cooldown.lastReason === "worker_unavailable")) {
        return "capacity";
    }
    return "quota_exhausted";
}
function resolveCooldownReasonFromLastReason(lastReason) {
    return lastReason === "worker_unavailable" ? "capacity" : "quota_exhausted";
}
