"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.publishSpawnChildContinuation = publishSpawnChildContinuation;
const catalog_1 = require("../../../i18n/catalog");
const types_1 = require("../../../platform/workflow-state/types");
const resolution_issue_1 = require("../../../skills/resolution-issue");
const issue_ref_1 = require("../../../workflows/issue-ref");
const state_1 = require("../../../workflows/state");
const child_issue_title_1 = require("../child-issue-title");
const child_publication_1 = require("../child-publication");
const child_workflow_state_1 = require("../child-workflow-state");
const issue_branch_target_1 = require("../issue-branch-target");
const issue_branch_remote_1 = require("../issue-branch-remote");
const issue_creation_publication_1 = require("../issue-creation-publication");
const publisher_factory_1 = require("../publisher-factory");
const workflow_template_root_1 = require("../workflow-template-root");
const transition_1 = require("./transition");
const utils_1 = require("./utils");
async function publishSpawnChildContinuation(input) {
    const workerExecution = {
        ...input.workerExecution,
        workflowState: requireUnplannedParentWorkflowState(input.workerExecution),
    };
    const continuation = readSpawnChildContinuation(workerExecution);
    const openIssues = await input.context.ticketTracker.listIssues({
        state: "open",
    });
    const originIssue = workerExecution.issue;
    const [liveBlockers, workflowStateByIssueId] = await Promise.all([
        input.context.ticketTracker.listBlockingDependencies(originIssue.id),
        (0, child_workflow_state_1.loadChildCanonicalStateMap)({
            ticketTracker: input.context.ticketTracker,
            issues: openIssues,
        }),
    ]);
    const existingChild = findOpenUnplannedChildIssue({
        openIssues,
        liveBlockers,
        originIssue,
        workflowStateByIssueId,
    });
    const reusableIssues = narrowReusableIssues(openIssues, originIssue, continuation.reason, workflowStateByIssueId);
    const childPlan = await buildUnplannedChildContinuationPlan({
        context: input.context,
        workflowLabels: input.workflowLabels,
        workerExecution,
        continuation,
        workspacePath: input.workspace.path,
        openIssues: reusableIssues,
        existingChild,
        workflowStateByIssueId,
    });
    if (childPlan.kind === "reuse") {
        if (childPlan.publicationPlan !== undefined) {
            const publisher = (0, publisher_factory_1.createRunWorkflowPublisher)(input.context);
            await (0, issue_creation_publication_1.executeIssuePublicationPlans)({
                publisher,
                plans: [childPlan.publicationPlan],
                eventHooks: {
                    repoRoot: (0, workflow_template_root_1.getWorkflowTemplateRepoRoot)(input.context),
                    config: input.context.config,
                    worker: input.context.worker,
                    ...(input.context.logHomeDirectory !== undefined
                        ? { homeDirectory: input.context.logHomeDirectory }
                        : {}),
                },
            });
        }
        return (0, transition_1.publishSpawnChildTransition)({
            context: input.context,
            claimedIssue: input.claimedIssue,
            logger: input.logger,
            workflowLabels: input.workflowLabels,
            workerExecution,
            summary: (0, transition_1.buildReuseSummary)(workerExecution.responseLanguage, childPlan.issue.displayId),
            visibleUpdateKind: child_publication_1.CHILD_ISSUE_VISIBLE_UPDATE_KINDS.unplannedChildReused,
            ...(childPlan.dependencyChanges !== undefined
                ? { dependencyChanges: childPlan.dependencyChanges }
                : {}),
        });
    }
    const publisher = (0, publisher_factory_1.createRunWorkflowPublisher)(input.context);
    const publication = await (0, issue_creation_publication_1.executeIssuePublicationPlans)({
        publisher,
        plans: [childPlan.publicationPlan],
        eventHooks: {
            repoRoot: (0, workflow_template_root_1.getWorkflowTemplateRepoRoot)(input.context),
            config: input.context.config,
            worker: input.context.worker,
            ...(input.context.logHomeDirectory !== undefined
                ? { homeDirectory: input.context.logHomeDirectory }
                : {}),
        },
    });
    const createdChild = publication.executions[0]?.issue;
    if (createdChild === undefined) {
        throw new Error("Unplanned child publication returned no target issue.");
    }
    return (0, transition_1.publishSpawnChildTransition)({
        context: input.context,
        claimedIssue: input.claimedIssue,
        logger: input.logger,
        workflowLabels: input.workflowLabels,
        workerExecution,
        summary: (0, transition_1.buildCreatedChildSummary)(workerExecution.responseLanguage, createdChild.displayId),
        visibleUpdateKind: child_publication_1.CHILD_ISSUE_VISIBLE_UPDATE_KINDS.unplannedChildCreated,
    });
}
async function buildUnplannedChildContinuationPlan(input) {
    if (input.existingChild !== null) {
        return {
            kind: "reuse",
            issue: input.existingChild.issue,
            ...(input.existingChild.linked
                ? {}
                : {
                    dependencyChanges: [
                        {
                            kind: "add_blocking_dependency",
                            issue: {
                                kind: "issue",
                                issueId: input.workerExecution.issue.id,
                            },
                            blockedBy: {
                                kind: "issue",
                                issueId: input.existingChild.issue.id,
                            },
                        },
                    ],
                }),
        };
    }
    const childBranchTarget = resolveUnplannedChildBranchTarget(input.continuation);
    const childBaseBranch = (0, issue_branch_target_1.resolveIssueCreationBaseBranch)({
        branchTarget: childBranchTarget,
        config: input.context.config,
        parentIssue: input.workerExecution.issue,
        effectiveWorkflowState: input.workerExecution.workflowState,
    });
    let parentBranchRemoteEnsured = false;
    const ensureParentBranchRemote = async () => {
        if (childBranchTarget !== "parent_issue_branch" ||
            parentBranchRemoteEnsured) {
            return;
        }
        await (0, issue_branch_remote_1.ensureParentIssueBranchRemoteRef)({
            context: input.context,
            parentIssue: input.workerExecution.issue,
            workflowState: input.workerExecution.workflowState,
        });
        parentBranchRemoteEnsured = true;
    };
    const selectedIssue = await selectReusableIssue({
        context: input.context,
        workspacePath: input.workspacePath,
        workerExecution: input.workerExecution,
        continuation: input.continuation,
        openIssues: [...input.openIssues],
    });
    if (selectedIssue !== null) {
        const selectedIssueWorkflowState = input.workflowStateByIssueId.get(selectedIssue.id) ?? null;
        const shouldMaterializeSelectedIssue = selectedIssueWorkflowState === null &&
            selectedIssue.workflowState.status === types_1.WORKFLOW_STATUSES.aiReady;
        if (shouldMaterializeSelectedIssue) {
            await ensureParentBranchRemote();
        }
        return {
            kind: "reuse",
            issue: selectedIssue,
            ...(shouldMaterializeSelectedIssue
                ? {
                    publicationPlan: (0, issue_creation_publication_1.buildPatchIssuePublicationPlan)({
                        issue: selectedIssue,
                        title: selectedIssue.title,
                        body: selectedIssue.body,
                        trackerFields: {
                            assignees: selectedIssue.assignees,
                            labelNames: selectedIssue.labelNames ?? [
                                input.workflowLabels.aiReady,
                            ],
                            ...(selectedIssue.milestoneRef !== undefined
                                ? { milestoneRef: selectedIssue.milestoneRef }
                                : {}),
                            ...(selectedIssue.projectRef !== undefined
                                ? { projectRef: selectedIssue.projectRef }
                                : {}),
                        },
                        workflowState: buildUnplannedChildWorkflowState({
                            parentIssue: input.workerExecution.issue,
                            parentWorkflowState: input.workerExecution.workflowState,
                            baseBranch: childBaseBranch,
                            objective: input.continuation.title,
                        }),
                        workflowProjection: {
                            previous: selectedIssue.workflowState,
                            next: selectedIssue.workflowState,
                        },
                        runLimitPolicy: {
                            config: input.context.config,
                            workflowLabels: input.workflowLabels,
                        },
                        responseLanguage: input.workerExecution.responseLanguage,
                    }),
                }
                : {}),
            dependencyChanges: [
                {
                    kind: "add_blocking_dependency",
                    issue: { kind: "issue", issueId: input.workerExecution.issue.id },
                    blockedBy: { kind: "issue", issueId: selectedIssue.id },
                },
            ],
        };
    }
    await ensureParentBranchRemote();
    return buildNewUnplannedChildIssuePlan({
        context: input.context,
        workflowLabels: input.workflowLabels,
        workerExecution: input.workerExecution,
        continuation: input.continuation,
        childBranchTarget,
        childBaseBranch,
    });
}
async function buildNewUnplannedChildIssuePlan(input) {
    const assignee = await (0, utils_1.resolveSingleServiceAssignee)(input.context.currentUserLogin, input.context.ticketTracker);
    const responseLanguage = input.workerExecution.responseLanguage;
    return {
        kind: "create",
        publicationPlan: (0, issue_creation_publication_1.buildCreateIssuePublicationPlan)({
            source: "unplanned",
            branchTarget: input.childBranchTarget,
            relationship: {
                kind: "blocking_parent",
                parentIssueId: input.workerExecution.issue.id,
            },
            title: buildUnplannedChildIssueTitle(input.workerExecution.issue.displayId, input.continuation.title),
            body: buildUnplannedChildIssueBody({
                responseLanguage,
                workerExecution: input.workerExecution,
                continuationReason: input.continuation.reason,
            }),
            trackerFields: {
                assignees: [assignee],
                labelNames: [input.workflowLabels.aiReady],
                ...(input.workerExecution.issue.milestoneRef !== undefined
                    ? { milestoneRef: input.workerExecution.issue.milestoneRef }
                    : {}),
                ...(input.workerExecution.issue.projectRef !== undefined
                    ? { projectRef: input.workerExecution.issue.projectRef }
                    : {}),
            },
            workflowState: buildUnplannedChildWorkflowState({
                parentIssue: input.workerExecution.issue,
                parentWorkflowState: input.workerExecution.workflowState,
                baseBranch: input.childBaseBranch,
                objective: input.continuation.title,
            }),
            runLimitPolicy: {
                config: input.context.config,
                workflowLabels: input.workflowLabels,
            },
            dependencyChanges: [
                {
                    kind: "add_blocking_dependency",
                    issue: { kind: "issue", issueId: input.workerExecution.issue.id },
                    blockedBy: { kind: "target" },
                },
            ],
            newIssueFailurePolicy: "close_created_internal_issue",
            responseLanguage,
        }),
    };
}
async function selectReusableIssue(input) {
    if (input.openIssues.length === 0) {
        return null;
    }
    const selectionInput = {
        originIssue: {
            id: input.workerExecution.issue.id,
            displayId: input.workerExecution.issue.displayId,
            title: input.workerExecution.issue.baseTitle,
            body: input.workerExecution.issue.body,
            url: input.workerExecution.issue.url,
        },
        continuation: input.continuation,
        openIssues: input.openIssues.map((issue) => ({
            id: issue.id,
            displayId: issue.displayId,
            title: issue.baseTitle,
            body: issue.body,
            status: issue.workflowState.status,
            url: issue.url,
        })),
    };
    try {
        const result = await input.context.worker.runContinuationIssueSelection({
            input: selectionInput,
            workspacePath: input.workspacePath,
            repositoryRoot: input.workspacePath,
        });
        if (result.kind !== "success") {
            return null;
        }
        if (result.launchResult.decision.action !== "reuse_existing_issue" ||
            result.launchResult.decision.targetIssueId === undefined) {
            return null;
        }
        const targetIssue = input.openIssues.find((issue) => issue.id === result.launchResult.decision.targetIssueId) ?? null;
        return isAcceptedOpenIssue(input.workerExecution.issue.id, targetIssue)
            ? targetIssue
            : null;
    }
    catch {
        return null;
    }
}
function narrowReusableIssues(openIssues, originIssue, continuationReason, workflowStateByIssueId) {
    const candidates = openIssues.filter((issue) => isAcceptedReusableIssue(originIssue.id, issue, workflowStateByIssueId));
    if (candidates.length <= 20) {
        return [...candidates].sort(utils_1.compareIssuesDeterministically);
    }
    const reasonText = normalizeSearchText(continuationReason ?? "");
    const originTitle = normalizeSearchText(originIssue.baseTitle);
    return [...candidates]
        .map((issue) => ({
        issue,
        score: scoreReusableIssue(issue, reasonText, originTitle),
    }))
        .sort((left, right) => {
        if (right.score !== left.score) {
            return right.score - left.score;
        }
        return (0, utils_1.compareIssuesDeterministically)(left.issue, right.issue);
    })
        .slice(0, 20)
        .map((entry) => entry.issue);
}
function scoreReusableIssue(issue, continuationReason, originTitle) {
    const haystack = normalizeSearchText([issue.baseTitle, issue.body].filter(Boolean).join("\n"));
    let score = 0;
    for (const token of collectAsciiTokens(continuationReason)) {
        if (haystack.includes(token)) {
            score += 5;
        }
    }
    for (const token of collectAsciiTokens(originTitle)) {
        if (haystack.includes(token)) {
            score += 2;
        }
    }
    if (continuationReason.length > 0 &&
        hasSharedCjkSubstring(haystack, continuationReason)) {
        score += 3;
    }
    if (originTitle.length > 0 && hasSharedCjkSubstring(haystack, originTitle)) {
        score += 1;
    }
    return score;
}
function normalizeSearchText(value) {
    return value.trim().toLowerCase();
}
function collectAsciiTokens(value) {
    return Array.from(new Set(value.match(/[a-z0-9_/-]{3,}/g) ?? []));
}
function hasSharedCjkSubstring(haystack, needle) {
    const compactNeedle = needle.replace(/\s+/g, "");
    for (let index = 0; index < compactNeedle.length - 1; index += 1) {
        const chunk = compactNeedle.slice(index, index + 2);
        if (/[\u3400-\u9fff\u3040-\u30ff]/u.test(chunk) &&
            haystack.includes(chunk)) {
            return true;
        }
    }
    return false;
}
function isAcceptedReusableIssue(originIssueId, issue, workflowStateByIssueId) {
    return (isAcceptedOpenIssue(originIssueId, issue) &&
        !isManagedChildWorkflowState(workflowStateByIssueId.get(issue.id) ?? null));
}
function isAcceptedOpenIssue(originIssueId, issue) {
    return (issue !== null &&
        issue !== undefined &&
        issue.id !== originIssueId &&
        issue.isOpen &&
        !issue.workflowState.rules.includes(types_1.WORKFLOW_RULES.aiDisabled));
}
function findOpenUnplannedChildIssue(input) {
    const liveBlockerIds = new Set(input.liveBlockers
        .filter((blocker) => blocker.isOpen)
        .map((blocker) => blocker.id));
    const matches = input.openIssues.filter((issue) => isAcceptedOpenIssue(input.originIssue.id, issue) &&
        isUnplannedChildWorkflowStateForParent(input.workflowStateByIssueId.get(issue.id) ?? null, input.originIssue.displayId));
    if (matches.length === 0) {
        return null;
    }
    const issue = [...matches].sort((left, right) => {
        const leftLinked = liveBlockerIds.has(left.id) ? 0 : 1;
        const rightLinked = liveBlockerIds.has(right.id) ? 0 : 1;
        if (leftLinked !== rightLinked) {
            return leftLinked - rightLinked;
        }
        return (0, utils_1.compareIssuesDeterministically)(left, right);
    })[0] ?? null;
    return issue === null
        ? null
        : {
            issue,
            linked: liveBlockerIds.has(issue.id),
        };
}
function buildUnplannedChildIssueTitle(originDisplayId, continuationTitle) {
    return (0, child_issue_title_1.appendParentIssueTitleSuffixWithinLimit)(continuationTitle, originDisplayId, 180);
}
function buildUnplannedChildIssueBody(input) {
    const copy = resolveUnplannedChildCopy(input.responseLanguage);
    const originIssue = input.workerExecution.issue;
    const lines = [
        `## ${copy.summaryHeading}`,
        `- ${copy.originIssueLabel}: ${originIssue.displayId}`,
        `- ${copy.originIssueUrlLabel}: ${originIssue.url}`,
        `- ${copy.originIssueTitleLabel}: ${originIssue.baseTitle}`,
        `- ${copy.continuationReasonLabel}: ${input.continuationReason}`,
        "",
        `## ${copy.workHeading}`,
        `- ${copy.workRuleOne}`,
        `- ${copy.workRuleTwo}`,
        `- ${copy.workRuleThree}`,
        "",
        `## ${copy.contextHeading}`,
        `- ${copy.workflowLabel}: ${input.workerExecution.workflowTemplateName ?? copy.none}`,
        `- ${copy.runModeLabel}: ${input.workerExecution.runMode}`,
        `- ${copy.branchLabel}: ${input.workerExecution.branchName}`,
        `- ${copy.pullRequestLabel}: ${input.workerExecution.pullRequestUrl ?? copy.none}`,
    ];
    if ((0, resolution_issue_1.isSkillResolutionContinuationReason)(input.continuationReason)) {
        lines.push("", (0, resolution_issue_1.buildSkillResolutionProcessSection)({
            locale: input.responseLanguage,
        }), "", (0, resolution_issue_1.buildSkillResolutionDoneWhenSection)({
            locale: input.responseLanguage,
            includeParentUnblock: true,
        }));
    }
    return lines.join("\n");
}
function resolveUnplannedChildBranchTarget(continuation) {
    return (0, resolution_issue_1.isSkillResolutionContinuationReason)(continuation.reason)
        ? "effective_base_branch"
        : "parent_issue_branch";
}
function buildUnplannedChildWorkflowState(input) {
    return (0, state_1.buildPersistedWorkflowState)({
        workflowSurface: "unplanned",
        workflowTemplates: input.parentWorkflowState.workflowTemplates,
        baseBranch: input.baseBranch,
        parentLink: {
            parentRef: input.parentIssue.displayId,
            childKey: child_workflow_state_1.UNPLANNED_CHILD_KEY,
        },
        executionContract: (0, state_1.createDefaultWorkflowExecutionContract)(input.objective),
        plan: null,
        consecutiveAiRuns: 0,
        totalAiRunHours: 0,
        lastScannedCommentCursor: null,
        ...(input.parentWorkflowState.configOverrides !== undefined
            ? { configOverrides: input.parentWorkflowState.configOverrides }
            : {}),
    });
}
function requireUnplannedParentWorkflowState(workerExecution) {
    if (workerExecution.workflowState === null) {
        throw new Error("spawn_child_issue requires the parent canonical workflow state.");
    }
    return workerExecution.workflowState;
}
function readSpawnChildContinuation(workerExecution) {
    if (workerExecution.continuation?.strategy !== "spawn_child_issue") {
        throw new Error("spawn_child_issue requires a spawn child continuation.");
    }
    return workerExecution.continuation;
}
function isManagedChildWorkflowState(workflowState) {
    return workflowState?.parentLink !== undefined;
}
function isUnplannedChildWorkflowStateForParent(workflowState, originDisplayId) {
    return (workflowState?.workflowSurface === "unplanned" &&
        workflowState.parentLink?.childKey === child_workflow_state_1.UNPLANNED_CHILD_KEY &&
        (0, issue_ref_1.workflowIssueRefsMatch)(workflowState.parentLink.parentRef, originDisplayId));
}
function resolveUnplannedChildCopy(responseLanguage) {
    return {
        none: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.none"),
        summaryHeading: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.summaryHeading"),
        workHeading: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.workHeading"),
        contextHeading: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.contextHeading"),
        originIssueLabel: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.originIssueLabel"),
        originIssueUrlLabel: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.originIssueUrlLabel"),
        originIssueTitleLabel: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.originIssueTitleLabel"),
        continuationReasonLabel: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.continuationReasonLabel"),
        workflowLabel: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.workflowLabel"),
        runModeLabel: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.runModeLabel"),
        branchLabel: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.branchLabel"),
        pullRequestLabel: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.pullRequestLabel"),
        workRuleOne: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.workRuleOne"),
        workRuleTwo: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.workRuleTwo"),
        workRuleThree: (0, catalog_1.translate)(responseLanguage, "run.spawnChild.unplanned.workRuleThree"),
    };
}
