/**
 * Session briefing computation.
 *
 * Aggregates session-start context from multiple sources for quick agent
 * orientation. This is the READ side of the handoff/briefing pair.
 *
 * Data sources:
 * - Last session handoff (session.handoff)
 * - Current focus (tasks.current)
 * - Top-N next tasks (tasks.next leverage-scored)
 * - Open bugs (tasks with origin:bug-report or label:bug)
 * - Blocked tasks (tasks.blockers)
 * - Active epics status (tasks.tree filtered)
 * - Pipeline stage data (from T4912)
 *
 * @task T4916
 * @epic T4914
 */
import { getAccessor } from '../store/data-accessor.js';
import { depsReady } from '../tasks/deps-ready.js';
import { getLastHandoff } from './handoff.js';
const PRIORITY_SCORE = {
    critical: 100,
    high: 75,
    medium: 50,
    low: 25,
};
/**
 * Compute the complete session briefing.
 * Aggregates data from all 6+ sources.
 */
export async function computeBriefing(projectRoot, options = {}) {
    const accessor = await getAccessor(projectRoot);
    const { tasks } = await accessor.queryTasks({});
    const focus = (await accessor.getMetaValue('focus_state'));
    // Build task map for quick lookups
    const taskMap = new Map(tasks.map((t) => [t.id, t]));
    // Determine scope
    const scopeFilter = await parseScope(options.scope, accessor);
    // Compute in-scope task IDs (undefined = all tasks in scope)
    const scopeTaskIds = getScopeTaskIdSet(scopeFilter, tasks);
    // 1. Last session handoff
    const lastSession = await computeLastSession(projectRoot, scopeFilter);
    // 2. Current active task
    const currentTaskInfo = computeCurrentTask(focus, taskMap);
    // 3. Next tasks (leverage-scored)
    const nextTasks = computeNextTasks(tasks, taskMap, focus, {
        maxTasks: options.maxNextTasks ?? 5,
        scopeTaskIds,
    });
    // 4. Open bugs
    const openBugs = computeOpenBugs(tasks, taskMap, {
        maxBugs: options.maxBugs ?? 10,
        scopeTaskIds,
    });
    // 5. Blocked tasks
    const blockedTasks = computeBlockedTasks(tasks, taskMap, {
        maxBlocked: options.maxBlocked ?? 10,
        scopeTaskIds,
    });
    // 6. Active epics
    const activeEpics = computeActiveEpics(tasks, taskMap, {
        maxEpics: options.maxEpics ?? 5,
        scopeTaskIds,
    });
    // 7. Pipeline stage (optional - may not be available)
    const pipelineStage = await computePipelineStage(focus);
    // 8. Brain memory context (optional, best-effort)
    let memoryContext;
    try {
        const { getSessionMemoryContext } = await import('../memory/session-memory.js');
        memoryContext = await getSessionMemoryContext(projectRoot, scopeFilter);
    }
    catch {
        // Brain memory not available -- proceed without
    }
    // Compute warnings
    const warnings = [];
    if (currentTaskInfo?.blockedBy?.length) {
        warnings.push(`Focused task ${currentTaskInfo.id} is blocked by: ${currentTaskInfo.blockedBy.join(', ')}`);
    }
    return {
        lastSession,
        currentTask: currentTaskInfo,
        nextTasks,
        openBugs,
        blockedTasks,
        activeEpics,
        ...(pipelineStage && { pipelineStage }),
        ...(warnings.length > 0 && { warnings }),
        ...(memoryContext && { memoryContext }),
    };
}
/**
 * Parse scope string into filter config.
 * Uses getActiveSession() to auto-detect scope when no explicit scope is provided.
 */
async function parseScope(scopeStr, accessor) {
    if (!scopeStr) {
        // Auto-detect from active session
        const activeSession = await accessor.getActiveSession();
        if (activeSession?.scope?.type === 'epic') {
            return { type: 'epic', epicId: activeSession.scope.rootTaskId };
        }
        if (activeSession?.scope?.type === 'global') {
            return { type: 'global' };
        }
        return undefined;
    }
    if (scopeStr === 'global') {
        return { type: 'global' };
    }
    const match = scopeStr.match(/^epic:(T\d+)$/);
    if (match) {
        return { type: 'epic', epicId: match[1] };
    }
    return undefined;
}
/**
 * Compute the set of in-scope task IDs for briefing filtering.
 * Returns undefined for global/unscoped (meaning all tasks are in scope).
 */
function getScopeTaskIdSet(scopeFilter, tasks) {
    if (!scopeFilter || scopeFilter.type === 'global') {
        return undefined; // All tasks in scope
    }
    const rootId = scopeFilter.epicId;
    if (!rootId)
        return undefined;
    const taskIds = new Set();
    const addDescendants = (taskId) => {
        taskIds.add(taskId);
        for (const t of tasks) {
            if (t.parentId === taskId) {
                addDescendants(t.id);
            }
        }
    };
    addDescendants(rootId);
    return taskIds;
}
/**
 * Compute last session info with handoff data.
 */
async function computeLastSession(projectRoot, scopeFilter) {
    try {
        const scope = scopeFilter ? { type: scopeFilter.type, epicId: scopeFilter.epicId } : undefined;
        const handoffResult = await getLastHandoff(projectRoot, scope);
        if (!handoffResult)
            return null;
        const { sessionId, handoff } = handoffResult;
        // Load sessions to get endedAt
        const accessor = await getAccessor(projectRoot);
        const allSessions = await accessor.loadSessions();
        const session = allSessions.find((s) => s.id === sessionId);
        if (!session || !session.endedAt)
            return null;
        // Calculate duration if startedAt is available
        let duration = 0;
        if (session.startedAt) {
            duration = Math.round((new Date(session.endedAt).getTime() - new Date(session.startedAt).getTime()) / 60000);
        }
        return {
            endedAt: session.endedAt,
            duration,
            handoff,
        };
    }
    catch {
        return null;
    }
}
/**
 * Compute current active task from task file.
 */
function computeCurrentTask(focus, taskMap) {
    const focusTaskId = focus?.currentTask;
    if (!focusTaskId)
        return null;
    const task = taskMap.get(focusTaskId);
    if (!task)
        return null;
    const info = {
        id: task.id,
        title: task.title,
        status: task.status,
    };
    // Check for unresolved dependencies on the focused task
    if (task.depends?.length) {
        const unresolved = task.depends.filter((depId) => {
            const dep = taskMap.get(depId);
            return dep && dep.status !== 'done' && dep.status !== 'cancelled';
        });
        if (unresolved.length > 0) {
            info.blockedBy = unresolved;
        }
    }
    return info;
}
/**
 * Compute leverage for a task.
 */
function calculateLeverage(taskId, taskMap) {
    let leverage = 0;
    for (const task of taskMap.values()) {
        const t = task;
        if (t.depends?.includes(taskId)) {
            leverage++;
        }
    }
    return leverage;
}
/**
 * Compute next tasks sorted by leverage and score.
 */
function computeNextTasks(tasks, taskMap, focus, options) {
    const pendingTasks = tasks.filter((t) => {
        const task = t;
        return (task.status === 'pending' && (!options.scopeTaskIds || options.scopeTaskIds.has(task.id)));
    });
    const scored = [];
    const currentPhase = focus?.currentPhase;
    for (const task of pendingTasks) {
        const t = task;
        if (!depsReady(t.depends, taskMap))
            continue;
        const leverage = calculateLeverage(t.id, taskMap);
        let score = PRIORITY_SCORE[t.priority || 'medium'] ?? 50;
        // Phase alignment bonus
        if (currentPhase && t.phase === currentPhase) {
            score += 20;
        }
        // Dependencies satisfied bonus
        if (t.depends && t.depends.length > 0) {
            score += 10;
        }
        // Age bonus
        if (t.createdAt) {
            const ageMs = Date.now() - new Date(t.createdAt).getTime();
            const ageDays = ageMs / (1000 * 60 * 60 * 24);
            if (ageDays > 7) {
                score += Math.min(15, Math.floor(ageDays / 7));
            }
        }
        // Leverage bonus
        if (leverage > 0) {
            score += leverage * 5;
        }
        scored.push({ id: t.id, title: t.title, leverage, score });
    }
    // Sort by score descending
    scored.sort((a, b) => b.score - a.score);
    return scored.slice(0, options.maxTasks);
}
/**
 * Compute open bugs.
 */
function computeOpenBugs(tasks, _taskMap, options) {
    const bugs = [];
    for (const task of tasks) {
        const t = task;
        const isBug = t.origin === 'bug-report' || t.labels?.includes('bug');
        const isOpen = t.status !== 'done' && t.status !== 'cancelled';
        if (isBug && isOpen && (!options.scopeTaskIds || options.scopeTaskIds.has(t.id))) {
            bugs.push({
                id: t.id,
                title: t.title,
                priority: t.priority || 'medium',
            });
        }
    }
    // Sort by priority
    const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
    bugs.sort((a, b) => (priorityOrder[a.priority] ?? 99) - (priorityOrder[b.priority] ?? 99));
    return bugs.slice(0, options.maxBugs);
}
/**
 * Compute blocked tasks.
 */
function computeBlockedTasks(tasks, taskMap, options) {
    const blocked = [];
    for (const task of tasks) {
        const t = task;
        if (options.scopeTaskIds && !options.scopeTaskIds.has(t.id))
            continue;
        const blockedBy = [];
        // Check blocked status
        if (t.status === 'blocked' && t.blockedBy) {
            blockedBy.push(t.blockedBy);
        }
        // Check unresolved dependencies
        if (t.depends && t.depends.length > 0) {
            for (const depId of t.depends) {
                const dep = taskMap.get(depId);
                if (dep && dep.status !== 'done' && dep.status !== 'cancelled') {
                    if (!blockedBy.includes(depId)) {
                        blockedBy.push(depId);
                    }
                }
            }
        }
        if (blockedBy.length > 0) {
            blocked.push({
                id: t.id,
                title: t.title,
                blockedBy,
            });
        }
    }
    return blocked.slice(0, options.maxBlocked);
}
/**
 * Compute active epics.
 */
function computeActiveEpics(tasks, taskMap, options) {
    const epics = [];
    for (const task of tasks) {
        const t = task;
        if (options.scopeTaskIds && !options.scopeTaskIds.has(t.id))
            continue;
        if (t.type === 'epic' && t.status === 'active') {
            const completionPercent = calculateEpicCompletion(t.id, taskMap);
            epics.push({
                id: t.id,
                title: t.title,
                completionPercent,
            });
        }
    }
    // Sort by completion (ascending - less complete first)
    epics.sort((a, b) => a.completionPercent - b.completionPercent);
    return epics.slice(0, options.maxEpics);
}
/**
 * Calculate completion percentage for an epic.
 */
function calculateEpicCompletion(epicId, taskMap) {
    let totalTasks = 0;
    let completedTasks = 0;
    // Collect all descendant tasks
    const collectTasks = (parentId) => {
        for (const task of taskMap.values()) {
            const t = task;
            if (t.parentId === parentId) {
                totalTasks++;
                if (t.status === 'done' || t.status === 'cancelled') {
                    completedTasks++;
                }
                collectTasks(t.id);
            }
        }
    };
    collectTasks(epicId);
    return totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
}
/**
 * Compute pipeline stage info from task file metadata.
 */
async function computePipelineStage(focus) {
    const taskId = focus?.currentTask;
    if (!taskId)
        return undefined;
    try {
        const { getPipeline } = await import('../lifecycle/pipeline.js');
        const pipeline = await getPipeline(taskId);
        if (!pipeline)
            return undefined;
        return {
            currentStage: pipeline.currentStage,
            stageStatus: pipeline.isActive ? 'active' : (pipeline.status ?? 'completed'),
        };
    }
    catch {
        return undefined;
    }
}
//# sourceMappingURL=briefing.js.map