/**
 * Release guards - epic-completeness and double-listing checks.
 *
 * Pre-release validation to ensure all tasks in an epic are accounted for
 * and no task appears in multiple releases.
 *
 * @task T4454
 * @epic T4454
 */
import { getAccessor } from '../store/data-accessor.js';
/**
 * Walk parent chain to find the epic ancestor of a task.
 */
function findEpicAncestor(taskId, tasksById) {
    const task = tasksById.get(taskId);
    if (!task)
        return null;
    if (task.type === 'epic')
        return taskId;
    // Walk up parent chain (max 3 levels)
    let current = task;
    for (let depth = 0; depth < 3; depth++) {
        if (!current.parentId)
            return null;
        const parent = tasksById.get(current.parentId);
        if (!parent)
            return null;
        if (parent.type === 'epic')
            return parent.id;
        current = parent;
    }
    return null;
}
/**
 * Check epic completeness for a set of release task IDs.
 * Verifies all children of each referenced epic are included.
 */
export async function checkEpicCompleteness(releaseTaskIds, cwd, accessor) {
    const acc = accessor ?? (await getAccessor(cwd));
    const { tasks: allTasks } = await acc.queryTasks({});
    if (!allTasks?.length) {
        return { hasIncomplete: false, epics: [], orphanTasks: [] };
    }
    const tasksById = new Map();
    for (const task of allTasks) {
        tasksById.set(task.id, task);
    }
    const releaseSet = new Set(releaseTaskIds);
    // Map each release task to its epic
    const taskToEpic = new Map();
    for (const taskId of releaseTaskIds) {
        taskToEpic.set(taskId, findEpicAncestor(taskId, tasksById));
    }
    // Find orphan tasks (no epic)
    const orphanTasks = releaseTaskIds.filter((id) => !taskToEpic.get(id));
    // Group by epic
    const byEpic = new Map();
    for (const [taskId, epicId] of taskToEpic) {
        if (!epicId)
            continue;
        if (!byEpic.has(epicId))
            byEpic.set(epicId, []);
        byEpic.get(epicId).push(taskId);
    }
    // Check each epic for completeness
    const epics = [];
    let hasIncomplete = false;
    for (const [epicId, includedTasks] of byEpic) {
        const epic = tasksById.get(epicId);
        if (!epic)
            continue;
        // Find all children of this epic
        const allChildren = allTasks.filter((t) => t.parentId === epicId && t.id !== epicId);
        const includedSet = new Set(includedTasks);
        // Build set of parent task IDs (tasks with children) — these are filtered out
        // by prepareRelease and should not be flagged as missing.
        const parentIds = new Set(allTasks.filter((t) => t.parentId).map((t) => t.parentId));
        // Only flag done leaf tasks not included in the release — pending/active/blocked
        // tasks are future work, and parent tasks are structural (excluded by prepareRelease).
        const missingChildren = allChildren
            .filter((t) => t.status === 'done' &&
            !parentIds.has(t.id) &&
            !includedSet.has(t.id) &&
            !releaseSet.has(t.id))
            .map((t) => ({ id: t.id, title: t.title, status: t.status }));
        if (missingChildren.length > 0)
            hasIncomplete = true;
        epics.push({
            epicId,
            epicTitle: epic.title,
            totalChildren: allChildren.length,
            includedChildren: includedTasks.length,
            missingChildren,
        });
    }
    return { hasIncomplete, epics, orphanTasks };
}
/**
 * Check if any tasks are listed in multiple releases.
 */
export function checkDoubleListing(releaseTaskIds, existingReleases) {
    const duplicates = [];
    for (const taskId of releaseTaskIds) {
        const inReleases = existingReleases
            .filter((r) => r.tasks.includes(taskId))
            .map((r) => r.version);
        if (inReleases.length > 0) {
            duplicates.push({ taskId, releases: inReleases });
        }
    }
    return {
        hasDoubleListing: duplicates.length > 0,
        duplicates,
    };
}
//# sourceMappingURL=guards.js.map