import { readFile, readdir, stat } from 'fs/promises';
import { join } from 'path';
/**
 * Scan lite-plan and lite-fix directories for task sessions
 * @param workflowDir - Path to .workflow directory
 * @returns Lite tasks data
 */
export async function scanLiteTasks(workflowDir) {
    const litePlanDir = join(workflowDir, '.lite-plan');
    const liteFixDir = join(workflowDir, '.lite-fix');
    const multiCliDir = join(workflowDir, '.multi-cli-plan');
    const [litePlan, liteFix, multiCliPlan] = await Promise.all([
        scanLiteDir(litePlanDir, 'lite-plan'),
        scanLiteDir(liteFixDir, 'lite-fix'),
        scanMultiCliDir(multiCliDir),
    ]);
    return { litePlan, liteFix, multiCliPlan };
}
/**
 * Scan a lite task directory
 * @param dir - Directory path
 * @param type - Task type ('lite-plan' or 'lite-fix')
 * @returns Array of lite task sessions
 */
async function scanLiteDir(dir, type) {
    try {
        const entries = await readdir(dir, { withFileTypes: true });
        const sessions = (await Promise.all(entries
            .filter((entry) => entry.isDirectory())
            .map(async (entry) => {
            const sessionPath = join(dir, entry.name);
            const [createdAt, plan, tasks, diagnoses] = await Promise.all([
                getCreatedTime(sessionPath),
                loadPlanJson(sessionPath),
                loadTaskJsons(sessionPath),
                type === 'lite-fix' ? loadDiagnoses(sessionPath) : Promise.resolve(undefined),
            ]);
            const session = {
                id: entry.name,
                type,
                path: sessionPath,
                createdAt,
                plan,
                tasks,
                diagnoses,
                progress: { total: 0, completed: 0, percentage: 0 },
            };
            session.progress = calculateProgress(session.tasks);
            return session;
        })))
            .filter((session) => session !== null)
            .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
        return sessions;
    }
    catch (err) {
        if (err?.code === 'ENOENT')
            return [];
        console.error(`Error scanning ${dir}:`, err?.message || String(err));
        return [];
    }
}
/**
 * Load session-state.json from multi-cli session directory
 * @param sessionPath - Session directory path
 * @returns Session state or null if not found
 */
async function loadSessionState(sessionPath) {
    const statePath = join(sessionPath, 'session-state.json');
    try {
        const content = await readFile(statePath, 'utf8');
        return JSON.parse(content);
    }
    catch {
        return null;
    }
}
/**
 * Build discussion topic structure from session state and synthesis
 * @param state - Session state from session-state.json
 * @param synthesis - Latest round synthesis
 * @returns Discussion topic for frontend rendering
 */
function buildDiscussionTopic(state, synthesis) {
    const keyQuestions = synthesis?.clarification_questions || [];
    const solutions = synthesis?.solutions || [];
    return {
        title: state?.task_description || 'Discussion Topic',
        description: solutions[0]?.summary || '',
        scope: {
            included: state?.ace_context?.relevant_files || [],
            excluded: [],
        },
        keyQuestions,
        status: state?.status || 'analyzing',
        tags: solutions.map((s) => s.name).slice(0, 3),
    };
}
/**
 * Scan multi-cli-plan directory for sessions
 * @param dir - Directory path to .multi-cli-plan
 * @returns Array of multi-cli sessions with extended metadata
 */
async function scanMultiCliDir(dir) {
    try {
        const entries = await readdir(dir, { withFileTypes: true });
        const sessions = (await Promise.all(entries
            .filter((entry) => entry.isDirectory())
            .map(async (entry) => {
            const sessionPath = join(dir, entry.name);
            const [createdAt, syntheses, sessionState, planJson] = await Promise.all([
                getCreatedTime(sessionPath),
                loadRoundSyntheses(sessionPath),
                loadSessionState(sessionPath),
                loadPlanJson(sessionPath),
            ]);
            // Extract data from syntheses
            const roundCount = syntheses.length;
            const latestSynthesis = syntheses.length > 0 ? syntheses[syntheses.length - 1] : null;
            // Calculate progress based on round count and convergence
            const progress = calculateMultiCliProgress(syntheses);
            // Build discussion topic for frontend
            const discussionTopic = buildDiscussionTopic(sessionState, latestSynthesis);
            // Determine status from session state or synthesis convergence
            const status = sessionState?.status ||
                (latestSynthesis?.convergence?.recommendation === 'converged' ? 'converged' : 'analyzing');
            // Use plan.json if available, otherwise extract from synthesis
            const plan = planJson || latestSynthesis;
            // Use tasks from plan.json if available, otherwise extract from synthesis
            const tasks = planJson?.tasks?.length > 0
                ? normalizePlanJsonTasks(planJson.tasks)
                : extractTasksFromSyntheses(syntheses);
            const session = {
                id: entry.name,
                type: 'multi-cli-plan',
                path: sessionPath,
                createdAt,
                plan,
                tasks,
                progress,
                // Extended multi-cli specific fields
                roundCount,
                topicTitle: sessionState?.task_description || 'Discussion Topic',
                status,
                metadata: {
                    roundId: roundCount,
                    timestamp: sessionState?.updated_at || createdAt,
                    currentPhase: sessionState?.current_phase || 1,
                },
                discussionTopic,
                rounds: syntheses,
                latestSynthesis,
            };
            return session;
        })))
            .filter((session) => session !== null)
            .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
        return sessions;
    }
    catch (err) {
        if (err?.code === 'ENOENT')
            return [];
        console.error(`Error scanning ${dir}:`, err?.message || String(err));
        return [];
    }
}
/**
 * Load all synthesis.json files from rounds subdirectories
 * @param sessionPath - Session directory path
 * @returns Array of synthesis objects sorted by round number
 */
async function loadRoundSyntheses(sessionPath) {
    const roundsDir = join(sessionPath, 'rounds');
    const syntheses = [];
    try {
        const roundEntries = await readdir(roundsDir, { withFileTypes: true });
        const roundDirs = roundEntries
            .filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name))
            .map((entry) => ({
            name: entry.name,
            num: parseInt(entry.name, 10),
        }))
            .sort((a, b) => a.num - b.num);
        for (const roundDir of roundDirs) {
            const synthesisPath = join(roundsDir, roundDir.name, 'synthesis.json');
            try {
                const content = await readFile(synthesisPath, 'utf8');
                const synthesis = JSON.parse(content);
                synthesis.round = roundDir.num;
                syntheses.push(synthesis);
            }
            catch (e) {
                console.warn('Failed to parse synthesis file:', synthesisPath, e.message);
            }
        }
    }
    catch (e) {
        // Ignore ENOENT errors (directory doesn't exist), warn on others
        if (e.code !== 'ENOENT') {
            console.warn('Failed to read rounds directory:', roundsDir, e.message);
        }
    }
    return syntheses;
}
/**
 * Calculate progress for multi-cli-plan sessions
 * Uses new convergence.score and convergence.recommendation when available
 * Falls back to old converged boolean for backward compatibility
 * @param syntheses - Array of round syntheses
 * @returns Progress info with convergence metrics
 */
function calculateMultiCliProgress(syntheses) {
    if (syntheses.length === 0) {
        return { total: 0, completed: 0, percentage: 0 };
    }
    const latestSynthesis = syntheses[syntheses.length - 1];
    // NEW schema: Use convergence object
    if (latestSynthesis.convergence) {
        const { score, recommendation } = latestSynthesis.convergence;
        const isConverged = recommendation === 'converged';
        // Calculate solutions metrics
        const solutions = latestSynthesis.solutions || [];
        const solutionsCount = solutions.length;
        const avgFeasibility = solutionsCount > 0
            ? solutions.reduce((sum, s) => sum + (s.feasibility || 0), 0) / solutionsCount
            : 0;
        // Total is based on rounds, percentage derived from convergence score
        const total = syntheses.length;
        const completed = isConverged ? total : Math.max(0, total - 1);
        const percentage = isConverged ? 100 : Math.round(score * 100);
        return {
            total,
            completed,
            percentage,
            convergenceScore: score,
            recommendation,
            solutionsCount,
            avgFeasibility: Math.round(avgFeasibility * 100) / 100
        };
    }
    // OLD schema: Fallback to converged boolean
    const isConverged = latestSynthesis.converged === true;
    const total = syntheses.length;
    const completed = isConverged ? total : Math.max(0, total - 1);
    const percentage = isConverged ? 100 : Math.round((completed / Math.max(total, 1)) * 100);
    return { total, completed, percentage };
}
/**
 * Extract tasks from synthesis objects
 * NEW schema: Extract from solutions[].implementation_plan.tasks
 * OLD schema: Extract from tasks[] array directly
 * @param syntheses - Array of round syntheses
 * @returns Normalized tasks from latest synthesis
 */
function extractTasksFromSyntheses(syntheses) {
    if (syntheses.length === 0)
        return [];
    const latestSynthesis = syntheses[syntheses.length - 1];
    // NEW schema: Extract tasks from solutions
    if (latestSynthesis.solutions && Array.isArray(latestSynthesis.solutions)) {
        const allTasks = [];
        for (const solution of latestSynthesis.solutions) {
            const implPlan = solution.implementation_plan;
            if (!implPlan?.tasks || !Array.isArray(implPlan.tasks))
                continue;
            for (const task of implPlan.tasks) {
                const normalizedTask = normalizeSolutionTask(task, solution);
                if (normalizedTask) {
                    allTasks.push(normalizedTask);
                }
            }
        }
        // Sort by task ID
        return allTasks.sort((a, b) => {
            const aNum = parseInt(a.id?.replace(/\D/g, '') || '0');
            const bNum = parseInt(b.id?.replace(/\D/g, '') || '0');
            return aNum - bNum;
        });
    }
    // OLD schema: Extract from tasks array directly
    const tasks = latestSynthesis.tasks;
    if (!Array.isArray(tasks))
        return [];
    return tasks
        .map((task) => normalizeTask(task))
        .filter((task) => task !== null);
}
/**
 * Normalize a solution task from NEW schema to NormalizedTask
 * @param task - SolutionTask from new schema
 * @param solution - Parent solution for context
 * @returns Normalized task
 */
function normalizeSolutionTask(task, solution) {
    if (!task || !task.id)
        return null;
    return {
        id: task.id,
        title: task.name || 'Untitled Task',
        status: task.status || 'pending',
        meta: {
            type: 'implementation',
            agent: null,
            scope: solution.name || null,
            module: null
        },
        context: {
            requirements: task.key_point ? [task.key_point] : [],
            focus_paths: task.files?.map(f => f.file) || [],
            acceptance: [],
            depends_on: task.depends_on || []
        },
        flow_control: {
            implementation_approach: task.files?.map((f, i) => ({
                step: `Step ${i + 1}`,
                action: `${f.action} ${f.file}${f.line ? ` at line ${f.line}` : ''}`
            })) || []
        },
        _raw: {
            task,
            solution: {
                name: solution.name,
                source_cli: solution.source_cli,
                feasibility: solution.feasibility,
                effort: solution.effort,
                risk: solution.risk
            }
        }
    };
}
/**
 * Normalize tasks from plan.json format to NormalizedTask[]
 * plan.json tasks have: id, name, description, depends_on, status, files, key_point, acceptance_criteria
 * @param tasks - Tasks array from plan.json
 * @returns Normalized tasks
 */
function normalizePlanJsonTasks(tasks) {
    if (!Array.isArray(tasks))
        return [];
    return tasks.map((task) => {
        if (!task || !task.id)
            return null;
        return {
            id: task.id,
            title: task.name || task.title || 'Untitled Task',
            status: task.status || 'pending',
            meta: {
                type: 'implementation',
                agent: null,
                scope: task.scope || null,
                module: null
            },
            context: {
                requirements: task.description ? [task.description] : (task.key_point ? [task.key_point] : []),
                focus_paths: task.files?.map((f) => typeof f === 'string' ? f : f.file) || [],
                acceptance: task.acceptance_criteria || [],
                depends_on: task.depends_on || []
            },
            flow_control: {
                implementation_approach: task.files?.map((f, i) => {
                    const filePath = typeof f === 'string' ? f : f.file;
                    const action = typeof f === 'string' ? 'modify' : f.action;
                    const line = typeof f === 'string' ? null : f.line;
                    return {
                        step: `Step ${i + 1}`,
                        action: `${action} ${filePath}${line ? ` at line ${line}` : ''}`
                    };
                }) || []
            },
            _raw: {
                task,
                estimated_complexity: task.estimated_complexity
            }
        };
    }).filter((task) => task !== null);
}
/**
 * Load plan.json or fix-plan.json from session directory
 * @param sessionPath - Session directory path
 * @returns Plan data or null
 */
async function loadPlanJson(sessionPath) {
    // Try fix-plan.json first (for lite-fix), then plan.json (for lite-plan)
    const fixPlanPath = join(sessionPath, 'fix-plan.json');
    const planPath = join(sessionPath, 'plan.json');
    // Try fix-plan.json first
    try {
        const content = await readFile(fixPlanPath, 'utf8');
        return JSON.parse(content);
    }
    catch {
        // Continue to try plan.json
    }
    // Fallback to plan.json
    try {
        const content = await readFile(planPath, 'utf8');
        return JSON.parse(content);
    }
    catch {
        return null;
    }
}
/**
 * Load all task JSON files from session directory
 * Supports multiple task formats:
 * 1. .task/IMPL-*.json files
 * 2. tasks array in plan.json
 * 3. task-*.json files in session root
 * @param sessionPath - Session directory path
 * @returns Array of task objects
 */
async function loadTaskJsons(sessionPath) {
    let tasks = [];
    // Method 1: Check .task/IMPL-*.json files
    const taskDir = join(sessionPath, '.task');
    try {
        const implFiles = (await readdir(taskDir))
            .filter((fileName) => fileName.endsWith('.json') && (fileName.startsWith('IMPL-') ||
            fileName.startsWith('TASK-') ||
            fileName.startsWith('task-') ||
            fileName.startsWith('diagnosis-') ||
            /^T\d+\.json$/i.test(fileName)));
        const implTasks = (await Promise.all(implFiles.map(async (fileName) => {
            const taskPath = join(taskDir, fileName);
            try {
                const content = await readFile(taskPath, 'utf8');
                return normalizeTask(JSON.parse(content));
            }
            catch {
                return null;
            }
        })))
            .filter((task) => task !== null);
        tasks = tasks.concat(implTasks);
    }
    catch {
        // Continue to other methods
    }
    // Method 2: Check plan.json or fix-plan.json for embedded tasks array
    if (tasks.length === 0) {
        const planFiles = [join(sessionPath, 'fix-plan.json'), join(sessionPath, 'plan.json')];
        for (const planFile of planFiles) {
            try {
                const plan = JSON.parse(await readFile(planFile, 'utf8'));
                if (Array.isArray(plan.tasks)) {
                    tasks = plan.tasks
                        .map((task) => normalizeTask(task))
                        .filter((task) => task !== null);
                    break;
                }
            }
            catch {
                // Continue to other plan files
            }
        }
    }
    // Method 3: Check for task-*.json and diagnosis-*.json files in session root
    if (tasks.length === 0) {
        try {
            const rootFiles = (await readdir(sessionPath))
                .filter((fileName) => fileName.endsWith('.json') && (fileName.startsWith('task-') ||
                fileName.startsWith('TASK-') ||
                fileName.startsWith('diagnosis-') ||
                /^T\d+\.json$/i.test(fileName)));
            const rootTasks = (await Promise.all(rootFiles.map(async (fileName) => {
                const taskPath = join(sessionPath, fileName);
                try {
                    const content = await readFile(taskPath, 'utf8');
                    return normalizeTask(JSON.parse(content));
                }
                catch {
                    return null;
                }
            })))
                .filter((task) => task !== null);
            tasks = tasks.concat(rootTasks);
        }
        catch {
            // No tasks found
        }
    }
    // Sort tasks by ID
    return tasks.sort((a, b) => {
        const aNum = parseInt(a.id?.replace(/\D/g, '') || '0');
        const bNum = parseInt(b.id?.replace(/\D/g, '') || '0');
        return aNum - bNum;
    });
}
/**
 * Normalize task object to consistent structure
 * @param task - Raw task object
 * @returns Normalized task
 */
function normalizeTask(task) {
    if (!task || typeof task !== 'object')
        return null;
    const taskObj = task;
    // Determine status - support various status formats
    let status = taskObj.status || 'pending';
    if (typeof status === 'object') {
        status = status.state || status.value || 'pending';
    }
    const meta = taskObj.meta;
    const context = taskObj.context;
    const flowControl = taskObj.flow_control;
    const implementation = taskObj.implementation;
    const modificationPoints = taskObj.modification_points;
    // Ensure id is always a string (handle numeric IDs from JSON)
    const rawId = taskObj.id ?? taskObj.task_id;
    const stringId = rawId != null ? String(rawId) : 'unknown';
    return {
        id: stringId,
        title: taskObj.title || taskObj.name || taskObj.summary || 'Untitled Task',
        status: status.toLowerCase(),
        // Preserve original fields for flexible rendering
        meta: meta ? {
            type: meta.type || taskObj.type || taskObj.action || 'task',
            agent: meta.agent || taskObj.agent || null,
            scope: meta.scope || taskObj.scope || null,
            module: meta.module || taskObj.module || null
        } : {
            type: taskObj.type || taskObj.action || 'task',
            agent: taskObj.agent || null,
            scope: taskObj.scope || null,
            module: taskObj.module || null
        },
        context: context ? {
            requirements: context.requirements || [],
            focus_paths: context.focus_paths || [],
            acceptance: context.acceptance || [],
            depends_on: context.depends_on || []
        } : {
            requirements: taskObj.requirements || (taskObj.description ? [taskObj.description] : []),
            focus_paths: taskObj.focus_paths || modificationPoints?.map(m => m.file).filter((f) => !!f) || [],
            acceptance: taskObj.acceptance || [],
            depends_on: taskObj.depends_on || []
        },
        flow_control: flowControl ? {
            implementation_approach: flowControl.implementation_approach || []
        } : {
            implementation_approach: implementation?.map((step, i) => ({
                step: `Step ${i + 1}`,
                action: step
            })) || []
        },
        // Keep all original fields for raw JSON view
        _raw: task
    };
}
/**
 * Get directory creation time
 * @param dirPath - Directory path
 * @returns ISO date string
 */
async function getCreatedTime(dirPath) {
    try {
        const stats = await stat(dirPath);
        return stats.birthtime.toISOString();
    }
    catch {
        return new Date().toISOString();
    }
}
/**
 * Calculate progress from tasks
 * @param tasks - Array of task objects
 * @returns Progress info
 */
function calculateProgress(tasks) {
    if (!tasks || tasks.length === 0) {
        return { total: 0, completed: 0, percentage: 0 };
    }
    const total = tasks.length;
    const completed = tasks.filter(t => t.status === 'completed').length;
    const percentage = Math.round((completed / total) * 100);
    return { total, completed, percentage };
}
/**
 * Get detailed lite task info
 * @param workflowDir - Workflow directory
 * @param type - 'lite-plan', 'lite-fix', or 'multi-cli-plan'
 * @param sessionId - Session ID
 * @returns Detailed task info
 */
export async function getLiteTaskDetail(workflowDir, type, sessionId) {
    let dir;
    if (type === 'lite-plan') {
        dir = join(workflowDir, '.lite-plan', sessionId);
    }
    else if (type === 'multi-cli-plan') {
        dir = join(workflowDir, '.multi-cli-plan', sessionId);
    }
    else {
        dir = join(workflowDir, '.lite-fix', sessionId);
    }
    try {
        const stats = await stat(dir);
        if (!stats.isDirectory())
            return null;
    }
    catch {
        return null;
    }
    // For multi-cli-plan, use synthesis-based loading
    if (type === 'multi-cli-plan') {
        const [syntheses, explorations, clarifications] = await Promise.all([
            loadRoundSyntheses(dir),
            loadExplorations(dir),
            loadClarifications(dir),
        ]);
        const latestSynthesis = syntheses.length > 0 ? syntheses[syntheses.length - 1] : null;
        const detail = {
            id: sessionId,
            type,
            path: dir,
            plan: latestSynthesis,
            tasks: extractTasksFromSyntheses(syntheses),
            explorations,
            clarifications,
        };
        return detail;
    }
    const [plan, tasks, explorations, clarifications, diagnoses] = await Promise.all([
        loadPlanJson(dir),
        loadTaskJsons(dir),
        loadExplorations(dir),
        loadClarifications(dir),
        type === 'lite-fix' ? loadDiagnoses(dir) : Promise.resolve(undefined),
    ]);
    const detail = {
        id: sessionId,
        type,
        path: dir,
        plan,
        tasks,
        explorations,
        clarifications,
        diagnoses,
    };
    return detail;
}
/**
 * Load exploration results
 * @param sessionPath - Session directory path
 * @returns Exploration results
 */
async function loadExplorations(sessionPath) {
    const explorePath = join(sessionPath, 'explorations.json');
    try {
        const content = await readFile(explorePath, 'utf8');
        return JSON.parse(content);
    }
    catch {
        return [];
    }
}
/**
 * Load clarification data
 * @param sessionPath - Session directory path
 * @returns Clarification data
 */
async function loadClarifications(sessionPath) {
    const clarifyPath = join(sessionPath, 'clarifications.json');
    try {
        const content = await readFile(clarifyPath, 'utf8');
        return JSON.parse(content);
    }
    catch {
        return null;
    }
}
/**
 * Load diagnosis files for lite-fix sessions
 * Loads diagnosis-*.json files from session root directory
 * @param sessionPath - Session directory path
 * @returns Diagnoses data with manifest and items
 */
async function loadDiagnoses(sessionPath) {
    const result = {
        manifest: null,
        items: []
    };
    // Try to load diagnoses-manifest.json first
    const manifestPath = join(sessionPath, 'diagnoses-manifest.json');
    try {
        result.manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
    }
    catch {
        // Continue without manifest
    }
    // Load all diagnosis-*.json files from session root
    try {
        const diagnosisFiles = (await readdir(sessionPath))
            .filter((fileName) => fileName.startsWith('diagnosis-') && fileName.endsWith('.json'));
        const items = (await Promise.all(diagnosisFiles.map(async (fileName) => {
            const filePath = join(sessionPath, fileName);
            try {
                const content = JSON.parse(await readFile(filePath, 'utf8'));
                return {
                    id: fileName.replace('diagnosis-', '').replace('.json', ''),
                    filename: fileName,
                    ...content,
                };
            }
            catch {
                return null;
            }
        })))
            .filter((item) => item !== null);
        result.items.push(...items);
    }
    catch {
        // Return empty items if directory read fails
    }
    return result;
}
//# sourceMappingURL=lite-scanner.js.map