"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.selectPullRequestReviewNecessityIssues = selectPullRequestReviewNecessityIssues;
exports.processPullRequestReviewNecessityIssue = processPullRequestReviewNecessityIssue;
const node_crypto_1 = require("node:crypto");
const issue_completion_1 = require("../../config/issue-completion");
const catalog_1 = require("../../i18n/catalog");
const constants_1 = require("../../platform/constants");
const types_1 = require("../../platform/ticket-tracker/types");
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 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 freshness_1 = require("./freshness");
const needs_human_merge_1 = require("./needs-human-merge");
const presentation_1 = require("./presentation");
const pull_request_auto_merge_1 = require("./pull-request-auto-merge");
const publisher_factory_1 = require("./publisher-factory");
const results_1 = require("./results");
const shared_1 = require("./shared");
async function selectPullRequestReviewNecessityIssues(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 ||
            workflowState.workflowSurface !== "pull_request" ||
            (0, auto_merge_state_1.hasActiveAutoMergeState)(workflowState)) {
            return [];
        }
        return [
            {
                issue: entry.issue,
                snapshot: entry.snapshot,
                workflowState,
            },
        ];
    });
}
async function processPullRequestReviewNecessityIssue(input) {
    const bootstrap = await (0, shared_1.loadPullRequestMonitorBootstrap)(input);
    const pullRequestPolicy = bootstrap.issueContext.config.issueCompletion.pullRequest;
    if (!(0, issue_completion_1.shouldEvaluatePullRequestReviewNecessity)(bootstrap.issueContext.config.issueCompletion)) {
        return null;
    }
    const effectivePolicy = {
        reviewPolicy: "if_needed",
        mergePolicy: pullRequestPolicy.mergePolicy,
    };
    if (input.executable.issue.kind !== types_1.TRACKER_ITEM_KINDS.pullRequest ||
        bootstrap.pullRequest === null ||
        bootstrap.pullRequest.state !== constants_1.PLATFORM_PULL_REQUEST_STATES.open) {
        return null;
    }
    const pullRequest = bootstrap.pullRequest;
    const evaluation = await buildPullRequestReviewNecessityInput({
        context: bootstrap.issueContext,
        issue: input.executable.issue,
        pullRequest,
        automationState: bootstrap.automationState,
        responseLanguage: bootstrap.responseLanguage,
        mergePolicy: effectivePolicy.mergePolicy,
        workflowState: input.executable.workflowState,
    });
    const evaluationInput = evaluation.decisionInput;
    const existingDecision = input.executable.workflowState.pullRequestReviewNecessity;
    const pullRequestCommentCursor = resolvePullRequestReviewNecessityCommentCursor(evaluation.pullRequestComments, existingDecision?.evaluationInput.commentCursor ?? null);
    const digest = buildPullRequestReviewNecessityDigest({
        decisionInput: evaluationInput,
        pullRequestCommentCursor,
        ...(evaluation.sourceCommentCursor !== undefined
            ? { sourceCommentCursor: evaluation.sourceCommentCursor }
            : {}),
    });
    if (existingDecision !== undefined &&
        existingDecision.evaluationInput.digest === digest &&
        existingDecision.evaluationInput.headRefOid ===
            evaluationInput.pullRequest.headRefOid) {
        if (existingDecision.action === "run_review") {
            return null;
        }
        return applyPullRequestReviewNecessityDecision({
            bootstrap,
            executable: input.executable,
            effectivePolicy,
            pullRequest,
            reviewNecessity: existingDecision,
            workflowState: input.executable.workflowState,
        });
    }
    let workerResult;
    try {
        workerResult =
            await bootstrap.issueContext.worker.runPullRequestReviewNecessity({
                ...evaluationInput,
                workspacePath: bootstrap.issueContext.cwd,
                repositoryRoot: bootstrap.issueContext.cwd,
            });
    }
    catch (error) {
        await (0, effect_log_1.appendEffectFailureLog)(bootstrap.logger, [
            (0, log_text_1.issueLogField)("effect", "pr_review_necessity"),
            (0, log_text_1.issueLogField)("outcome", "run_review_after_gate_failure"),
            (0, results_1.renderFailureDetails)(error, bootstrap.logger.locale),
        ]);
        return null;
    }
    if (workerResult.kind === "deferred") {
        return buildPullRequestReviewNecessityDeferredSummary({
            issue: input.executable.issue,
            responseLanguage: bootstrap.responseLanguage,
            workerResult,
        });
    }
    const reviewNecessity = buildPullRequestReviewNecessityState({
        decision: workerResult.launchResult.decision,
        digest,
        evaluatedAt: new Date().toISOString(),
        input: evaluationInput,
        pullRequestCommentCursor,
        ...(evaluation.sourceCommentCursor !== undefined
            ? { sourceCommentCursor: evaluation.sourceCommentCursor }
            : {}),
    });
    const workflowState = {
        ...input.executable.workflowState,
        pullRequestReviewNecessity: reviewNecessity,
    };
    return applyPullRequestReviewNecessityDecision({
        bootstrap,
        executable: input.executable,
        effectivePolicy,
        pullRequest,
        reviewNecessity,
        workflowState,
    });
}
async function applyPullRequestReviewNecessityDecision(input) {
    const { bootstrap, effectivePolicy, reviewNecessity, workflowState } = input;
    if (reviewNecessity.action === "run_review") {
        await (0, needs_human_merge_1.publishAutoMergeState)({
            context: bootstrap.issueContext,
            issue: input.executable.issue,
            responseLanguage: bootstrap.responseLanguage,
            workflowState,
            appendCanonicalWorkflowStateComment: true,
        });
        await (0, effect_log_1.appendEffectResultLog)(bootstrap.logger, [
            (0, log_text_1.issueLogField)("effect", "pr_review_necessity"),
            (0, log_text_1.issueLogField)("outcome", "run_review"),
            (0, log_text_1.issueLogField)("reason", reviewNecessity.reason),
        ]);
        return null;
    }
    const summary = buildPullRequestReviewSkippedSummary(bootstrap.responseLanguage, reviewNecessity.reason);
    if (effectivePolicy.mergePolicy === "auto") {
        const autoMergeWorkflowState = {
            ...workflowState,
            autoMergeState: auto_merge_state_1.WORKFLOW_AUTO_MERGE_STATES.pending,
        };
        delete autoMergeWorkflowState.needsHumanReason;
        const publication = await (0, needs_human_merge_1.publishAutoMergeState)({
            context: bootstrap.issueContext,
            issue: input.executable.issue,
            responseLanguage: bootstrap.responseLanguage,
            workflowState: autoMergeWorkflowState,
            appendCanonicalWorkflowStateComment: true,
            visibleUpdate: (0, visible_update_builders_1.buildWorkflowVisibleUpdate)({
                event: publisher_events_1.WORKFLOW_VISIBLE_EVENTS.prReviewSkipped,
                summary,
                next: buildPullRequestReviewSkippedAutoMergeNextStep(bootstrap.responseLanguage),
                workerOutcome: constants_2.WORKER_OUTCOMES.proceed,
            }),
        });
        if (publication.targetWorkflowProjection !== null) {
            input.executable.issue.workflowState = {
                ...publication.targetWorkflowProjection,
            };
        }
        await (0, effect_log_1.appendEffectResultLog)(bootstrap.logger, [
            (0, log_text_1.issueLogField)("effect", "pr_review_necessity"),
            (0, log_text_1.issueLogField)("outcome", "skip_review_auto_merge"),
            (0, log_text_1.issueLogField)("reason", reviewNecessity.reason),
        ]);
        return (0, pull_request_auto_merge_1.continuePullRequestAutoMerge)({
            context: bootstrap.issueContext,
            issue: bootstrap.issueForBranch,
            logger: bootstrap.logger,
            branchName: bootstrap.branchName,
            responseLanguage: bootstrap.responseLanguage,
            workflowState: autoMergeWorkflowState,
            publishAutoMergePendingUpdateWhenWaiting: true,
            pullRequest: input.pullRequest,
            automationState: bootstrap.automationState,
        });
    }
    const next = buildPullRequestReviewSkippedNextStep(bootstrap.responseLanguage);
    const publication = await (0, needs_human_merge_1.publishAutoMergeNeedsHumanUpdate)({
        context: bootstrap.issueContext,
        issue: input.executable.issue,
        responseLanguage: bootstrap.responseLanguage,
        workflowState,
        summary,
        next,
        needsHumanReason: publisher_events_1.WORKFLOW_NEEDS_HUMAN_REASONS.reviewReady,
        visibleUpdateEvent: publisher_events_1.WORKFLOW_VISIBLE_EVENTS.prReviewSkipped,
    });
    if (publication.targetWorkflowProjection !== null) {
        input.executable.issue.workflowState = {
            ...publication.targetWorkflowProjection,
        };
    }
    await (0, effect_log_1.appendEffectResultLog)(bootstrap.logger, [
        (0, log_text_1.issueLogField)("effect", "pr_review_necessity"),
        (0, log_text_1.issueLogField)("outcome", "skip_review_manual"),
        (0, log_text_1.issueLogField)("reason", reviewNecessity.reason),
    ]);
    return (0, results_1.buildRunCliSummary)({
        issue: input.executable.issue,
        outcome: constants_3.RUN_ISSUE_OUTCOMES.queuedHuman,
        responseLanguage: bootstrap.responseLanguage,
        summary,
        commentUrl: (0, publisher_factory_1.resolveRunWorkflowPublicationCommentUrl)(publication),
        prUrl: input.pullRequest.url,
        nextStep: next,
    });
}
async function buildPullRequestReviewNecessityInput(input) {
    const [changedFiles, commits, sourceContext, pullRequestComments] = await Promise.all([
        input.context.repositoryHost.listPullRequestChangedFiles(input.pullRequest.number),
        input.context.repositoryHost.listPullRequestCommits(input.pullRequest.number),
        loadPullRequestReviewNecessitySourceIssueContext(input.context, input.issue),
        (0, shared_1.loadSortedIssueComments)(input.context.ticketTracker, input.issue.id),
    ]);
    return {
        decisionInput: {
            issue: toReviewNecessityIssueSnapshot(input.issue),
            latestComments: pullRequestComments.map(toReviewNecessityCommentSnapshot),
            sourceIssue: sourceContext === null
                ? null
                : toReviewNecessityIssueSnapshot(sourceContext.issue),
            sourceComments: sourceContext?.comments.map(toReviewNecessityCommentSnapshot) ?? [],
            pullRequest: {
                number: input.pullRequest.number,
                url: input.pullRequest.url,
                title: input.issue.title,
                isDraft: input.pullRequest.isDraft,
                headBranch: input.pullRequest.headBranch,
                baseBranch: input.pullRequest.baseBranch,
                headRefOid: input.pullRequest.headRefOid ?? null,
            },
            automationState: toReviewNecessityAutomationStateSnapshot(input.automationState),
            changedFiles: changedFiles.map(toReviewNecessityChangedFileSnapshot),
            commits: commits.map(toReviewNecessityCommitSnapshot),
            effectivePolicy: {
                reviewPolicy: "if_needed",
                mergePolicy: input.mergePolicy,
            },
            workflowStateSummary: buildPullRequestReviewNecessityWorkflowStateSummary(input.workflowState),
            outputLanguage: input.responseLanguage,
        },
        pullRequestComments,
        ...(sourceContext === null
            ? {}
            : {
                sourceCommentCursor: resolveLatestCommentCursor(sourceContext.comments),
            }),
    };
}
async function loadPullRequestReviewNecessitySourceIssueContext(context, issue) {
    const sourceIssueId = await (0, shared_1.resolveLinkedIssueIdFromPullRequestTimeline)(context.ticketTracker, issue);
    if (sourceIssueId === null || sourceIssueId === issue.id) {
        return null;
    }
    const sourceIssue = await context.ticketTracker.getIssue(sourceIssueId);
    return {
        issue: sourceIssue,
        comments: await (0, shared_1.loadSortedIssueComments)(context.ticketTracker, sourceIssueId, {
            timeline: "issue-only",
        }),
    };
}
function toReviewNecessityIssueSnapshot(issue) {
    return {
        id: issue.id,
        displayId: issue.displayId,
        title: issue.title,
        body: issue.body,
        url: issue.url,
    };
}
function toReviewNecessityCommentSnapshot(comment) {
    return {
        author: comment.author,
        createdAt: comment.createdAt,
        kind: comment.kind,
        body: comment.body,
        ...(comment.path === undefined ? {} : { path: comment.path }),
        ...(comment.line === undefined ? {} : { line: comment.line }),
        ...(comment.reviewState === undefined
            ? {}
            : { reviewState: comment.reviewState }),
    };
}
function toReviewNecessityChangedFileSnapshot(file) {
    return {
        path: file.path,
        status: file.status ?? "unknown",
        additions: file.additions ?? 0,
        deletions: file.deletions ?? 0,
        changes: file.changes ?? 0,
    };
}
function toReviewNecessityCommitSnapshot(commit) {
    return {
        oid: commit.oid,
        title: commit.title,
        authoredDate: commit.authoredAt ?? null,
    };
}
function toReviewNecessityAutomationStateSnapshot(automationState) {
    if (automationState === null) {
        return null;
    }
    return {
        reviewDecision: automationState.reviewDecision,
        mergeable: resolveReviewNecessityMergeable(automationState.mergeability),
        mergeStateStatus: automationState.mergeStateStatus ?? null,
        headRefOid: automationState.headRefOid,
    };
}
function resolveReviewNecessityMergeable(mergeability) {
    switch (mergeability) {
        case "MERGEABLE":
            return true;
        case "CONFLICTING":
            return false;
        case "UNKNOWN":
        case null:
            return null;
    }
}
function buildPullRequestReviewNecessityWorkflowStateSummary(workflowState) {
    const steps = workflowState.plan?.steps ?? [];
    const completedStepCount = steps.filter((step) => step.result !== null).length;
    const activeStep = steps.find((step) => step.result === null);
    const parts = [
        `surface=${workflowState.workflowSurface}`,
        `steps=${completedStepCount}/${steps.length}`,
        `activeStep=${activeStep?.title ?? "none"}`,
        `needsHumanReason=${workflowState.needsHumanReason ?? "none"}`,
        `autoMergeState=${workflowState.autoMergeState ?? "none"}`,
        `followupProposalPolicy=${workflowState.followup?.proposalPolicy ?? "off"}`,
    ];
    return parts.join("; ");
}
function buildPullRequestReviewNecessityDigest(input) {
    const { latestComments: _latestComments, sourceComments: _sourceComments, ...decisionInput } = input.decisionInput;
    return (0, node_crypto_1.createHash)("sha256")
        .update(JSON.stringify({
        decisionInput,
        pullRequestCommentCursor: input.pullRequestCommentCursor,
        sourceCommentCursor: input.sourceCommentCursor,
    }))
        .digest("hex");
}
function buildPullRequestReviewNecessityState(input) {
    return {
        action: input.decision.action,
        reason: input.decision.reason,
        evidence: [...input.decision.evidence],
        reviewTriggers: [...input.decision.reviewTriggers],
        evaluatedAt: input.evaluatedAt,
        effectivePolicy: {
            reviewPolicy: "if_needed",
            mergePolicy: input.input.effectivePolicy.mergePolicy,
        },
        evaluationInput: {
            digest: input.digest,
            pullRequestNumber: input.input.pullRequest.number,
            headRefOid: input.input.pullRequest.headRefOid,
            commentCursor: input.pullRequestCommentCursor,
            ...(input.input.sourceIssue === null
                ? {}
                : {
                    sourceIssueId: input.input.sourceIssue.id,
                    ...(input.sourceCommentCursor !== undefined
                        ? { sourceCommentCursor: input.sourceCommentCursor }
                        : {}),
                }),
        },
    };
}
function resolvePullRequestReviewNecessityCommentCursor(comments, previousCursor) {
    if (previousCursor !== null &&
        !(0, workflow_timeline_scan_1.hasActionableWorkflowCommentAfterCursor)(comments, previousCursor)) {
        return previousCursor;
    }
    return resolveLatestCommentCursor(comments);
}
function resolveLatestCommentCursor(comments) {
    const latestComment = comments[comments.length - 1];
    return latestComment === undefined
        ? null
        : (0, workflow_timeline_scan_1.toWorkflowCommentCursor)(latestComment);
}
function buildPullRequestReviewNecessityDeferredSummary(input) {
    const triedProfiles = input.workerResult.triedProfiles.map((profile) => (0, freshness_1.renderWorkerAttemptLabel)(profile.workerName));
    const presentation = (0, presentation_1.buildDeferredPresentation)(input.responseLanguage, input.workerResult.cooldownUntil, triedProfiles);
    return (0, results_1.buildRunCliSummary)({
        issue: input.issue,
        outcome: constants_3.RUN_ISSUE_OUTCOMES.deferred,
        responseLanguage: input.responseLanguage,
        summary: presentation.summary,
        commentUrl: null,
        prUrl: input.issue.url,
        nextStep: presentation.next,
        deferredReason: input.workerResult.reason,
        triedProfiles,
        cooldownUntil: input.workerResult.cooldownUntil,
    });
}
function buildPullRequestReviewSkippedSummary(responseLanguage, reason) {
    return (0, catalog_1.translate)(responseLanguage, "run.prReviewNecessity.skipped.summary", {
        reason,
    });
}
function buildPullRequestReviewSkippedNextStep(responseLanguage) {
    return (0, catalog_1.translate)(responseLanguage, "run.prReviewNecessity.skipped.next");
}
function buildPullRequestReviewSkippedAutoMergeNextStep(responseLanguage) {
    return (0, catalog_1.translate)(responseLanguage, "run.prReviewNecessity.skipped.autoNext");
}
