"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveRunnableWorkingInPrCleanupIssues = exports.resolveRunnableScheduledIssues = exports.resolveRunnableRunningIssues = exports.resolveRunnableRecurringParentIssues = exports.resolveRunnableReadyIssues = exports.resolveRunnableNeedsHumanIssues = exports.resolveRunnableControlPlaneActionCandidates = exports.inspectRegisteredRepo = exports.scanClosedFollowupRequests = void 0;
exports.createActiveRepoTaskRegistry = createActiveRepoTaskRegistry;
exports.awaitActiveRepoTasks = awaitActiveRepoTasks;
exports.countActiveRepoWork = countActiveRepoWork;
exports.runScheduledRepoEntry = runScheduledRepoEntry;
exports.scanWorkflowConfigurationChanges = scanWorkflowConfigurationChanges;
exports.resolveRunnableWorkflowSetupIssues = resolveRunnableWorkflowSetupIssues;
const control_plane_config_1 = require("../../config/control-plane-config");
const issue_completion_1 = require("../../config/issue-completion");
const branding_1 = require("../../core/branding");
const network_recovery_state_1 = require("../../core/network-recovery-state");
const rate_limit_backoff_1 = require("../../core/rate-limit-backoff");
const service_repo_state_1 = require("../../core/service-repo-state");
const service_repo_state_storage_1 = require("../../core/service-repo-state-storage");
const catalog_1 = require("../../i18n/catalog");
const provider_errors_1 = require("../../platform/provider-errors");
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 pending_publication_recovery_1 = require("../../run/run-once-cycle/pending-publication-recovery");
const execution_ownership_1 = require("../../run/execution-ownership");
const workflow_template_root_1 = require("../../run/run-once-cycle/workflow-template-root");
const types_3 = require("../../workflows/types");
const auto_merge_state_1 = require("../../workflows/auto-merge-state");
const auth_message_sanitizer_1 = require("../auth-message-sanitizer");
const run_cycle_context_1 = require("../run-cycle-context");
const runtime_1 = require("../../worker/runtime");
const shared_1 = require("../service/shared");
const closed_managed_label_reconciliation_1 = require("./closed-managed-label-reconciliation");
const config_failure_publish_1 = require("./config-failure-publish");
const config_publish_execution_1 = require("./config-publish-execution");
const config_scan_1 = require("./config-scan");
const config_scan_helpers_1 = require("./config-scan-helpers");
const drain_1 = require("./drain");
const events_1 = require("./events");
const followup_scan_1 = require("./followup-scan");
const repo_helpers_1 = require("./repo-helpers");
const repo_inspection_support_1 = require("./repo-inspection-support");
const repo_scheduling_support_1 = require("./repo-scheduling-support");
const repo_task_planning_1 = require("./repo-task-planning");
const state_patch_preflight_1 = require("./state-patch-preflight");
const tracked_project_1 = require("./tracked-project");
const workflow_task_graph_1 = require("./workflow-task-graph");
var followup_scan_2 = require("./followup-scan");
Object.defineProperty(exports, "scanClosedFollowupRequests", { enumerable: true, get: function () { return followup_scan_2.scanClosedFollowupRequests; } });
var repo_inspection_support_2 = require("./repo-inspection-support");
Object.defineProperty(exports, "inspectRegisteredRepo", { enumerable: true, get: function () { return repo_inspection_support_2.inspectRegisteredRepo; } });
var repo_scheduling_support_2 = require("./repo-scheduling-support");
Object.defineProperty(exports, "resolveRunnableControlPlaneActionCandidates", { enumerable: true, get: function () { return repo_scheduling_support_2.resolveRunnableControlPlaneActionCandidates; } });
Object.defineProperty(exports, "resolveRunnableNeedsHumanIssues", { enumerable: true, get: function () { return repo_scheduling_support_2.resolveRunnableNeedsHumanIssues; } });
Object.defineProperty(exports, "resolveRunnableReadyIssues", { enumerable: true, get: function () { return repo_scheduling_support_2.resolveRunnableReadyIssues; } });
Object.defineProperty(exports, "resolveRunnableRecurringParentIssues", { enumerable: true, get: function () { return repo_scheduling_support_2.resolveRunnableRecurringParentIssues; } });
Object.defineProperty(exports, "resolveRunnableRunningIssues", { enumerable: true, get: function () { return repo_scheduling_support_2.resolveRunnableRunningIssues; } });
Object.defineProperty(exports, "resolveRunnableScheduledIssues", { enumerable: true, get: function () { return repo_scheduling_support_2.resolveRunnableScheduledIssues; } });
Object.defineProperty(exports, "resolveRunnableWorkingInPrCleanupIssues", { enumerable: true, get: function () { return repo_scheduling_support_2.resolveRunnableWorkingInPrCleanupIssues; } });
const BROKEN_REPO_RETRY_MS = 30_000;
const SERVICE_LOG_NO_EXECUTABLE_ISSUES = "No executable issues.";
const SERVICE_LOG_CYCLE_FAILED = `${branding_1.SERVICE_DISPLAY_NAME} cycle failed.`;
function formatWorkflowSchemaFailureReason(missingRequirements) {
    const missingNames = missingRequirements.map((requirement) => requirement.name);
    return missingNames.length === 0
        ? "Workflow label schema is invalid."
        : `Workflow label schema is invalid or incomplete: ${missingNames.join(", ")}`;
}
function getBlockingWorkflowSchemaRequirements(requirements) {
    return requirements.filter((requirement) => requirement.kind !== "metadata");
}
function createActiveRepoTaskRegistry() {
    return {
        byIssueId: new Map(),
        refreshingIssueIds: new Set(),
    };
}
function isUnsupportedProviderFailure(error) {
    return (0, provider_errors_1.isUnsupportedProviderError)(error);
}
async function recordBrokenRepoResult(entry, reason, locale) {
    await (0, events_1.logServiceEvent)({
        type: shared_1.SERVICE_EVENT_TYPES.repoResult,
        ...(0, events_1.buildRepoEventBase)(entry),
        cwd: entry.cwd,
        result: shared_1.SERVICE_REPO_RESULTS.failed,
        reason: (0, auth_message_sanitizer_1.sanitizeAuthMessage)(reason, locale),
        timestamp: new Date().toISOString(),
    });
}
async function awaitActiveRepoTasks(registry) {
    await Promise.allSettled([...registry.byIssueId.values()].map((entry) => entry.promise));
}
function countActiveRepoWork(registry) {
    return new Set([...registry.byIssueId.keys(), ...registry.refreshingIssueIds])
        .size;
}
// biome-ignore lint/complexity: Repo poll flow stays explicit here to keep scheduling behavior readable.
async function runScheduledRepoEntry(entry, activeTasks, locale = catalog_1.DEFAULT_APP_LOCALE, homeDirectory, repoStateRegistry = (0, service_repo_state_1.createRepoServiceStateRegistry)(), options = {}) {
    const trackedProjectKey = (0, tracked_project_1.getEntryTrackedProjectKey)(entry);
    const nowMs = Date.now();
    if (options.drainSignal &&
        (await (0, drain_1.isServiceDrainRequested)(options.drainSignal))) {
        return 1_000;
    }
    const cooldown = (0, service_repo_state_1.getActiveRepoCooldown)(repoStateRegistry, trackedProjectKey, nowMs);
    if (cooldown !== null) {
        return Math.max(1, cooldown.nextRetryAtMs - nowMs);
    }
    try {
        const prerequisite = await (0, repo_inspection_support_1.inspectRegisteredRepoPrerequisites)(entry, undefined, catalog_1.DEFAULT_APP_LOCALE);
        if (prerequisite !== null) {
            if (prerequisite.state === shared_1.SERVICE_REPO_STATES.broken) {
                await recordBrokenRepoResult(entry, prerequisite.reason ?? SERVICE_LOG_CYCLE_FAILED, catalog_1.DEFAULT_APP_LOCALE);
            }
            return BROKEN_REPO_RETRY_MS;
        }
        const { config } = (0, provider_resolution_1.resolveSupportedProviders)(await (0, control_plane_config_1.loadConfig)(entry.cwd));
        const inspected = await (0, repo_inspection_support_1.inspectRegisteredRepoWithConfig)(entry, config, undefined, catalog_1.DEFAULT_APP_LOCALE, { useConfigLocale: false });
        if (inspected.state === shared_1.SERVICE_REPO_STATES.disabled) {
            return BROKEN_REPO_RETRY_MS;
        }
        if (inspected.state === shared_1.SERVICE_REPO_STATES.broken) {
            await recordBrokenRepoResult(entry, inspected.reason ?? SERVICE_LOG_CYCLE_FAILED, catalog_1.DEFAULT_APP_LOCALE);
            return BROKEN_REPO_RETRY_MS;
        }
        locale = config.i18n.defaultLanguage;
        (0, repo_scheduling_support_1.pruneCompletedActiveTasks)(activeTasks);
        const runContext = await (0, run_cycle_context_1.createRunCycleContextBundle)(entry.cwd, config, {
            ...(homeDirectory ? { homeDirectory } : {}),
            skipWorkerRuntime: true,
        });
        await (0, workflow_template_root_1.refreshWorkflowTemplateRepoRoot)(runContext);
        const workflowTemplateRepoRoot = (0, workflow_template_root_1.getWorkflowTemplateRepoRoot)(runContext);
        const workflowSchema = await runContext.ticketTracker.checkWorkflowSchema?.();
        if (workflowSchema && !workflowSchema.ok) {
            const blockingRequirements = getBlockingWorkflowSchemaRequirements(workflowSchema.missingRequirements);
            if (blockingRequirements.length > 0) {
                if (typeof runContext.ticketTracker.ensureWorkflowSchema !== "function") {
                    throw new Error(formatWorkflowSchemaFailureReason(blockingRequirements));
                }
                await runContext.ticketTracker.ensureWorkflowSchema({
                    syncMetadata: false,
                });
                const refreshedWorkflowSchema = await runContext.ticketTracker.checkWorkflowSchema?.();
                const remainingBlockingRequirements = refreshedWorkflowSchema
                    ? getBlockingWorkflowSchemaRequirements(refreshedWorkflowSchema.missingRequirements)
                    : blockingRequirements;
                if (remainingBlockingRequirements.length > 0) {
                    throw new Error(formatWorkflowSchemaFailureReason(remainingBlockingRequirements));
                }
            }
        }
        if (options.drainSignal &&
            (await (0, drain_1.isServiceDrainRequested)(options.drainSignal))) {
            return config.pollIntervalSec * 1_000;
        }
        let recoveryEntries = await (0, network_recovery_state_1.listRepoNetworkRecoveryEntries)(trackedProjectKey, homeDirectory);
        if (options.drainSignal &&
            (await (0, drain_1.isServiceDrainRequested)(options.drainSignal))) {
            return config.pollIntervalSec * 1_000;
        }
        await (0, repo_scheduling_support_1.recoverDueNetworkIssues)({
            entry,
            config,
            ticketTracker: runContext.ticketTracker,
            currentUserLogin: runContext.currentUserLogin,
            recoveryEntries,
            activeTasks,
            homeDirectory,
        });
        recoveryEntries = await (0, network_recovery_state_1.listRepoNetworkRecoveryEntries)(trackedProjectKey, homeDirectory);
        const snapshot = (0, service_repo_state_1.getOrCreateRepoSnapshotState)(repoStateRegistry, trackedProjectKey);
        const syncStartedAtMs = Date.now();
        const syncPlan = (0, service_repo_state_1.resolveRepoSyncPlan)(snapshot, syncStartedAtMs);
        const syncedIssues = await (0, repo_scheduling_support_1.listServiceRepoIssuesForSync)(runContext.ticketTracker, syncPlan);
        const configScanActivity = await loadServiceConfigScanActivity({
            ticketTracker: runContext.ticketTracker,
            snapshot,
        });
        const syncMergeResult = (0, service_repo_state_1.mergeRepoSyncResult)(snapshot, syncedIssues, syncPlan.mode, {
            nowMs: Date.now(),
            additionalConfigScanCandidateIssueIds: configScanActivity.candidateIssueIds,
            excludedConfigScanCandidateIssueIds: activeTasks.refreshingIssueIds,
        });
        const openIssues = syncMergeResult.openIssues;
        const configCandidateIssues = openIssues.filter((issue) => syncMergeResult.configScanCandidateIssueIds.has(issue.id));
        const statePatchIssues = resolveStatePatchPreflightIssues(snapshot, openIssues, configCandidateIssues);
        const statePatchPreflight = await (0, state_patch_preflight_1.runServiceStatePatchPreflight)({
            entry,
            config,
            ticketTracker: runContext.ticketTracker,
            issues: statePatchIssues,
            homeDirectory,
            repoStateRegistry,
            trackedProjectKey,
            retryMs: BROKEN_REPO_RETRY_MS,
            ...(options.drainSignal ? { drainSignal: options.drainSignal } : {}),
        });
        if (!statePatchPreflight.completed) {
            return config.pollIntervalSec * 1_000;
        }
        if (statePatchPreflight.retryDelayMs !== null) {
            return statePatchPreflight.retryDelayMs;
        }
        snapshot.lastStatePatchWorkflowStateVersion = types_3.WORKFLOW_STATE_VERSION;
        const serviceEligibleOpenIssues = excludeStatePatchIsolatedIssues(openIssues, statePatchPreflight.isolatedIssueIds);
        const serviceEligibleConfigCandidateIssues = configCandidateIssues.filter((issue) => !statePatchPreflight.isolatedIssueIds.has(issue.id));
        if (options.drainSignal &&
            (await (0, drain_1.isServiceDrainRequested)(options.drainSignal))) {
            return config.pollIntervalSec * 1_000;
        }
        if ((0, config_scan_1.supportsServiceConfigScan)(runContext.ticketTracker)) {
            const configScanResult = await scanWorkflowConfigurationChanges({
                cwd: entry.cwd,
                workflowTemplateRepoRoot,
                config,
                ticketTracker: runContext.ticketTracker,
                openIssues: serviceEligibleOpenIssues,
                configCandidateIssues: serviceEligibleConfigCandidateIssues,
                ...(runContext.currentUserLogin
                    ? { currentUserLogin: runContext.currentUserLogin }
                    : {}),
                ...(options.drainSignal ? { drainSignal: options.drainSignal } : {}),
            });
            for (const issueId of configScanResult.scannedIssueIds) {
                const issue = snapshot.openIssuesById.get(issueId);
                if (issue) {
                    (0, service_repo_state_1.recordRepoConfigScanAttempt)(snapshot, issue, Date.now());
                }
            }
            if (!configScanResult.completed) {
                return config.pollIntervalSec * 1_000;
            }
        }
        if (options.drainSignal &&
            (await (0, drain_1.isServiceDrainRequested)(options.drainSignal))) {
            return config.pollIntervalSec * 1_000;
        }
        const closedManagedLabelCandidates = await (0, closed_managed_label_reconciliation_1.listClosedServiceManagedLabelReconciliationCandidates)({
            ticketTracker: runContext.ticketTracker,
            workflowLabels: config.ticketTracker.workflowLabels,
            candidateIssues: syncedIssues,
            includeBackfill: syncPlan.mode === "full",
            ...(options.drainSignal ? { drainSignal: options.drainSignal } : {}),
        });
        if (!closedManagedLabelCandidates.completed) {
            return config.pollIntervalSec * 1_000;
        }
        if (options.drainSignal &&
            (await (0, drain_1.isServiceDrainRequested)(options.drainSignal))) {
            return config.pollIntervalSec * 1_000;
        }
        let closedFollowupWorker = null;
        const closedFollowupResult = await (0, followup_scan_1.scanClosedFollowupRequests)({
            cwd: entry.cwd,
            workflowTemplateRepoRoot,
            config,
            ticketTracker: runContext.ticketTracker,
            candidateIssues: [
                ...syncedIssues,
                ...closedManagedLabelCandidates.issues,
            ],
            currentUserLogin: runContext.currentUserLogin,
            loadWorker: async () => {
                closedFollowupWorker ??= await (0, runtime_1.createConfiguredWorkerRuntime)(config, {
                    ...(homeDirectory ? { homeDirectory } : {}),
                });
                return closedFollowupWorker;
            },
            ...(options.drainSignal ? { drainSignal: options.drainSignal } : {}),
        });
        if (!closedFollowupResult.completed) {
            return config.pollIntervalSec * 1_000;
        }
        if (options.drainSignal &&
            (await (0, drain_1.isServiceDrainRequested)(options.drainSignal))) {
            return config.pollIntervalSec * 1_000;
        }
        const closedReconcileResult = await (0, closed_managed_label_reconciliation_1.reconcileClosedServiceManagedLabels)({
            cwd: entry.cwd,
            config,
            ticketTracker: runContext.ticketTracker,
            candidateIssues: closedManagedLabelCandidates.issues,
            ...(options.drainSignal ? { drainSignal: options.drainSignal } : {}),
        });
        if (!closedReconcileResult.completed) {
            return config.pollIntervalSec * 1_000;
        }
        if (options.drainSignal &&
            (await (0, drain_1.isServiceDrainRequested)(options.drainSignal))) {
            return config.pollIntervalSec * 1_000;
        }
        if (!configScanActivity.completed) {
            return config.pollIntervalSec * 1_000;
        }
        (0, service_repo_state_1.recordRepoSyncCheckpoint)(snapshot, syncPlan.mode, syncStartedAtMs, {
            configScanActivityCursor: configScanActivity.nextCursor,
        });
        const tasksToLaunch = await resolveServiceCycleIssueTasksToLaunch({
            cwd: entry.cwd,
            workflowTemplateRepoRoot,
            ticketTracker: runContext.ticketTracker,
            repositoryHost: runContext.repositoryHost,
            openIssues: serviceEligibleOpenIssues,
            config,
            activeTasks,
            recoveryEntries,
            ...(runContext.currentUserLogin
                ? { currentUserLogin: runContext.currentUserLogin }
                : {}),
        });
        if (tasksToLaunch.length === 0 && activeTasks.byIssueId.size === 0) {
            console.log(`[${(0, tracked_project_1.formatTrackedProjectLabel)(entry)}] ${SERVICE_LOG_NO_EXECUTABLE_ISSUES}`);
            await (0, events_1.logServiceEvent)({
                type: shared_1.SERVICE_EVENT_TYPES.repoResult,
                ...(0, events_1.buildRepoEventBase)(entry),
                cwd: entry.cwd,
                result: shared_1.SERVICE_REPO_RESULTS.noOp,
                timestamp: new Date().toISOString(),
            });
        }
        for (const task of tasksToLaunch) {
            if (options.drainSignal &&
                (await (0, drain_1.isServiceDrainRequested)(options.drainSignal))) {
                break;
            }
            const affectedIssueIds = (0, repo_task_planning_1.normalizeAffectedIssueIds)(task);
            for (const issueId of affectedIssueIds) {
                activeTasks.refreshingIssueIds.add(issueId);
                (0, service_repo_state_1.removeRepoSnapshotIssue)(snapshot, issueId);
            }
            const promise = (async () => {
                let issueIdsToRefresh = affectedIssueIds;
                try {
                    const additionalIssueIds = await (0, repo_scheduling_support_1.launchScheduledIssueTask)(entry, config, runContext, task.issue.id, locale, task.workflowGraphIssues, task.workerSlotCount, task.requiresWorkerRuntime, homeDirectory, repoStateRegistry, task.kind, task.recoveryEntry);
                    issueIdsToRefresh = dedupeIssueIds([
                        ...affectedIssueIds,
                        ...additionalIssueIds,
                    ]);
                }
                finally {
                    await refreshCompletedTaskSnapshots({
                        snapshot,
                        ticketTracker: runContext.freshTicketTracker,
                        issueIds: issueIdsToRefresh,
                    });
                    for (const issueId of affectedIssueIds) {
                        activeTasks.refreshingIssueIds.delete(issueId);
                    }
                    activeTasks.byIssueId.delete(task.issue.id);
                }
            })();
            activeTasks.byIssueId.set(task.issue.id, {
                promise,
                workerSlotCount: task.workerSlotCount,
                consumesMergeSlot: task.consumesMergeSlot,
            });
        }
        return config.pollIntervalSec * 1_000;
    }
    catch (error) {
        if (isUnsupportedProviderFailure(error)) {
            throw error;
        }
        const rateLimitBackoff = (0, rate_limit_backoff_1.extractRateLimitBackoff)(error);
        if (rateLimitBackoff !== null) {
            const retryAtMs = Date.parse(rateLimitBackoff.nextRetryAt);
            if (!Number.isNaN(retryAtMs)) {
                (0, service_repo_state_1.recordRepoCooldown)(repoStateRegistry, trackedProjectKey, {
                    nextRetryAtMs: retryAtMs,
                    reason: rateLimitBackoff.reason,
                    lastError: error instanceof Error ? error.message : SERVICE_LOG_CYCLE_FAILED,
                    updatedAt: new Date().toISOString(),
                });
                await (0, service_repo_state_storage_1.persistRepoServiceStateRegistrySafely)(repoStateRegistry, homeDirectory);
                return Math.max(1, retryAtMs - Date.now());
            }
        }
        const reason = (0, auth_message_sanitizer_1.sanitizeAuthMessage)(error instanceof Error ? error.message : SERVICE_LOG_CYCLE_FAILED, catalog_1.DEFAULT_APP_LOCALE);
        console.error(`[${(0, tracked_project_1.formatTrackedProjectLabel)(entry)}] ${reason}`);
        await (0, events_1.logServiceEvent)({
            type: shared_1.SERVICE_EVENT_TYPES.repoResult,
            ...(0, events_1.buildRepoEventBase)(entry),
            cwd: entry.cwd,
            result: shared_1.SERVICE_REPO_RESULTS.failed,
            reason,
            timestamp: new Date().toISOString(),
        });
        return BROKEN_REPO_RETRY_MS;
    }
}
async function refreshCompletedTaskSnapshot(input) {
    try {
        const cleanupState = await input.ticketTracker.getIssueCleanupState(input.issueId);
        if (cleanupState.isOpen) {
            const issue = await input.ticketTracker.getIssue(input.issueId);
            (0, service_repo_state_1.refreshRepoSnapshotIssue)(input.snapshot, issue);
            return;
        }
    }
    catch {
        // Closed or deleted issues can disappear during cleanup.
    }
    (0, service_repo_state_1.removeRepoSnapshotIssue)(input.snapshot, input.issueId);
}
async function refreshCompletedTaskSnapshots(input) {
    await Promise.all(input.issueIds.map((issueId) => refreshCompletedTaskSnapshot({
        snapshot: input.snapshot,
        ticketTracker: input.ticketTracker,
        issueId,
    })));
}
function dedupeIssueIds(issueIds) {
    return [...new Set(issueIds)];
}
async function loadServiceConfigScanActivity(input) {
    if (typeof input.ticketTracker.listServiceConfigScanActivity !== "function") {
        return {
            candidateIssueIds: [],
            nextCursor: input.snapshot.configScanActivityCursor,
            completed: true,
        };
    }
    return input.ticketTracker.listServiceConfigScanActivity({
        cursor: input.snapshot.configScanActivityCursor,
        initializeOnly: input.snapshot.configScanActivityCursor === null,
    });
}
function resolveStatePatchPreflightIssues(snapshot, openIssues, configCandidateIssues) {
    return snapshot.lastStatePatchWorkflowStateVersion === types_3.WORKFLOW_STATE_VERSION
        ? configCandidateIssues
        : openIssues;
}
async function scanWorkflowConfigurationChanges(input) {
    const scannedIssueIds = [];
    const scannableIssues = input.configCandidateIssues.filter((issue) => (issue.kind === types_1.TRACKER_ITEM_KINDS.issue ||
        issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest) &&
        !(issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest &&
            (0, repo_helpers_1.shouldSkipForServiceRuleLabels)(issue.workflowState.rules)));
    const workflowTemplateRepoRoot = input.workflowTemplateRepoRoot ?? input.cwd;
    let completedScan = true;
    for (const issue of scannableIssues) {
        if (input.drainSignal &&
            (await (0, drain_1.isServiceDrainRequested)(input.drainSignal))) {
            completedScan = false;
            break;
        }
        if (!issue ||
            typeof issue !== "object" ||
            !("workflowState" in issue) ||
            !issue.workflowState ||
            !Array.isArray(issue.workflowState.rules) ||
            typeof issue.title !== "string" ||
            typeof issue.body !== "string") {
            continue;
        }
        try {
            scannedIssueIds.push(issue.id);
            if (await shouldDeferConfigScanToWorkflowSetup(input, issue)) {
                continue;
            }
            const plan = await (0, config_scan_1.scanIssueWorkflowConfiguration)({
                repoRoot: workflowTemplateRepoRoot,
                config: input.config,
                ticketTracker: input.ticketTracker,
                issue,
                enableImplicitReplan: true,
            });
            await (0, config_publish_execution_1.publishServiceRuntimeConfigScanPlan)({
                repoRoot: workflowTemplateRepoRoot,
                ticketTrackerKind: input.config.ticketTracker.kind,
                displayTimeZone: input.config.i18n.displayTimeZone,
                workflowLabels: input.config.ticketTracker.workflowLabels,
                ticketTracker: input.ticketTracker,
                plan,
                openIssues: input.openIssues,
                eventHooks: {
                    config: input.config,
                },
            });
        }
        catch (error) {
            // Config-scan failures are issue-local; keep scanning later issues.
            const reason = (0, auth_message_sanitizer_1.sanitizeAuthMessage)(error instanceof Error ? error.message : SERVICE_LOG_CYCLE_FAILED, catalog_1.DEFAULT_APP_LOCALE);
            console.error(`[service-config-scan] Skipped ${issue.displayId}: ${reason}`);
            try {
                await (0, config_failure_publish_1.publishServiceRuntimeConfigFailureHandoff)({
                    cwd: input.cwd,
                    config: input.config,
                    ticketTracker: input.ticketTracker,
                    issue,
                    openIssues: input.openIssues,
                    workflowTemplateRepoRoot,
                    ...(input.currentUserLogin
                        ? { currentUserLogin: input.currentUserLogin }
                        : {}),
                    error,
                });
            }
            catch (handoffError) {
                const handoffReason = (0, auth_message_sanitizer_1.sanitizeAuthMessage)(handoffError instanceof Error
                    ? handoffError.message
                    : SERVICE_LOG_CYCLE_FAILED, catalog_1.DEFAULT_APP_LOCALE);
                console.error(`[service-config-scan] Failed to hand off ${issue.displayId}: ${handoffReason}`);
            }
        }
    }
    if (!completedScan) {
        return { completed: false, scannedIssueIds };
    }
    return { completed: true, scannedIssueIds };
}
async function shouldDeferConfigScanToWorkflowSetup(input, issue) {
    const setupRequest = await (0, config_scan_helpers_1.resolveRunnableWorkflowSetupRequestForIssue)({
        ticketTracker: input.ticketTracker,
        config: input.config,
        issue,
        responseLanguage: input.config.i18n.defaultLanguage,
        ...(input.currentUserLogin
            ? { currentAssignee: input.currentUserLogin }
            : {}),
    });
    return setupRequest !== null;
}
// biome-ignore lint/complexity/noExcessiveLinesPerFunction: The scheduler keeps candidate categories together to preserve task priority ordering.
async function resolveServiceCycleIssueTasksToLaunch(input) {
    const blockedIssueIds = resolveBlockedNetworkRecoveryIssueIds(input.recoveryEntries);
    const baseRunnableIssues = resolveBaseRunnableIssues({
        issues: input.openIssues,
        ...(input.currentUserLogin
            ? { currentUserLogin: input.currentUserLogin }
            : {}),
    });
    const readyCandidateIssues = baseRunnableIssues.filter((issue) => issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiReady);
    const runningCandidateIssues = baseRunnableIssues.filter((issue) => issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiRunning);
    const { workflowGraph, workflowGraphIssues, workflowStateByIssueId } = await (0, workflow_task_graph_1.loadServiceWorkflowGraphContext)({
        ticketTracker: input.ticketTracker,
        openIssues: input.openIssues,
    });
    const workflowSetupIssues = await resolveRunnableWorkflowSetupIssues({
        config: input.config,
        ticketTracker: input.ticketTracker,
        issues: baseRunnableIssues,
        workflowStateByIssueId,
        ...(input.currentUserLogin
            ? { currentUserLogin: input.currentUserLogin }
            : {}),
    });
    const workflowSetupIssueIds = new Set(workflowSetupIssues.map((issue) => issue.id));
    const terminalSourceFinalizationTasks = await (0, repo_scheduling_support_1.resolveTerminalSourceFinalizationTasks)({
        config: input.config,
        repositoryHost: input.repositoryHost,
        ticketTracker: input.ticketTracker,
        issues: [...readyCandidateIssues, ...runningCandidateIssues].filter((issue) => !workflowSetupIssueIds.has(issue.id)),
        workflowStateByIssueId,
        workflowGraphIssues,
    });
    const terminalSourceFinalizationIssueIds = new Set(terminalSourceFinalizationTasks.map((task) => task.issue.id));
    const controlPlaneActionCandidates = [
        ...readyCandidateIssues,
        ...runningCandidateIssues,
    ]
        .filter((issue) => !workflowSetupIssueIds.has(issue.id))
        .filter((issue) => !terminalSourceFinalizationIssueIds.has(issue.id))
        .filter((issue) => (0, repo_helpers_1.isServiceControlPlaneActionCandidate)({
        issue,
        workflowState: workflowStateByIssueId.get(issue.id) ?? null,
        ...(input.currentUserLogin
            ? { currentUserLogin: input.currentUserLogin }
            : {}),
    }));
    const controlPlaneActionRunnableCandidates = await (0, repo_scheduling_support_1.resolveRunnableControlPlaneActionCandidates)({
        ticketTracker: input.ticketTracker,
        issues: controlPlaneActionCandidates,
        workflowStateByIssueId,
        config: input.config,
    });
    const controlPlaneActionIssues = controlPlaneActionRunnableCandidates.map(({ issue }) => issue);
    const controlPlaneActionIssueIdsWithReflectableCompletedChild = new Set(controlPlaneActionRunnableCandidates.flatMap((candidate) => candidate.hasReflectableCompletedControllerChild
        ? [candidate.issue.id]
        : []));
    const pullRequestReviewNecessityIssues = resolveRunnablePullRequestReviewNecessityIssues({
        config: input.config,
        issues: readyCandidateIssues
            .filter((issue) => !workflowSetupIssueIds.has(issue.id))
            .filter((issue) => !terminalSourceFinalizationIssueIds.has(issue.id)),
        workflowStateByIssueId,
    });
    const pullRequestReviewNecessityIssueIds = new Set(pullRequestReviewNecessityIssues.map((issue) => issue.id));
    const workerReadyCandidateIssues = readyCandidateIssues.filter((issue) => workflowSetupIssueIds.has(issue.id) ||
        terminalSourceFinalizationIssueIds.has(issue.id) ||
        pullRequestReviewNecessityIssueIds.has(issue.id)
        ? false
        : isWorkerRunnableCandidateOutsideAutoMergeMonitor({
            issue,
            workflowState: workflowStateByIssueId.get(issue.id) ?? null,
            ...(input.currentUserLogin
                ? { currentUserLogin: input.currentUserLogin }
                : {}),
        }));
    const workerRunningCandidateIssues = runningCandidateIssues.filter((issue) => terminalSourceFinalizationIssueIds.has(issue.id)
        ? false
        : isWorkerRunnableCandidateOutsideAutoMergeMonitor({
            issue,
            workflowState: workflowStateByIssueId.get(issue.id) ?? null,
            ...(input.currentUserLogin
                ? { currentUserLogin: input.currentUserLogin }
                : {}),
        }));
    const runnableIssues = await (0, repo_scheduling_support_1.resolveRunnableReadyIssues)({
        config: input.config,
        ticketTracker: input.ticketTracker,
        issues: workerReadyCandidateIssues,
        allOpenIssues: workflowGraphIssues,
        ...(input.currentUserLogin
            ? { currentUserLogin: input.currentUserLogin }
            : {}),
    });
    const runningContinuationIssues = await (0, repo_scheduling_support_1.resolveRunnableRunningIssues)({
        config: input.config,
        ticketTracker: input.ticketTracker,
        issues: workerRunningCandidateIssues,
        ...(input.currentUserLogin
            ? { currentUserLogin: input.currentUserLogin }
            : {}),
    });
    const { recurringParentIssues, scheduledRunIssues } = await resolveScheduledQueueIssues({
        workflowTemplateRepoRoot: input.workflowTemplateRepoRoot ?? input.cwd,
        config: input.config,
        ticketTracker: input.ticketTracker,
        issues: input.openIssues,
        ...(input.currentUserLogin
            ? { currentUserLogin: input.currentUserLogin }
            : {}),
    });
    const needsHumanIssues = await (0, repo_scheduling_support_1.resolveRunnableNeedsHumanIssues)({
        ticketTracker: input.ticketTracker,
        repositoryHost: input.repositoryHost,
        issues: baseRunnableIssues.filter((issue) => issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiNeedsHuman &&
            issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest),
        config: input.config,
        skipOpenManagedDependencyWait: input.config.dependencies.strategy === "auto",
        ...(input.currentUserLogin
            ? { currentUserLogin: input.currentUserLogin }
            : {}),
    });
    const needsHumanPullRequestTasks = await (0, repo_scheduling_support_1.resolvePullRequestMonitorTasks)({
        ticketTracker: input.ticketTracker,
        issues: needsHumanIssues,
    });
    const autoMergeIssues = await (0, repo_scheduling_support_1.resolveRunnableAutoMergeIssues)({
        ticketTracker: input.ticketTracker,
        issues: baseRunnableIssues,
        skipOpenManagedDependencyWait: input.config.dependencies.strategy === "auto",
        ...(input.currentUserLogin
            ? { currentUserLogin: input.currentUserLogin }
            : {}),
    });
    const autoMergePullRequestTasks = await (0, repo_scheduling_support_1.resolvePullRequestMonitorTasks)({
        ticketTracker: input.ticketTracker,
        issues: autoMergeIssues,
        skipOpenManagedDependencyWait: input.config.dependencies.strategy === "auto",
    });
    const workingInPrCleanupIssues = await (0, repo_scheduling_support_1.resolveRunnableWorkingInPrCleanupIssues)({
        config: input.config,
        repositoryHost: input.repositoryHost,
        issues: baseRunnableIssues.filter((issue) => issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiWorkingInPr &&
            issue.kind === types_1.TRACKER_ITEM_KINDS.issue),
    });
    const workerIssues = [
        ...runningContinuationIssues,
        ...scheduledRunIssues.filter((issue) => !workflowSetupIssueIds.has(issue.id)),
        ...pullRequestReviewNecessityIssues,
        ...runnableIssues,
    ];
    const singleSlotWorkerTasks = await (0, repo_scheduling_support_1.resolveWorkerIssueTasks)({
        ticketTracker: input.ticketTracker,
        issues: workflowSetupIssues,
        workflowGraphIssues,
        workerSlotCount: 1,
        workerSlotDemand: 1,
    });
    const workerTasks = await (0, repo_scheduling_support_1.resolveWorkerIssueTasks)({
        ticketTracker: input.ticketTracker,
        issues: workerIssues,
        workflowGraphIssues,
        workerSlotCount: 1,
    });
    return (0, repo_task_planning_1.buildLaunchableServiceTasks)({
        activeTasks: input.activeTasks,
        blockedIssueIds,
        config: input.config,
        controlPlaneActionIssues,
        controlPlaneActionIssueIdsWithReflectableCompletedChild,
        needsHumanPullRequestTasks,
        autoMergePullRequestTasks,
        terminalSourceFinalizationTasks,
        pendingPublicationRecoveryTasks: resolvePendingPublicationRecoveryTasks(input),
        workflowGraph,
        workflowGraphIssues,
        workflowStateByIssueId,
        recurringParentIssues: recurringParentIssues.filter((issue) => !workflowSetupIssueIds.has(issue.id)),
        singleSlotWorkerTasks,
        workerTasks,
        workingInPrCleanupIssues,
    });
}
function resolveBlockedNetworkRecoveryIssueIds(recoveryEntries) {
    return new Set(recoveryEntries
        .filter((entry) => (0, pending_publication_recovery_1.hasPendingWaitHumanPublication)(entry) ||
        Date.parse(entry.nextRetryAt) > Date.now())
        .map((entry) => entry.issueId));
}
function resolvePendingPublicationRecoveryTasks(input) {
    const issueById = new Map(input.openIssues.map((issue) => [issue.id, issue]));
    return input.recoveryEntries
        .filter(pending_publication_recovery_1.hasPendingWaitHumanPublication)
        .filter(pending_publication_recovery_1.isPendingWaitHumanPublicationRecoveryDue)
        .flatMap((recoveryEntry) => {
        const issue = issueById.get(recoveryEntry.issueId);
        if (issue === undefined) {
            return [];
        }
        return [
            {
                kind: "pending_publication_recovery",
                issue,
                affectedIssueIds: [issue.id],
                requiresWorkerRuntime: false,
                workerSlotCount: 0,
                consumesMergeSlot: false,
                recoveryEntry,
            },
        ];
    });
}
function excludeStatePatchIsolatedIssues(issues, isolatedIssueIds) {
    return isolatedIssueIds.size === 0
        ? issues
        : issues.filter((issue) => !isolatedIssueIds.has(issue.id));
}
function resolveBaseRunnableIssues(input) {
    return input.issues.filter((issue) => (0, repo_helpers_1.hasServiceRunnableBasePreconditions)({
        assignees: issue.assignees,
        rules: issue.workflowState.rules,
        ...(input.currentUserLogin
            ? { currentUserLogin: input.currentUserLogin }
            : {}),
    }));
}
function isWorkerRunnableCandidateOutsideAutoMergeMonitor(input) {
    if (input.issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest &&
        (0, auto_merge_state_1.hasActiveAutoMergeState)(input.workflowState)) {
        return false;
    }
    return (0, repo_helpers_1.isServiceWorkerRunnableCandidate)(input);
}
function resolveRunnablePullRequestReviewNecessityIssues(input) {
    return input.issues.filter((issue) => {
        if (issue.kind !== types_1.TRACKER_ITEM_KINDS.pullRequest) {
            return false;
        }
        const workflowState = input.workflowStateByIssueId.get(issue.id) ?? null;
        if (workflowState === null ||
            workflowState.workflowSurface !== "pull_request" ||
            (0, auto_merge_state_1.hasActiveAutoMergeState)(workflowState) ||
            (0, execution_ownership_1.isExecutionOwnershipActive)(workflowState.executionOwnership)) {
            return false;
        }
        const effectiveConfig = (0, control_plane_config_1.resolveIssueEffectiveConfigForWorkflowState)(input.config, workflowState);
        return (0, issue_completion_1.shouldEvaluatePullRequestReviewNecessity)(effectiveConfig.issueCompletion);
    });
}
async function resolveRunnableWorkflowSetupIssues(input) {
    if (!(0, config_scan_1.supportsServiceConfigScan)(input.ticketTracker)) {
        return [];
    }
    const pendingSetupIssues = [];
    for (const issue of input.issues) {
        const workflowState = input.workflowStateByIssueId.get(issue.id) ?? null;
        if (workflowState === null ||
            (0, execution_ownership_1.isExecutionOwnershipActive)(workflowState.executionOwnership)) {
            continue;
        }
        const request = await (0, config_scan_helpers_1.resolveRunnableWorkflowSetupRequestForIssue)({
            ticketTracker: input.ticketTracker,
            config: input.config,
            issue,
            responseLanguage: input.config.i18n.defaultLanguage,
            ...(input.currentUserLogin
                ? { currentAssignee: input.currentUserLogin }
                : {}),
        });
        if (request?.kind === "fresh_comment") {
            pendingSetupIssues.push(issue);
        }
    }
    return pendingSetupIssues;
}
async function resolveScheduledQueueIssues(input) {
    const sharedInput = {
        config: input.config,
        repoRoot: input.workflowTemplateRepoRoot,
        ticketTrackerKind: input.config.ticketTracker.kind,
        displayTimeZone: input.config.i18n.displayTimeZone,
        ticketTracker: input.ticketTracker,
        issues: input.issues,
        workflowLabels: input.config.ticketTracker.workflowLabels,
        ...(input.currentUserLogin
            ? { currentUserLogin: input.currentUserLogin }
            : {}),
    };
    const recurringParentIssues = await (0, repo_scheduling_support_1.resolveRunnableRecurringParentIssues)(sharedInput);
    const scheduledRunIssues = await (0, repo_scheduling_support_1.resolveRunnableScheduledIssues)(sharedInput);
    return { recurringParentIssues, scheduledRunIssues };
}
