/**
 * Composite planning view - aggregates multiple task queries for planning.
 * @task T4914
 * @epic T4454
 */
import { getAccessor } from '../store/data-accessor.js';
import { depsReady } from './deps-ready.js';
const PRIORITY_SCORE = {
    critical: 100,
    high: 75,
    medium: 50,
    low: 25,
};
async function loadAllTasks(projectRoot) {
    const accessor = await getAccessor(projectRoot);
    const { tasks } = await accessor.queryTasks({});
    return tasks;
}
/**
 * Calculate leverage score for a task based on how many other tasks depend on it.
 */
function calculateLeverage(taskId, taskMap) {
    let leverage = 0;
    for (const task of taskMap.values()) {
        if (task.depends?.includes(taskId)) {
            leverage++;
        }
    }
    return leverage;
}
/**
 * Find the epic ID for a task (parent epic, not immediate parent).
 */
function findEpicId(task, taskMap) {
    let current = task;
    const visited = new Set();
    while (current?.parentId && !visited.has(current.id)) {
        visited.add(current.id);
        const parent = taskMap.get(current.parentId);
        if (!parent)
            break;
        if (parent.type === 'epic') {
            return parent.id;
        }
        current = parent;
    }
    // If task itself is an epic
    if (task.type === 'epic') {
        return task.id;
    }
    // Return parentId as fallback, or task's own id
    return task.parentId ?? task.id;
}
/**
 * Get current phase from tasks.
 */
async function getCurrentPhase(projectRoot) {
    const accessor = await getAccessor(projectRoot);
    const meta = await accessor.getMetaValue('project_meta');
    return meta?.currentPhase ?? null;
}
/**
 * Calculate completion percentage for an epic.
 */
function calculateEpicCompletion(epicId, taskMap) {
    let totalTasks = 0;
    let completedTasks = 0;
    let activeTasks = 0;
    // Count direct children and their descendants
    const collectTasks = (parentId) => {
        for (const task of taskMap.values()) {
            if (task.parentId === parentId) {
                totalTasks++;
                if (task.status === 'done' || task.status === 'cancelled') {
                    completedTasks++;
                }
                if (task.status === 'active') {
                    activeTasks++;
                }
                // Recursively count descendants
                collectTasks(task.id);
            }
        }
    };
    collectTasks(epicId);
    const completionPercent = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
    return { activeTasks, completionPercent };
}
/**
 * Build composite planning view.
 * @task T4914
 */
export async function coreTaskPlan(projectRoot) {
    const allTasks = await loadAllTasks(projectRoot);
    const taskMap = new Map(allTasks.map((t) => [t.id, t]));
    const currentPhase = await getCurrentPhase(projectRoot);
    // ========================================================================
    // 1. In-Progress Epics (epics with active status)
    // ========================================================================
    const inProgressEpics = [];
    for (const task of allTasks) {
        if (task.type === 'epic' && task.status === 'active') {
            const { activeTasks, completionPercent } = calculateEpicCompletion(task.id, taskMap);
            inProgressEpics.push({
                epicId: task.id,
                epicTitle: task.title,
                activeTasks,
                completionPercent,
            });
        }
    }
    // ========================================================================
    // 2. Ready Tasks (pending tasks with deps satisfied)
    // ========================================================================
    const readyTasks = [];
    const pendingTasks = allTasks.filter((t) => t.status === 'pending');
    for (const task of pendingTasks) {
        if (depsReady(task.depends, taskMap)) {
            const leverage = calculateLeverage(task.id, taskMap);
            const epicId = findEpicId(task, taskMap);
            // Calculate score using same logic as tasks.next
            const reasons = [];
            let score = 0;
            score += PRIORITY_SCORE[task.priority] ?? 50;
            reasons.push(`priority: ${task.priority} (+${PRIORITY_SCORE[task.priority] ?? 50})`);
            if (currentPhase && task.phase === currentPhase) {
                score += 20;
                reasons.push(`phase alignment: ${currentPhase} (+20)`);
            }
            score += 10;
            reasons.push('all dependencies satisfied (+10)');
            if (task.createdAt) {
                const ageMs = Date.now() - new Date(task.createdAt).getTime();
                const ageDays = ageMs / (1000 * 60 * 60 * 24);
                if (ageDays > 7) {
                    const ageBonus = Math.min(15, Math.floor(ageDays / 7));
                    score += ageBonus;
                    reasons.push(`age: ${Math.floor(ageDays)} days (+${ageBonus})`);
                }
            }
            // Add leverage bonus to score
            if (leverage > 0) {
                score += leverage * 5;
                reasons.push(`leverage: unblocks ${leverage} task(s) (+${leverage * 5})`);
            }
            readyTasks.push({
                id: task.id,
                title: task.title,
                priority: task.priority,
                epicId,
                leverage,
                score,
                reasons,
            });
        }
    }
    // Sort by score descending
    readyTasks.sort((a, b) => b.score - a.score);
    // ========================================================================
    // 3. Blocked Tasks
    // ========================================================================
    const blockedTasks = [];
    // Tasks with blocked status
    for (const task of allTasks) {
        if (task.status === 'blocked') {
            const blockedBy = [];
            if (task.blockedBy) {
                blockedBy.push(task.blockedBy);
            }
            if (task.depends) {
                for (const depId of task.depends) {
                    const dep = taskMap.get(depId);
                    if (dep && dep.status !== 'done' && dep.status !== 'cancelled') {
                        blockedBy.push(depId);
                    }
                }
            }
            // Calculate how many tasks this blocks
            let blocksCount = 0;
            for (const t of allTasks) {
                if (t.depends?.includes(task.id)) {
                    blocksCount++;
                }
            }
            blockedTasks.push({
                id: task.id,
                title: task.title,
                blockedBy,
                blocksCount,
            });
        }
    }
    // Tasks with unresolved dependencies (pending + has incomplete deps)
    for (const task of allTasks) {
        if (task.status === 'pending' && task.depends && task.depends.length > 0) {
            const hasUnresolvedDeps = task.depends.some((depId) => {
                const dep = taskMap.get(depId);
                return dep && dep.status !== 'done' && dep.status !== 'cancelled';
            });
            if (hasUnresolvedDeps && !blockedTasks.some((b) => b.id === task.id)) {
                const blockedBy = task.depends.filter((depId) => {
                    const dep = taskMap.get(depId);
                    return dep && dep.status !== 'done' && dep.status !== 'cancelled';
                });
                // Calculate how many tasks this blocks
                let blocksCount = 0;
                for (const t of allTasks) {
                    if (t.depends?.includes(task.id)) {
                        blocksCount++;
                    }
                }
                blockedTasks.push({
                    id: task.id,
                    title: task.title,
                    blockedBy,
                    blocksCount,
                });
            }
        }
    }
    // ========================================================================
    // 4. Open Bugs (origin:bug-report OR label:bug)
    // ========================================================================
    const openBugs = [];
    for (const task of allTasks) {
        const isBug = task.origin === 'bug-report' || task.labels?.includes('bug');
        const isOpen = task.status !== 'done' && task.status !== 'cancelled';
        if (isBug && isOpen) {
            const epicId = findEpicId(task, taskMap);
            openBugs.push({
                id: task.id,
                title: task.title,
                priority: task.priority,
                epicId,
            });
        }
    }
    // ========================================================================
    // 5. Metrics
    // ========================================================================
    const epics = allTasks.filter((t) => t.type === 'epic');
    const activeEpics = epics.filter((t) => t.status === 'active').length;
    const actionable = allTasks.filter((t) => (t.status === 'pending' || t.status === 'active') && !blockedTasks.some((b) => b.id === t.id)).length;
    const blocked = blockedTasks.length;
    // Calculate average leverage across all tasks
    let totalLeverage = 0;
    for (const task of allTasks) {
        totalLeverage += calculateLeverage(task.id, taskMap);
    }
    const avgLeverage = allTasks.length > 0 ? Math.round((totalLeverage / allTasks.length) * 100) / 100 : 0;
    const metrics = {
        totalEpics: epics.length,
        activeEpics,
        totalTasks: allTasks.length,
        actionable,
        blocked,
        openBugs: openBugs.length,
        avgLeverage,
    };
    return {
        inProgress: inProgressEpics,
        ready: readyTasks,
        blocked: blockedTasks,
        openBugs,
        metrics,
    };
}
//# sourceMappingURL=plan.js.map