"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildWorkflowIssueGraph = buildWorkflowIssueGraph;
exports.compareWorkflowFrontierPriority = compareWorkflowFrontierPriority;
const types_1 = require("../platform/workflow-state/types");
const issue_ref_1 = require("../workflows/issue-ref");
const shared_1 = require("./run-once-cycle/shared");
const workflow_structure_1 = require("./workflow-structure");
function buildWorkflowIssueGraph(entries) {
    const issueRefIndex = buildWorkflowIssueRefIndex(entries);
    const nodeByIssueId = new Map();
    for (const entry of entries) {
        nodeByIssueId.set(entry.issue.id, {
            ...entry,
            parentRef: entry.workflowState?.parentLink?.parentRef ?? null,
            parentIssueId: null,
            childIssueIds: [],
            isOrphan: false,
        });
    }
    for (const node of nodeByIssueId.values()) {
        const parentIssueId = resolveParentIssueId(node.parentRef, issueRefIndex);
        if (parentIssueId === null) {
            node.parentIssueId = null;
            node.isOrphan = node.parentRef !== null;
            continue;
        }
        node.parentIssueId = parentIssueId;
        const parent = nodeByIssueId.get(parentIssueId);
        if (parent === undefined) {
            node.parentIssueId = null;
            node.isOrphan = true;
            continue;
        }
        parent.childIssueIds.push(node.issue.id);
    }
    const derivedNodeByIssueId = new Map();
    const cache = new Map();
    for (const node of nodeByIssueId.values()) {
        const derived = deriveWorkflowIssueNode(node, nodeByIssueId, cache);
        derivedNodeByIssueId.set(node.issue.id, derived);
    }
    return {
        nodeByIssueId: derivedNodeByIssueId,
        orphanIssueIds: [...derivedNodeByIssueId.values()]
            .filter((node) => node.isOrphan)
            .map((node) => node.issue.id)
            .sort((left, right) => left.localeCompare(right)),
    };
}
function compareWorkflowFrontierPriority(left, right, graph) {
    const leftBand = resolveWorkflowFrontierBand(graph.nodeByIssueId.get(left.issueId));
    const rightBand = resolveWorkflowFrontierBand(graph.nodeByIssueId.get(right.issueId));
    if (leftBand !== rightBand) {
        return leftBand - rightBand;
    }
    return (0, shared_1.compareExecutionPriority)(left, right);
}
function resolveWorkflowFrontierBand(node) {
    if (node === undefined) {
        return 4;
    }
    if (node.structuralViolation !== null) {
        return 4;
    }
    if (node.isOrphan) {
        return 4;
    }
    if (node.isExecutionLeaf) {
        return 0;
    }
    if (node.controllerState === "stale") {
        return 2;
    }
    return 3;
}
function deriveWorkflowIssueNode(node, nodeByIssueId, cache, activePath = new Set()) {
    const cached = cache.get(node.issue.id);
    if (cached !== undefined) {
        return {
            ...node,
            childIssueIds: [...node.childIssueIds].sort((left, right) => left.localeCompare(right)),
            isExecutionLeaf: resolveIsExecutionLeaf(node),
            structuralViolation: resolveStructuralViolation(node),
            controllerState: resolveControllerState(node, cached.openDescendantCount),
            ...cached,
        };
    }
    if (activePath.has(node.issue.id)) {
        const cycleFallback = {
            rollupStatus: node.issue.workflowState.status,
            subtreeIssueCount: 1,
            openDescendantCount: 0,
        };
        cache.set(node.issue.id, cycleFallback);
        return {
            ...node,
            childIssueIds: [...node.childIssueIds].sort((left, right) => left.localeCompare(right)),
            isExecutionLeaf: resolveIsExecutionLeaf(node),
            structuralViolation: resolveStructuralViolation(node),
            controllerState: resolveControllerState(node, cycleFallback.openDescendantCount),
            ...cycleFallback,
        };
    }
    activePath.add(node.issue.id);
    const childNodes = node.childIssueIds
        .map((childIssueId) => {
        const child = nodeByIssueId.get(childIssueId);
        return child === undefined
            ? null
            : deriveWorkflowIssueNode(child, nodeByIssueId, cache, activePath);
    })
        .filter((value) => value !== null);
    activePath.delete(node.issue.id);
    const rollupStatus = deriveWorkflowRollupStatus(node.issue.workflowState.status, childNodes.map((child) => child.rollupStatus));
    const subtreeIssueCount = 1 + childNodes.reduce((count, child) => count + child.subtreeIssueCount, 0);
    const openDescendantCount = childNodes.reduce((count, child) => {
        return count + (child.issue.isOpen ? 1 : 0) + child.openDescendantCount;
    }, 0);
    const derived = {
        rollupStatus,
        subtreeIssueCount,
        openDescendantCount,
    };
    cache.set(node.issue.id, derived);
    return {
        ...node,
        childIssueIds: [...node.childIssueIds].sort((left, right) => left.localeCompare(right)),
        isExecutionLeaf: resolveIsExecutionLeaf(node),
        structuralViolation: resolveStructuralViolation(node),
        controllerState: resolveControllerState(node, openDescendantCount),
        ...derived,
    };
}
function buildWorkflowIssueRefIndex(entries) {
    const index = new Map();
    for (const entry of entries) {
        for (const candidate of [
            entry.issue.id,
            entry.issue.displayId,
            (0, issue_ref_1.normalizeWorkflowIssueDisplayRef)(entry.issue.displayId),
        ]) {
            const normalized = (0, issue_ref_1.normalizeWorkflowIssueGraphLookupKey)(candidate);
            if (normalized !== null && !index.has(normalized)) {
                index.set(normalized, entry.issue.id);
            }
        }
    }
    return index;
}
function resolveParentIssueId(parentRef, issueRefIndex) {
    const normalized = (0, issue_ref_1.normalizeWorkflowIssueGraphLookupKey)(parentRef);
    if (normalized === null) {
        return null;
    }
    return issueRefIndex.get(normalized) ?? null;
}
function deriveWorkflowRollupStatus(selfStatus, childStatuses) {
    const allStatuses = [selfStatus, ...childStatuses];
    return ([...allStatuses].sort((left, right) => resolveWorkflowStatusPriority(right) -
        resolveWorkflowStatusPriority(left))[0] ?? selfStatus);
}
function resolveWorkflowStatusPriority(status) {
    switch (status) {
        case types_1.WORKFLOW_STATUSES.aiRunning:
            return 4;
        case types_1.WORKFLOW_STATUSES.aiNeedsHuman:
            return 3;
        case types_1.WORKFLOW_STATUSES.aiReady:
            return 2;
        case types_1.WORKFLOW_STATUSES.aiWorkingInPr:
            return 1;
        default:
            return 0;
    }
}
function resolveControllerState(node, openDescendantCount) {
    if (!isWorkflowGraphParent(node)) {
        return "none";
    }
    return openDescendantCount === 0 ? "stale" : "ready";
}
function resolveIsExecutionLeaf(node) {
    return !isWorkflowGraphParent(node);
}
function resolveStructuralViolation(node) {
    if (node.childIssueIds.length > 0 &&
        !(0, workflow_structure_1.isWorkflowParentState)(node.workflowState)) {
        return "non_leaf_execution";
    }
    return null;
}
function isWorkflowGraphParent(node) {
    return (node.childIssueIds.length > 0 || (0, workflow_structure_1.isWorkflowParentState)(node.workflowState));
}
