import { execSync } from 'node:child_process';
import { readdir, writeFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { parseFrontmatterField } from '../shared/frontmatter.js';
import { resolveProjectsRoot } from '../shared/oat-paths.js';
import { readOatLocalConfig } from '../../config/oat-config.js';
import { ensureDir, fileExists } from '../../fs/io.js';
const defaultGit = {
    isGitRepo(root) {
        try {
            execSync('git rev-parse --git-dir', {
                cwd: root,
                stdio: 'pipe',
            });
            return true;
        }
        catch {
            return false;
        }
    },
    diffFileCount(root, sha) {
        try {
            const output = execSync(`git diff --name-only ${sha} HEAD`, {
                cwd: root,
                stdio: 'pipe',
                encoding: 'utf8',
            });
            if (!output.trim())
                return 0;
            return output.trim().split('\n').length;
        }
        catch {
            return 0;
        }
    },
};
async function hasAnyProjects(projectsRoot) {
    try {
        const entries = await readdir(projectsRoot, { withFileTypes: true });
        return entries.some((e) => e.isDirectory());
    }
    catch {
        return false;
    }
}
async function readActiveProject(repoRoot) {
    const localConfig = await readOatLocalConfig(repoRoot);
    const result = {
        name: '',
        path: '',
        status: 'not set',
        lastPausedProject: localConfig.lastPausedProject ?? null,
    };
    const rawValue = localConfig.activeProject?.trim();
    if (!rawValue)
        return result;
    result.name = basename(rawValue);
    result.path = rawValue;
    const fullProjectPath = join(repoRoot, rawValue);
    try {
        const entries = await readdir(fullProjectPath);
        if (!entries) {
            result.status = 'directory missing';
            return result;
        }
    }
    catch {
        result.status = 'directory missing';
        return result;
    }
    if (!(await fileExists(join(fullProjectPath, 'state.md')))) {
        result.status = 'state.md missing';
        return result;
    }
    result.status = 'active';
    return result;
}
export function phaseInHillList(phase, listStr) {
    return listStr.includes(`"${phase}"`);
}
async function readProjectState(repoRoot, projectPath) {
    const stateFile = join(repoRoot, projectPath, 'state.md');
    const phase = (await parseFrontmatterField(stateFile, 'oat_phase')) || 'unknown';
    const phaseStatus = (await parseFrontmatterField(stateFile, 'oat_phase_status')) || 'unknown';
    let currentTask = await parseFrontmatterField(stateFile, 'oat_current_task');
    if (!currentTask || currentTask === 'null')
        currentTask = '-';
    const lifecycle = (await parseFrontmatterField(stateFile, 'oat_lifecycle')) || 'active';
    const pauseTimestamp = lifecycle === 'paused'
        ? await parseFrontmatterField(stateFile, 'oat_pause_timestamp')
        : '';
    const pauseReason = lifecycle === 'paused'
        ? await parseFrontmatterField(stateFile, 'oat_pause_reason')
        : '';
    const blockers = (await parseFrontmatterField(stateFile, 'oat_blockers')) || '[]';
    const hillCheckpoints = (await parseFrontmatterField(stateFile, 'oat_hill_checkpoints')) || '[]';
    const hillCompleted = (await parseFrontmatterField(stateFile, 'oat_hill_completed')) || '[]';
    const workflowMode = (await parseFrontmatterField(stateFile, 'oat_workflow_mode')) ||
        'spec-driven';
    const docsUpdatedRaw = await parseFrontmatterField(stateFile, 'oat_docs_updated');
    const docsUpdated = docsUpdatedRaw && docsUpdatedRaw !== 'null' ? docsUpdatedRaw : '';
    let hillStatus;
    if (phaseInHillList(phase, hillCheckpoints)) {
        hillStatus = phaseInHillList(phase, hillCompleted) ? 'passed' : 'pending';
    }
    else {
        hillStatus = 'n/a';
    }
    return {
        phase,
        phaseStatus,
        currentTask,
        lifecycle,
        pauseTimestamp,
        pauseReason,
        blockers,
        hillCheckpoints,
        hillCompleted,
        hillStatus,
        workflowMode,
        docsUpdated,
    };
}
async function readKnowledgeStatus(repoRoot) {
    const indexFile = join(repoRoot, '.oat', 'repo', 'knowledge', 'project-index.md');
    const result = {
        generatedAt: '',
        mergeBaseSha: '',
        status: 'not generated',
    };
    if (!(await fileExists(indexFile)))
        return result;
    result.generatedAt = await parseFrontmatterField(indexFile, 'oat_generated_at');
    result.mergeBaseSha = await parseFrontmatterField(indexFile, 'oat_source_main_merge_base_sha');
    if (result.generatedAt) {
        result.status = 'generated';
    }
    return result;
}
function calculateStaleness(knowledge, git, repoRoot, today) {
    const result = {
        filesChanged: 0,
        ageDays: 0,
        status: 'fresh',
    };
    if (!knowledge.mergeBaseSha) {
        result.status = 'unknown';
        return result;
    }
    try {
        result.filesChanged = git.diffFileCount(repoRoot, knowledge.mergeBaseSha);
    }
    catch {
        result.filesChanged = 0;
    }
    if (knowledge.generatedAt) {
        const genDate = new Date(knowledge.generatedAt);
        const todayDate = new Date(today);
        if (!Number.isNaN(genDate.getTime()) &&
            !Number.isNaN(todayDate.getTime())) {
            result.ageDays = Math.floor((todayDate.getTime() - genDate.getTime()) / 86400000);
        }
    }
    if (result.filesChanged > 20 || result.ageDays > 7) {
        result.status = 'stale';
    }
    else if (result.filesChanged > 5 || result.ageDays > 3) {
        result.status = 'aging';
    }
    return result;
}
function computeNextStep(project, hasProjects, state) {
    if (project.status === 'not set') {
        if (project.lastPausedProject) {
            return {
                step: `oat project open ${basename(project.lastPausedProject)}`,
                reason: 'Resume paused project or open a different one',
            };
        }
        if (hasProjects) {
            return {
                step: 'oat-project-open',
                reason: 'Select an existing project to continue work',
            };
        }
        return {
            step: 'oat-project-new',
            reason: 'Create a new project to start work',
        };
    }
    if (project.status !== 'active') {
        return {
            step: 'oat-project-open',
            reason: `Current project has issues: ${project.status}`,
        };
    }
    if (!state) {
        return { step: 'oat-project-progress', reason: 'Check current progress' };
    }
    if (state.lifecycle === 'paused') {
        return {
            step: `oat project open ${project.name}`,
            reason: 'Project is paused — open to resume',
        };
    }
    // HiLL checkpoint gating
    if (state.phaseStatus === 'complete' &&
        phaseInHillList(state.phase, state.hillCheckpoints) &&
        !phaseInHillList(state.phase, state.hillCompleted)) {
        // Spec is no longer a discrete HiLL checkpoint in newly-scaffolded projects —
        // requirements confirmation is folded into oat-project-design. Legacy
        // projects whose state.md still lists `spec` in oat_hill_checkpoints route
        // to oat-project-design here so they finish through the consolidated flow
        // rather than being pushed back at the deprecated standalone skill.
        const phaseSkillMap = {
            discovery: 'oat-project-discover',
            spec: 'oat-project-design',
            design: 'oat-project-design',
            plan: 'oat-project-plan',
            implement: 'oat-project-implement',
        };
        return {
            step: phaseSkillMap[state.phase] ?? 'oat-project-progress',
            reason: `Complete ${state.phase} HiLL approval before advancing`,
        };
    }
    // Workflow mode routing
    const key = `${state.workflowMode}:${state.phase}:${state.phaseStatus}`;
    const routeMap = {
        'spec-driven:discovery:in_progress': {
            step: 'oat-project-discover',
            reason: 'Continue discovery phase',
        },
        'spec-driven:discovery:complete': {
            step: 'oat-project-design',
            reason: 'Confirm requirements and produce design (spec is folded into design)',
        },
        // Legacy projects whose phase still reads `spec` route to design — the
        // consolidated oat-project-design skill handles existing spec.md gracefully.
        'spec-driven:spec:in_progress': {
            step: 'oat-project-design',
            reason: 'Continue into design (spec is folded into design — legacy phase)',
        },
        'spec-driven:spec:complete': {
            step: 'oat-project-design',
            reason: 'Create design from specification',
        },
        'spec-driven:design:in_progress': {
            step: 'oat-project-design',
            reason: 'Continue design phase',
        },
        'spec-driven:design:complete': {
            step: 'oat-project-plan',
            reason: 'Create implementation plan from design',
        },
        'quick:discovery:in_progress': {
            step: 'oat-project-discover',
            reason: 'Continue quick discovery phase',
        },
        'quick:discovery:complete': {
            step: 'oat-project-plan',
            reason: 'Generate plan directly for quick workflow',
        },
        'import:plan:in_progress': {
            step: 'oat-project-import-plan',
            reason: 'Continue import-plan normalization',
        },
    };
    if (routeMap[key])
        return routeMap[key];
    // Shared routing (wildcard mode)
    const sharedKey = `${state.phase}:${state.phaseStatus}`;
    const sharedMap = {
        'plan:in_progress': {
            step: 'oat-project-plan',
            reason: 'Continue planning phase',
        },
        'plan:complete': {
            step: 'oat-project-implement',
            reason: 'Start implementation',
        },
        'implement:in_progress': {
            step: 'oat-project-implement',
            reason: 'Continue implementation',
        },
        'implement:pr_open': {
            step: 'oat-project-revise',
            reason: 'PR open — run oat-project-revise for feedback or oat-project-complete when approved',
        },
    };
    if (sharedMap[sharedKey])
        return sharedMap[sharedKey];
    // Implementation complete — recommend docs sync if not yet done, then PR
    if (state.phase === 'implement' && state.phaseStatus === 'complete') {
        if (!state.docsUpdated || state.docsUpdated === '') {
            return {
                step: 'oat-project-document',
                reason: 'Sync documentation before generating PR',
            };
        }
        return {
            step: 'oat-project-pr-final',
            reason: 'Generate final PR description (final review passed)',
        };
    }
    return { step: 'oat-project-progress', reason: 'Check current progress' };
}
function isTerminalCoordinationProject(kind, phase, phaseStatus) {
    return (kind === 'coordination' &&
        phase === 'decomposition' &&
        phaseStatus === 'complete');
}
async function listAvailableProjects(repoRoot, projectsRoot) {
    const fullRoot = join(repoRoot, projectsRoot);
    try {
        const entries = await readdir(fullRoot, { withFileTypes: true });
        const dirs = entries.filter((e) => e.isDirectory());
        if (dirs.length === 0) {
            return {
                active: '*(No projects found)*',
                decompositions: '*(No decompositions found)*',
            };
        }
        const lines = [];
        const decompositions = [];
        for (const dir of dirs) {
            const stateFile = join(fullRoot, dir.name, 'state.md');
            if (await fileExists(stateFile)) {
                const phase = (await parseFrontmatterField(stateFile, 'oat_phase')) || 'unknown';
                const phaseStatus = (await parseFrontmatterField(stateFile, 'oat_phase_status')) ||
                    'in_progress';
                const kind = (await parseFrontmatterField(stateFile, 'oat_kind')) ||
                    'implementation';
                const line = `- **${dir.name}** - ${phase}`;
                if (isTerminalCoordinationProject(kind, phase, phaseStatus)) {
                    decompositions.push(line);
                }
                else {
                    lines.push(line);
                }
            }
        }
        return {
            active: lines.length > 0 ? lines.join('\n') : '*(No projects found)*',
            decompositions: decompositions.length > 0
                ? decompositions.join('\n')
                : '*(No decompositions found)*',
        };
    }
    catch {
        return {
            active: '*(No projects directory found)*',
            decompositions: '*(No decompositions found)*',
        };
    }
}
function buildDashboardMarkdown(project, state, knowledge, staleness, nextStep, projectsList, generatedDate) {
    const timestamp = `${generatedDate}`;
    const lines = [];
    lines.push('---');
    lines.push('oat_generated: true');
    lines.push(`oat_generated_at: ${generatedDate}`);
    lines.push('---');
    lines.push('');
    lines.push('# OAT Repo State Dashboard');
    lines.push('');
    lines.push(`**Generated:** ${timestamp}`);
    lines.push('');
    lines.push('## Active Project');
    lines.push('');
    if (project.status === 'not set') {
        lines.push('*(not set)*');
    }
    else {
        lines.push(`**${project.name}** (\`${project.path}\`)`);
    }
    lines.push('');
    if (project.status === 'not set' && project.lastPausedProject) {
        lines.push('## Last Paused Project');
        lines.push('');
        lines.push(`**${basename(project.lastPausedProject)}** (\`${project.lastPausedProject}\`)`);
        lines.push('');
    }
    if (project.status === 'active' && state) {
        lines.push('## Active Project Summary');
        lines.push('');
        lines.push('| Field | Value |');
        lines.push('|-------|-------|');
        lines.push(`| Mode | ${state.workflowMode} |`);
        lines.push(`| Phase | ${state.phase} |`);
        lines.push(`| Status | ${state.phaseStatus} |`);
        if (state.lifecycle !== 'active') {
            lines.push(`| Lifecycle | ${state.lifecycle} |`);
            if (state.pauseTimestamp) {
                lines.push(`| Pause Timestamp | ${state.pauseTimestamp} |`);
            }
            if (state.pauseReason) {
                lines.push(`| Pause Reason | ${state.pauseReason} |`);
            }
        }
        lines.push(`| HiLL Gate | ${state.hillStatus} |`);
        lines.push(`| Current Task | ${state.currentTask} |`);
        const docsLabel = state.docsUpdated === 'complete'
            ? '✓ complete'
            : state.docsUpdated === 'skipped'
                ? '⊘ skipped'
                : '⚠ not yet run';
        lines.push(`| Docs Updated | ${docsLabel} |`);
        lines.push('');
        lines.push(`Details: \`${project.path}/state.md\``);
        lines.push('');
    }
    else if (project.status !== 'not set') {
        lines.push(`**Warning:** ${project.status}`);
        lines.push('');
    }
    lines.push('## Knowledge Status');
    lines.push('');
    lines.push('| Field | Value |');
    lines.push('|-------|-------|');
    lines.push(`| Generated | ${knowledge.generatedAt || 'N/A'} |`);
    lines.push(`| Age | ${staleness.ageDays} days |`);
    lines.push(`| Files Changed | ${staleness.filesChanged} |`);
    lines.push(`| Status | ${staleness.status} |`);
    lines.push('');
    lines.push('## Recommended Next Step');
    lines.push('');
    lines.push(`**${nextStep.step}** - ${nextStep.reason}`);
    lines.push('');
    lines.push('## Quick Commands');
    lines.push('');
    lines.push('- `oat-project-progress` - Check current status');
    lines.push('- `oat-repo-knowledge-index` - Refresh knowledge base');
    lines.push('- `oat-project-new` - Create a spec-driven project');
    lines.push('- `oat-project-quick-start` - Create a quick workflow project');
    lines.push('- `oat-project-import-plan` - Import an external provider plan');
    lines.push('- `oat project open <name>` - Open or resume a project');
    lines.push('- `oat project pause [name]` - Pause active or named project');
    lines.push('- `oat-project-complete` - Mark project complete');
    lines.push('');
    lines.push('## Available Projects');
    lines.push('');
    lines.push(projectsList.active);
    lines.push('');
    lines.push('## Decompositions');
    lines.push('');
    lines.push(projectsList.decompositions);
    lines.push('');
    return lines.join('\n');
}
export async function generateStateDashboard(options) {
    const { repoRoot } = options;
    const env = options.env ?? process.env;
    const today = options.today ?? new Date().toISOString().slice(0, 10);
    const git = options.git ?? defaultGit;
    const dashboardPath = join(repoRoot, '.oat', 'state.md');
    const projectsRoot = await resolveProjectsRoot(repoRoot, env);
    const hasProjects = await hasAnyProjects(join(repoRoot, projectsRoot));
    const project = await readActiveProject(repoRoot);
    let state = null;
    if (project.status === 'active') {
        state = await readProjectState(repoRoot, project.path);
    }
    const knowledge = await readKnowledgeStatus(repoRoot);
    const staleness = calculateStaleness(knowledge, git, repoRoot, today);
    const nextStep = computeNextStep(project, hasProjects, state);
    const projectsList = await listAvailableProjects(repoRoot, projectsRoot);
    const markdown = buildDashboardMarkdown(project, state, knowledge, staleness, nextStep, projectsList, today);
    await ensureDir(join(repoRoot, '.oat'));
    await writeFile(dashboardPath, markdown, 'utf8');
    return {
        dashboardPath,
        projectName: project.name || null,
        projectStatus: project.status,
        stalenessStatus: staleness.status,
        recommendedStep: nextStep.step,
        recommendedReason: nextStep.reason,
    };
}
