"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PENDING_WAIT_HUMAN_PUBLICATION_BLOCKED_REASON = void 0;
exports.hasPendingWaitHumanPublication = hasPendingWaitHumanPublication;
exports.isPendingWaitHumanPublicationRecoveryDue = isPendingWaitHumanPublicationRecoveryDue;
exports.isPendingWaitHumanPublicationRecoverableStatus = isPendingWaitHumanPublicationRecoverableStatus;
exports.resolvePendingWaitHumanPublicationRecoverySafety = resolvePendingWaitHumanPublicationRecoverySafety;
exports.blockPendingWaitHumanPublicationRecovery = blockPendingWaitHumanPublicationRecovery;
exports.schedulePendingWaitHumanPublicationRecovery = schedulePendingWaitHumanPublicationRecovery;
exports.recoverPendingWaitHumanPublicationsForRun = recoverPendingWaitHumanPublicationsForRun;
exports.recoverPendingWaitHumanPublication = recoverPendingWaitHumanPublication;
const network_recovery_state_1 = require("../../core/network-recovery-state");
const tracked_project_1 = require("../../core/tracked-project");
const transient_network_error_1 = require("../../core/transient-network-error");
const catalog_1 = require("../../i18n/catalog");
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 constants_1 = require("../constants");
const issue_logger_1 = require("../issue-logger");
const log_text_1 = require("../log-text");
const presentation_1 = require("./presentation");
const results_1 = require("./results");
const shared_1 = require("./shared");
const publisher_factory_1 = require("./publisher-factory");
exports.PENDING_WAIT_HUMAN_PUBLICATION_BLOCKED_REASON = "pending_wait_human_publication_blocked";
function hasPendingWaitHumanPublication(entry) {
    return entry.pendingPublication?.kind === shared_1.PENDING_WAIT_HUMAN_PUBLICATION_KIND;
}
function isPendingWaitHumanPublicationRecoveryDue(entry) {
    return (hasPendingWaitHumanPublication(entry) &&
        entry.pendingPublication.status === "scheduled" &&
        Date.parse(entry.nextRetryAt) <= Date.now());
}
function isPendingWaitHumanPublicationRecoverableStatus(status) {
    return (status === types_1.WORKFLOW_STATUSES.aiRunning ||
        status === types_1.WORKFLOW_STATUSES.aiNeedsHuman);
}
function resolvePendingWaitHumanPublicationRecoverySafety(input) {
    if (!input.issue.isOpen) {
        return { safe: false, reason: "issue_closed" };
    }
    if (!isPendingWaitHumanPublicationRecoverableStatus(input.issue.workflowState.status) ||
        !matchesPendingPublicationWorkflowSnapshot(input.entry, input.issue)) {
        return { safe: false, reason: "state_changed" };
    }
    if (input.serviceRunnableBasePreconditions === false) {
        return { safe: false, reason: "precondition_failed" };
    }
    return { safe: true };
}
async function blockPendingWaitHumanPublicationRecovery(input) {
    const blockedAt = input.entry.pendingPublication.status === "blocked" &&
        input.entry.pendingPublication.blockedAt
        ? input.entry.pendingPublication.blockedAt
        : new Date().toISOString();
    const blockedEntry = {
        ...input.entry,
        updatedAt: new Date().toISOString(),
        pendingPublication: {
            ...input.entry.pendingPublication,
            status: "blocked",
            blockedReason: input.reason,
            blockedAt,
        },
    };
    await (0, network_recovery_state_1.upsertNetworkRecoveryEntry)(blockedEntry, input.homeDirectory);
    return blockedEntry;
}
async function schedulePendingWaitHumanPublicationRecovery(input) {
    const trackedProjectKey = (0, provider_resolution_1.getTrackedProjectKey)((0, tracked_project_1.toTrackedProjectRef)(input.context.config));
    const existingEntry = (await (0, network_recovery_state_1.listRepoNetworkRecoveryEntries)(trackedProjectKey, input.context.logHomeDirectory)).find((entry) => entry.issueId === input.issue.id &&
        entry.reason === shared_1.TRANSIENT_NETWORK_RECOVERY_REASON);
    const attemptCount = (existingEntry?.attemptCount ?? 0) + 1;
    const retryAt = new Date(Date.now() + calculatePendingPublicationRecoveryDelayMs(attemptCount)).toISOString();
    const lastError = (0, results_1.buildFailureSummary)(input.error);
    const createdAt = existingEntry && hasPendingWaitHumanPublication(existingEntry)
        ? existingEntry.pendingPublication.createdAt
        : new Date().toISOString();
    await (0, network_recovery_state_1.upsertNetworkRecoveryEntry)({
        trackedProjectKey,
        issueId: input.issue.id,
        reason: shared_1.TRANSIENT_NETWORK_RECOVERY_REASON,
        attemptCount,
        nextRetryAt: retryAt,
        lastError,
        updatedAt: new Date().toISOString(),
        pendingPublication: {
            kind: shared_1.PENDING_WAIT_HUMAN_PUBLICATION_KIND,
            status: "scheduled",
            transitionState: input.transitionState,
            publishRequest: input.publishRequest,
            effectResult: input.effectResult,
            createdAt,
        },
    }, input.context.logHomeDirectory);
    await input.logger.appendSection((0, log_text_1.issueLogSection)("networkRecoveryScheduled"), [
        (0, log_text_1.issueLogField)("reason", shared_1.TRANSIENT_NETWORK_RECOVERY_REASON),
        (0, log_text_1.issueLogField)("note", `pendingPublication=${shared_1.PENDING_WAIT_HUMAN_PUBLICATION_KIND}`),
        (0, log_text_1.issueLogField)("attemptCount", attemptCount),
        (0, log_text_1.issueLogField)("retryAt", retryAt),
    ].join("\n"));
    return {
        attemptCount,
        retryAt,
        lastError,
        summary: buildPendingPublicationRecoverySummary({
            issue: input.issue,
            effectResult: input.effectResult,
            responseLanguage: input.transitionState.responseLanguage,
            retryAt,
        }),
    };
}
async function recoverPendingWaitHumanPublicationsForRun(context) {
    const trackedProjectKey = (0, provider_resolution_1.getTrackedProjectKey)((0, tracked_project_1.toTrackedProjectRef)(context.config));
    const pendingEntries = (await (0, network_recovery_state_1.listRepoNetworkRecoveryEntries)(trackedProjectKey, context.logHomeDirectory)).filter(hasPendingWaitHumanPublication);
    const selectedIssueIds = context.issueIds === undefined ? null : new Set(context.issueIds);
    const selectedEntries = pendingEntries.filter((entry) => selectedIssueIds === null
        ? isPendingWaitHumanPublicationRecoveryDue(entry)
        : selectedIssueIds.has(entry.issueId));
    const summaries = [];
    for (const entry of selectedEntries) {
        if (entry.pendingPublication.status === "blocked") {
            summaries.push(await buildPendingPublicationBlockedSummary(context, entry));
            continue;
        }
        if (!isPendingWaitHumanPublicationRecoveryDue(entry)) {
            summaries.push(await buildPendingPublicationNotDueSummary(context, entry));
            continue;
        }
        summaries.push(await recoverPendingWaitHumanPublication({ context, entry }));
    }
    return summaries;
}
async function executePendingWaitHumanWriteCommentPublication(input) {
    await input.logger.appendSection((0, log_text_1.issueLogSection)("plannedEffects"), (0, issue_logger_1.formatList)(["write_comment"]));
    const publisher = (0, publisher_factory_1.createRunWorkflowPublisher)(input.context);
    const publication = await publisher.publishWorkflow(refreshPendingPublicationRequestTarget({
        request: input.publishRequest,
        issue: input.issue,
    }));
    const effectResult = {
        ...input.initialResult,
        commentUrl: publication.publishStatus === "applied"
            ? (0, publisher_factory_1.resolveRunWorkflowPublicationCommentUrl)(publication)
            : input.initialResult.commentUrl,
        pullRequestUrl: input.initialResult.pullRequestUrl,
        pullRequestThreadId: input.initialResult.pullRequestThreadId,
    };
    await input.logger.appendSection((0, log_text_1.issueLogSection)("effectResult"), [
        (0, log_text_1.issueLogField)("effect", "write_comment"),
        (0, log_text_1.issueLogField)("note", `publishStatus=${publication.publishStatus}`),
        (0, log_text_1.issueLogField)("commentUrl", publication.publishStatus === "guard_rejected"
            ? "(guard_rejected)"
            : (effectResult.commentUrl ??
                (publication.visibleUpdateComment === null ? "(skipped)" : "-"))),
    ].join("\n"));
    return { effectResult, publishStatus: publication.publishStatus };
}
async function recoverPendingWaitHumanPublication(input) {
    const issue = input.issue ??
        (await input.context.ticketTracker.getIssue(input.entry.issueId));
    const { logger } = (0, shared_1.buildIssueLogContext)(input.context.config, issue, input.context.logHomeDirectory);
    const transitionState = {
        ...input.entry.pendingPublication.transitionState,
        issue,
    };
    await logger.appendRunStart({
        runKind: "pending-publication-recovery",
        issue,
        currentLabels: (0, workflow_labels_1.projectWorkflowLabelNames)(input.context.config.ticketTracker.workflowLabels, issue.workflowState),
        pullRequestUrl: transitionState.pullRequestUrl,
    });
    if (input.entry.pendingPublication.status === "blocked") {
        const summary = buildPendingPublicationBlockedSummaryFromIssue({
            entry: input.entry,
            issue,
            reason: input.entry.pendingPublication.blockedReason ?? "precondition_failed",
        });
        await logger.appendSection((0, log_text_1.issueLogSection)("finalSummary"), (0, presentation_1.renderCliSummary)(summary, logger.locale));
        return summary;
    }
    const safety = resolvePendingWaitHumanPublicationRecoverySafety({
        entry: input.entry,
        issue,
    });
    if (!safety.safe) {
        return blockStalePendingPublication({
            context: input.context,
            entry: input.entry,
            issue,
            logger,
            reason: safety.reason,
        });
    }
    await logger.appendSection((0, log_text_1.issueLogSection)("resolvedTransition"), (0, presentation_1.renderOutcomeTransitionSection)({
        workflowLabels: input.context.config.ticketTracker.workflowLabels,
        from: transitionState.previousSnapshot.status,
        to: transitionState.nextSnapshot.status,
        requestedOutcome: transitionState.requestedOutcome,
        finalOutcome: transitionState.finalOutcome,
        facts: transitionState.facts,
        locale: logger.locale,
    }));
    try {
        const publication = await executePendingWaitHumanWriteCommentPublication({
            context: input.context,
            issue,
            logger,
            publishRequest: input.entry.pendingPublication.publishRequest,
            initialResult: input.entry.pendingPublication.effectResult,
        });
        if (publication.publishStatus === "guard_rejected") {
            return blockStalePendingPublication({
                context: input.context,
                entry: input.entry,
                issue,
                logger,
                reason: "state_changed",
            });
        }
        const effectResult = publication.effectResult;
        const summary = (0, results_1.buildCliSummary)(transitionState, effectResult);
        await (0, network_recovery_state_1.removeNetworkRecoveryEntry)((0, provider_resolution_1.getTrackedProjectKey)((0, tracked_project_1.toTrackedProjectRef)(input.context.config)), input.entry.issueId, input.entry.reason, input.context.logHomeDirectory);
        await logger.appendSection((0, log_text_1.issueLogSection)("finalSummary"), (0, presentation_1.renderCliSummary)(summary, logger.locale));
        return summary;
    }
    catch (error) {
        if (!(0, transient_network_error_1.isTransientNetworkError)(error)) {
            throw error;
        }
        const recoveryDecision = await schedulePendingWaitHumanPublicationRecovery({
            context: input.context,
            logger,
            issue,
            transitionState,
            publishRequest: input.entry.pendingPublication.publishRequest,
            effectResult: input.entry.pendingPublication.effectResult,
            error,
        });
        await logger.appendSection((0, log_text_1.issueLogSection)("finalSummary"), (0, presentation_1.renderCliSummary)(recoveryDecision.summary, logger.locale));
        return recoveryDecision.summary;
    }
}
async function buildPendingPublicationNotDueSummary(context, entry) {
    const issue = await context.ticketTracker.getIssue(entry.issueId);
    return buildPendingPublicationRecoverySummary({
        issue,
        effectResult: entry.pendingPublication.effectResult,
        responseLanguage: entry.pendingPublication.transitionState.responseLanguage,
        retryAt: entry.nextRetryAt,
    });
}
async function buildPendingPublicationBlockedSummary(context, entry) {
    const issue = await context.ticketTracker.getIssue(entry.issueId);
    return buildPendingPublicationBlockedSummaryFromIssue({
        entry,
        issue,
        reason: entry.pendingPublication.blockedReason ?? "precondition_failed",
    });
}
function buildPendingPublicationBlockedSummaryFromIssue(input) {
    return (0, results_1.buildRunCliSummary)({
        issue: input.issue,
        outcome: constants_1.RUN_ISSUE_OUTCOMES.interrupted,
        responseLanguage: input.entry.pendingPublication.transitionState.responseLanguage,
        headline: `Issue ${input.issue.displayId}: pending wait_human publish blocked`,
        commentUrl: input.entry.pendingPublication.effectResult.commentUrl,
        prUrl: input.entry.pendingPublication.effectResult.pullRequestUrl,
        pullRequestThreadId: input.entry.pendingPublication.effectResult.pullRequestThreadId,
        nextStep: formatPendingPublicationBlockedNextStep(input.reason),
        deferredReason: exports.PENDING_WAIT_HUMAN_PUBLICATION_BLOCKED_REASON,
        interruptReason: "state-changed",
    });
}
function formatPendingPublicationBlockedNextStep(reason) {
    if (reason === "issue_closed") {
        return "The saved wait_human publish context is preserved because the issue is closed.";
    }
    if (reason === "precondition_failed") {
        return "The saved wait_human publish context is preserved because service preconditions no longer match.";
    }
    return "The saved wait_human publish context is preserved because the issue changed before retry.";
}
async function blockStalePendingPublication(input) {
    const blockedEntry = await blockPendingWaitHumanPublicationRecovery({
        trackedProjectKey: (0, provider_resolution_1.getTrackedProjectKey)((0, tracked_project_1.toTrackedProjectRef)(input.context.config)),
        entry: input.entry,
        reason: input.reason,
        homeDirectory: input.context.logHomeDirectory,
    });
    const summary = buildPendingPublicationBlockedSummaryFromIssue({
        entry: blockedEntry,
        issue: input.issue,
        reason: input.reason,
    });
    await input.logger.appendSection((0, log_text_1.issueLogSection)("publishAborted"), [
        (0, log_text_1.issueLogField)("reason", input.reason),
        (0, log_text_1.issueLogField)("currentWorkflow", input.issue.workflowState.status),
    ].join("\n"));
    await input.logger.appendSection((0, log_text_1.issueLogSection)("finalSummary"), (0, presentation_1.renderCliSummary)(summary, input.logger.locale));
    return summary;
}
function buildPendingPublicationRecoverySummary(input) {
    return (0, results_1.buildRunCliSummary)({
        issue: input.issue,
        outcome: constants_1.RUN_ISSUE_OUTCOMES.failed,
        responseLanguage: input.responseLanguage,
        summary: (0, catalog_1.translate)(input.responseLanguage, "run.summary.transientNetwork"),
        commentUrl: input.effectResult.commentUrl,
        prUrl: input.effectResult.pullRequestUrl,
        pullRequestThreadId: input.effectResult.pullRequestThreadId,
        nextStep: (0, catalog_1.translate)(input.responseLanguage, "run.summary.transientNetwork.next"),
        deferredReason: shared_1.TRANSIENT_NETWORK_RECOVERY_REASON,
        cooldownUntil: input.retryAt,
    });
}
function refreshPendingPublicationRequestTarget(input) {
    if (input.request.target.kind === "existing_issue" &&
        input.request.target.issueId === input.issue.id) {
        return {
            ...input.request,
            target: {
                ...input.request.target,
                preloadedIssue: input.issue,
            },
        };
    }
    if (input.request.target.kind === "pull_request_thread" &&
        input.request.target.pullRequestThreadId === input.issue.id) {
        return {
            ...input.request,
            target: {
                ...input.request.target,
                preloadedIssue: input.issue,
            },
        };
    }
    return input.request;
}
function matchesPendingPublicationWorkflowSnapshot(entry, issue) {
    return (workflowSnapshotsEqual(issue.workflowState, entry.pendingPublication.transitionState.previousSnapshot) ||
        workflowSnapshotsEqual(issue.workflowState, entry.pendingPublication.transitionState.nextSnapshot));
}
function workflowSnapshotsEqual(left, right) {
    return (left.status === right.status &&
        sortedValues(left.rules).join("\n") === sortedValues(right.rules).join("\n"));
}
function sortedValues(values) {
    return [...values].sort((left, right) => left.localeCompare(right));
}
function calculatePendingPublicationRecoveryDelayMs(attemptCount) {
    return Math.min(shared_1.TRANSIENT_NETWORK_RECOVERY_BASE_MS * 2 ** Math.max(attemptCount - 1, 0), shared_1.TRANSIENT_NETWORK_RECOVERY_MAX_MS);
}
