"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.runQueueCommand = runQueueCommand;
const workflow_update_1 = require("../comments/workflow-update");
const control_plane_config_1 = require("../config/control-plane-config");
const service_state_1 = require("../core/service-state");
const catalog_1 = require("../i18n/catalog");
const provider_resolution_1 = require("../platform/provider-resolution");
const types_1 = require("../platform/ticket-tracker/types");
const types_2 = require("../platform/workflow-state/types");
const constants_1 = require("../run/constants");
const execution_ownership_1 = require("../run/execution-ownership");
const run_once_cycle_1 = require("../run/run-once-cycle");
const shared_1 = require("../run/run-once-cycle/shared");
const terminal_source_finalization_1 = require("../run/run-once-cycle/terminal-source-finalization");
const workflow_template_root_1 = require("../run/run-once-cycle/workflow-template-root");
const thread_effective_config_1 = require("../run/thread-effective-config");
const runtime_1 = require("../worker/runtime");
const plan_progress_1 = require("../workflows/plan-progress");
const recurring_1 = require("../workflows/recurring");
const recurring_auto_stop_1 = require("../workflows/recurring-auto-stop");
const scheduled_run_1 = require("../workflows/scheduled-run");
const auto_merge_state_1 = require("../workflows/auto-merge-state");
const approval_instructions_1 = require("../workflows/approval-instructions");
const workflow_state_read_support_1 = require("../workflows/workflow-state-read-support");
const locale_1 = require("./locale");
const prompts_1 = require("./prompts");
const run_cycle_context_1 = require("./run-cycle-context");
const config_publish_execution_1 = require("./service-runtime/config-publish-execution");
const repo_helpers_1 = require("./service-runtime/repo-helpers");
const config_scan_1 = require("./service-runtime/config-scan");
const config_scan_helpers_1 = require("./service-runtime/config-scan-helpers");
// biome-ignore lint/complexity/noExcessiveLinesPerFunction: manual run preflight stays centralized to keep gating order explicit.
async function runQueueCommand(cwd, options, deps = {}) {
    const locale = await (0, locale_1.loadCommandLocale)(cwd);
    const liveService = await (0, service_state_1.getLiveServiceState)();
    if (liveService) {
        console.error((0, catalog_1.translate)(locale, "run.command.serviceRunning", {
            pid: liveService.pid,
        }));
        return 1;
    }
    const { config } = (0, provider_resolution_1.resolveSupportedProviders)(await (0, control_plane_config_1.loadConfig)(cwd));
    const workflowLabels = config.ticketTracker.workflowLabels;
    const runContext = await (0, run_cycle_context_1.createRunCycleContextBundle)(cwd, config, {
        skipWorkerRuntime: true,
        runSource: "manual",
        workerSlotBudget: config.parallelism.maxWorkers,
        ...(options.issueIds ? { issueIds: options.issueIds } : {}),
    });
    const { ticketTracker } = runContext;
    await (0, workflow_template_root_1.refreshWorkflowTemplateRepoRoot)(runContext);
    const currentUserLogin = runContext.currentUserLogin;
    if (!currentUserLogin) {
        throw new Error("Authenticated user login is required for manual runs.");
    }
    const prompter = deps.prompter ?? (0, prompts_1.createCliPrompter)({ locale });
    const requestedIssues = options.issueIds
        ? await loadRequestedOpenIssues(ticketTracker, options.issueIds)
        : [];
    let manualIssuePool = options.issueIds
        ? requestedIssues
        : await loadOpenManualIssues(ticketTracker);
    let manualCandidateSelection = await selectManualCandidateIssues({
        issues: manualIssuePool,
        explicitIssueIds: options.issueIds !== undefined,
        config,
        ticketTracker,
        repositoryHost: runContext.repositoryHost,
        maxIssues: config.parallelism.maxIssues,
    });
    const lockedIssues = (await Promise.all(requestedIssues.map(async (issue) => ({
        issue,
        workflowState: await (0, workflow_state_read_support_1.resolvePersistedIssueWorkflowState)(ticketTracker, issue.id),
    }))))
        .filter(({ issue, workflowState }) => issue.workflowState.status === "ai-running" &&
        (0, execution_ownership_1.isExecutionOwnershipActive)(workflowState?.executionOwnership))
        .map(({ issue }) => issue)
        .sort((left, right) => left.displayId.localeCompare(right.displayId));
    if (lockedIssues.length > 0) {
        for (const issue of lockedIssues) {
            console.error((0, catalog_1.translate)(locale, "run.command.issueLocked", {
                displayId: issue.displayId,
                label: workflowLabels.aiRunning,
                issueId: issue.id,
            }));
        }
        return 1;
    }
    ({ manualIssuePool, manualCandidateSelection } =
        await scanManualIssuePoolAndReloadCandidates({
            runContext,
            config,
            ticketTracker,
            manualIssuePool,
            manualCandidateSelection,
            ...(options.issueIds !== undefined ? { issueIds: options.issueIds } : {}),
        }));
    const manualCandidateIssues = manualCandidateSelection.issues;
    const explicitRequestedIssues = options.issueIds
        ? manualIssuePool
        : requestedIssues;
    const needsHumanIssues = explicitRequestedIssues
        .filter((issue) => issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiNeedsHuman &&
        issue.kind === types_1.TRACKER_ITEM_KINDS.issue)
        .sort((left, right) => left.displayId.localeCompare(right.displayId));
    const needsHumanPullRequests = explicitRequestedIssues
        .filter((issue) => issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiNeedsHuman &&
        issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest)
        .sort((left, right) => left.displayId.localeCompare(right.displayId));
    if (needsHumanIssues.length > 0) {
        for (const issue of needsHumanIssues) {
            console.error(await buildNeedsHumanRunMessage({
                locale,
                ticketTracker,
                issue,
                workflowLabels,
            }));
        }
        return 1;
    }
    const automatedNeedsHumanPullRequests = await filterAutomatedNeedsHumanPullRequests({
        issues: needsHumanPullRequests,
        config,
        ticketTracker,
    });
    const automatedNeedsHumanPullRequestIds = new Set(automatedNeedsHumanPullRequests.map((issue) => issue.id));
    const blockedNeedsHumanPullRequests = needsHumanPullRequests.filter((issue) => !automatedNeedsHumanPullRequestIds.has(issue.id));
    if (blockedNeedsHumanPullRequests.length > 0) {
        for (const issue of blockedNeedsHumanPullRequests) {
            console.error(await buildNeedsHumanRunMessage({
                locale,
                ticketTracker,
                issue,
                workflowLabels,
            }));
        }
        return 1;
    }
    const assigneeOverrideIssues = await selectManualAssigneeOverrideIssues({
        issues: manualCandidateIssues,
        currentUserLogin,
        config,
        ticketTracker,
    });
    const confirmedIssues = await confirmManualAssigneeOverrides(assigneeOverrideIssues, currentUserLogin, ticketTracker, prompter);
    if (!confirmedIssues.ok) {
        return 1;
    }
    if (manualCandidateSelection.requiresWorkerRuntime) {
        runContext.worker = await (0, runtime_1.createConfiguredWorkerRuntime)(config);
    }
    runContext.prefetchedIssues = manualCandidateIssues;
    runContext.workflowGraphIssues = options.issueIds
        ? await ticketTracker.listIssues({ state: "open" })
        : manualCandidateIssues;
    const result = await (0, run_once_cycle_1.runOnceCycle)(runContext);
    const exitCode = result.status === constants_1.RUN_CYCLE_STATUSES.failed ? 1 : 0;
    if (result.summaries.length === 0) {
        console.log((0, catalog_1.translate)(locale, "run.command.noExecutableIssues"));
        return exitCode;
    }
    for (const summary of result.summaries) {
        console.log(summary.headline);
        console.log(renderOpenLine(summary, locale));
        console.log((0, catalog_1.translate)(locale, "run.command.next", { text: summary.next_step }));
    }
    return exitCode;
}
async function buildNeedsHumanRunMessage(input) {
    const comments = await (0, shared_1.loadSortedIssueComments)(input.ticketTracker, input.issue.id, {
        timeline: "issue-only",
    });
    const needsHumanReason = (0, workflow_update_1.findLatestNeedsHumanReason)(comments);
    if (needsHumanReason === "start_approval") {
        return (0, catalog_1.translate)(input.locale, "run.command.issueNeedsHumanStartApproval", {
            displayId: input.issue.displayId,
            label: input.workflowLabels.aiNeedsHuman,
            issueId: input.issue.id,
            issueStartCommand: (0, approval_instructions_1.buildIssueStartCliCommand)(input.issue.id),
        });
    }
    if (needsHumanReason === "assignee_resolution") {
        return (0, catalog_1.translate)(input.locale, "run.command.issueNeedsHumanAssigneeResolution", {
            displayId: input.issue.displayId,
            label: input.workflowLabels.aiNeedsHuman,
            issueId: input.issue.id,
        });
    }
    if (needsHumanReason === "timeout") {
        return (0, catalog_1.translate)(input.locale, "run.command.issueNeedsHumanTimeout", {
            displayId: input.issue.displayId,
            label: input.workflowLabels.aiNeedsHuman,
            readyLabel: input.workflowLabels.aiReady,
            issueId: input.issue.id,
        });
    }
    return (0, catalog_1.translate)(input.locale, "run.command.issueNeedsHuman", {
        displayId: input.issue.displayId,
        label: input.workflowLabels.aiNeedsHuman,
        readyLabel: input.workflowLabels.aiReady,
        issueId: input.issue.id,
    });
}
function renderOpenLine(summary, locale) {
    const segments = [
        (0, catalog_1.translate)(locale, "run.command.open.segment.issue", {
            url: summary.issue_url,
        }),
    ];
    if (summary.comment_url) {
        segments.push((0, catalog_1.translate)(locale, "run.command.open.segment.comment", {
            url: summary.comment_url,
        }));
    }
    if (summary.pr_url) {
        segments.push((0, catalog_1.translate)(locale, "run.command.open.segment.pr", {
            url: summary.pr_url,
        }));
    }
    return (0, catalog_1.translate)(locale, "run.command.open", {
        segments: segments.join(" | "),
    });
}
async function loadRequestedOpenIssues(ticketTracker, issueIds) {
    const uniqueIssueIds = [...new Set(issueIds)];
    const issues = await Promise.all(uniqueIssueIds.map((issueId) => ticketTracker.getIssue(issueId)));
    return issues.filter((issue) => issue.isOpen);
}
async function scanManualIssuePoolAndReloadCandidates(input) {
    if (!(0, config_scan_1.supportsServiceConfigScan)(input.ticketTracker)) {
        return {
            manualIssuePool: input.manualIssuePool,
            manualCandidateSelection: input.manualCandidateSelection,
        };
    }
    const issuesToScan = input.manualIssuePool.filter(isManualConfigScannableIssue);
    for (const issue of issuesToScan) {
        const plan = await (0, config_scan_1.scanIssueWorkflowConfiguration)({
            repoRoot: (0, workflow_template_root_1.getWorkflowTemplateRepoRoot)(input.runContext),
            config: input.config,
            ticketTracker: input.ticketTracker,
            issue,
        });
        await (0, config_publish_execution_1.publishServiceRuntimeConfigScanPlan)({
            repoRoot: (0, workflow_template_root_1.getWorkflowTemplateRepoRoot)(input.runContext),
            ticketTrackerKind: input.config.ticketTracker.kind,
            displayTimeZone: input.config.i18n.displayTimeZone,
            workflowLabels: input.config.ticketTracker.workflowLabels,
            ticketTracker: input.ticketTracker,
            plan,
            openIssues: issuesToScan,
            eventHooks: {
                config: input.config,
                worker: input.runContext.worker,
                ...(input.runContext.logHomeDirectory !== undefined
                    ? { homeDirectory: input.runContext.logHomeDirectory }
                    : {}),
            },
        });
    }
    if (issuesToScan.length === 0) {
        return {
            manualIssuePool: input.manualIssuePool,
            manualCandidateSelection: input.manualCandidateSelection,
        };
    }
    const manualIssuePool = input.issueIds !== undefined
        ? await loadRequestedOpenIssues(input.ticketTracker, input.issueIds)
        : await loadOpenManualIssues(input.ticketTracker);
    return {
        manualIssuePool,
        manualCandidateSelection: await selectManualCandidateIssues({
            issues: manualIssuePool,
            explicitIssueIds: input.issueIds !== undefined,
            config: input.config,
            ticketTracker: input.ticketTracker,
            repositoryHost: input.runContext.repositoryHost,
            maxIssues: input.config.parallelism.maxIssues,
        }),
    };
}
function isManualConfigScannableIssue(issue) {
    return ((issue.kind === types_1.TRACKER_ITEM_KINDS.issue ||
        issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest) &&
        (issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiReady ||
            issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiRunning ||
            issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiNeedsHuman ||
            issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiRecurring ||
            issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiScheduled));
}
async function loadOpenManualIssues(ticketTracker) {
    return ticketTracker.listIssues({
        state: "open",
        includeSourceBranchName: false,
    });
}
async function selectManualCandidateIssues(input) {
    if (input.explicitIssueIds) {
        return {
            issues: input.issues,
            requiresWorkerRuntime: await hasManualWorkerCandidates({
                issues: input.issues,
                config: input.config,
                ticketTracker: input.ticketTracker,
                repositoryHost: input.repositoryHost,
            }),
        };
    }
    const nonWorkerCandidates = [];
    const workerCandidates = [];
    for (const issue of input.issues) {
        const candidateKind = await resolveManualCandidateKind({
            issue,
            config: input.config,
            ticketTracker: input.ticketTracker,
            repositoryHost: input.repositoryHost,
            allowRunningContinuation: input.explicitIssueIds,
        });
        if (candidateKind === "non-worker") {
            nonWorkerCandidates.push(issue);
            continue;
        }
        if (candidateKind === "worker") {
            workerCandidates.push(issue);
        }
    }
    return {
        issues: [
            ...nonWorkerCandidates,
            ...workerCandidates.slice(0, input.maxIssues),
        ],
        requiresWorkerRuntime: workerCandidates.length > 0,
    };
}
async function resolveManualCandidateKind(input) {
    if (await isAutomatedNeedsHumanPullRequest(input)) {
        return (await doesPullRequestMonitorNeedWorkflowClosing(input))
            ? "worker"
            : "non-worker";
    }
    if (input.issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiWorkingInPr &&
        input.issue.kind === types_1.TRACKER_ITEM_KINDS.issue) {
        const workflowState = await (0, workflow_state_read_support_1.resolvePersistedIssueWorkflowState)(input.ticketTracker, input.issue.id);
        return (await (0, terminal_source_finalization_1.resolveWorkingInPrMergeFinalizationCandidate)({
            config: input.config,
            repositoryHost: input.repositoryHost,
            issue: input.issue,
            workflowState,
        })) !== null
            ? "worker"
            : "non-worker";
    }
    if (!isWorkflowStateManualCandidateStatus(input.issue.workflowState.status)) {
        return null;
    }
    const workflowState = await (0, workflow_state_read_support_1.resolvePersistedIssueWorkflowState)(input.ticketTracker, input.issue.id);
    return resolveWorkflowStateManualCandidateKind({
        ...input,
        workflowState,
    });
}
function isWorkflowStateManualCandidateStatus(status) {
    return (status === types_2.WORKFLOW_STATUSES.aiReady ||
        status === types_2.WORKFLOW_STATUSES.aiRunning ||
        status === types_2.WORKFLOW_STATUSES.aiRecurring ||
        status === types_2.WORKFLOW_STATUSES.aiScheduled);
}
async function resolveWorkflowStateManualCandidateKind(input) {
    const { issue, workflowState } = input;
    if (!(0, execution_ownership_1.isExecutionOwnershipActive)(workflowState?.executionOwnership) &&
        (await hasPendingManualWorkflowSetup(input))) {
        return "worker";
    }
    if ((0, plan_progress_1.hasUnmaterializedReadyControllerChildIssueAction)(workflowState) ||
        ((0, plan_progress_1.hasControllerChildIssueControlPlaneAction)(workflowState) &&
            !(0, plan_progress_1.hasWorkflowWorkerExecutableAction)(workflowState))) {
        return "non-worker";
    }
    if (isRecurringParentManualCandidate(issue.workflowState.status, {
        rules: issue.workflowState.rules,
        workflowState,
    }) ||
        isScheduledRunManualCandidate(issue.workflowState.status, {
            rules: issue.workflowState.rules,
            workflowState,
        })) {
        return "non-worker";
    }
    if (issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiRecurring ||
        issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiScheduled ||
        (0, scheduled_run_1.hasActiveScheduledRun)(workflowState) ||
        (0, execution_ownership_1.isExecutionOwnershipActive)(workflowState?.executionOwnership)) {
        return null;
    }
    if (isPendingAutoMergePullRequestManualCandidate({ issue, workflowState })) {
        return (await doesPullRequestMonitorNeedWorkflowClosing(input))
            ? "worker"
            : "non-worker";
    }
    if ((await (0, terminal_source_finalization_1.resolveTerminalSourceFinalizationCandidate)({
        config: input.config,
        repositoryHost: input.repositoryHost,
        issue,
        workflowState,
    })) !== null) {
        return "worker";
    }
    const hasHumanResumeAction = (0, repo_helpers_1.hasHumanResumeWorkerAction)({
        issue,
        workflowState,
    });
    if (issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiRunning &&
        input.allowRunningContinuation !== true &&
        !hasHumanResumeAction) {
        return null;
    }
    return (0, plan_progress_1.hasWorkflowWorkerExecutableAction)(workflowState) ||
        hasHumanResumeAction
        ? "worker"
        : null;
}
async function hasPendingManualWorkflowSetup(input) {
    if (input.config.aiWorkflowOptionAutofill === "off" ||
        !(0, config_scan_1.supportsServiceConfigScan)(input.ticketTracker)) {
        return false;
    }
    const request = await (0, config_scan_helpers_1.resolveRunnableWorkflowSetupRequestForIssue)({
        ticketTracker: input.ticketTracker,
        config: input.config,
        issue: input.issue,
        responseLanguage: input.config.i18n.defaultLanguage,
    });
    return request !== null;
}
function isRecurringParentManualCandidate(status, input) {
    return ((status === types_2.WORKFLOW_STATUSES.aiReady ||
        status === types_2.WORKFLOW_STATUSES.aiRecurring) &&
        !input.rules.includes(types_2.WORKFLOW_RULES.aiDisabled) &&
        input.workflowState !== null &&
        (0, recurring_1.isRecurringParentWorkflowState)(input.workflowState) &&
        !(0, execution_ownership_1.isExecutionOwnershipActive)(input.workflowState.executionOwnership) &&
        (0, recurring_auto_stop_1.isRecurringLifecycleTaskDue)(input.workflowState));
}
function isScheduledRunManualCandidate(status, input) {
    return ((status === types_2.WORKFLOW_STATUSES.aiReady ||
        status === types_2.WORKFLOW_STATUSES.aiScheduled) &&
        !input.rules.includes(types_2.WORKFLOW_RULES.aiDisabled) &&
        input.workflowState !== null &&
        (0, scheduled_run_1.hasActiveScheduledRun)(input.workflowState) &&
        !(0, execution_ownership_1.isExecutionOwnershipActive)(input.workflowState.executionOwnership) &&
        (status === types_2.WORKFLOW_STATUSES.aiReady ||
            (0, scheduled_run_1.isScheduledRunDue)(input.workflowState)));
}
async function hasManualWorkerCandidates(input) {
    for (const issue of input.issues) {
        const candidateKind = await resolveManualCandidateKind({
            issue,
            config: input.config,
            ticketTracker: input.ticketTracker,
            repositoryHost: input.repositoryHost,
            allowRunningContinuation: true,
        });
        if (candidateKind === "worker") {
            return true;
        }
    }
    return false;
}
async function doesPullRequestMonitorNeedWorkflowClosing(input) {
    const linkedIssueId = await (0, shared_1.resolveLinkedIssueIdFromPullRequestTimeline)(input.ticketTracker, input.issue);
    if (linkedIssueId === null || linkedIssueId === input.issue.id) {
        return false;
    }
    return ((await (0, workflow_state_read_support_1.resolvePersistedIssueWorkflowState)(input.ticketTracker, linkedIssueId)) !== null);
}
function isPendingAutoMergePullRequestManualCandidate(input) {
    return (input.issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest &&
        input.issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiReady &&
        (0, auto_merge_state_1.hasActiveAutoMergeState)(input.workflowState));
}
async function isAutomatedNeedsHumanPullRequest(input) {
    return (input.issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiNeedsHuman &&
        input.issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest &&
        (await (0, thread_effective_config_1.isTrackerIssueAutoMergeEnabled)({
            config: input.config,
            ticketTracker: input.ticketTracker,
            issue: input.issue,
        })));
}
async function selectManualAssigneeOverrideIssues(input) {
    const overrideIssues = [];
    for (const issue of input.issues) {
        if (isAssignableToCurrentUser(issue.assignees, input.currentUserLogin)) {
            continue;
        }
        if (await isAutomatedNeedsHumanPullRequest({
            issue,
            config: input.config,
            ticketTracker: input.ticketTracker,
        })) {
            continue;
        }
        overrideIssues.push(issue);
    }
    return overrideIssues;
}
async function filterAutomatedNeedsHumanPullRequests(input) {
    const automated = [];
    for (const issue of input.issues) {
        if (await isAutomatedNeedsHumanPullRequest({
            issue,
            config: input.config,
            ticketTracker: input.ticketTracker,
        })) {
            automated.push(issue);
        }
    }
    return automated;
}
async function confirmManualAssigneeOverrides(issues, currentUserLogin, ticketTracker, prompter) {
    for (const issue of issues) {
        if (isAssignableToCurrentUser(issue.assignees, currentUserLogin)) {
            continue;
        }
        if (!prompter.interactive) {
            console.error(`Issue ${issue.displayId} is assigned to ${issue.assignees.join(", ")}. Re-run in an interactive terminal to confirm replacing the assignee with ${currentUserLogin}.`);
            return { ok: false };
        }
        const confirmed = await prompter.confirm({
            label: `Issue ${issue.displayId} is assigned to ${issue.assignees.join(", ")}. Replace the assignee with ${currentUserLogin} and continue?`,
            defaultValue: false,
        });
        if (!confirmed) {
            console.error(`Canceled run because issue ${issue.displayId} is assigned to ${issue.assignees.join(", ")}.`);
            return { ok: false };
        }
        await ticketTracker.replaceAssignees(issue.id, [currentUserLogin]);
        issue.assignees = [currentUserLogin];
    }
    return { ok: true };
}
function isAssignableToCurrentUser(assignees, currentUserLogin) {
    if (assignees.length === 0) {
        return true;
    }
    return assignees.every((assignee) => assignee === currentUserLogin);
}
