// SPEC-093: Data collector for docs site generator
import { readFileSync } from 'node:fs';
import { readdir, readFile, stat } from 'node:fs/promises';
import { join } from 'node:path';
import { parseFrontmatter } from '../frontmatter-parser.js';
const SDD_PHASES = {
    Initialize: ['init_project', 'init_constitution', 'set_locale'],
    Analyze: [
        'suggest_stack',
        'check_versions',
        'suggest_tooling',
        'detect_deprecations',
        'security_check',
        'audit_stack',
        'paradigm_report',
        'discover_mcps',
        'suggest_mcps',
        'detect_agent',
        'check_config_health',
    ],
    'Create Spec': [
        'clarify_requirements',
        'create_spec',
        'define_ui_contract',
        'design_schema',
        'event_contracts',
        'challenge_spec',
        'detect_contradictions',
        'apply_template',
        'list_templates',
    ],
    'Filter & Enrich': [
        'estimate',
        'check_readiness',
        'manage_scope',
        'reality_check',
        'estimate_ai_cost',
        'recommend_model',
        'plan_team_distribution',
    ],
    Review: [
        'validate',
        'detect_drift',
        'check_spec_accuracy',
        'validate_workflow',
        'spec_coverage',
        'review_pr',
    ],
    Implement: [
        'generate_execution_plan',
        'generate_tests',
        'generate_rules',
        'generate_skill',
        'generate_sub_agent',
        'generate_teammate_prompt',
        'package_handoff',
        'manage_git',
        'generate_orchestration_script',
        'orchestrate',
        'orchestrate_agents',
        'transform_code',
        'generate_ci',
        'generate_planu_ci',
        'generate_ide_config',
        'scaffold_plugin',
        'configure_workers',
        'orchestrate_runtime',
        'configure_llm_providers',
        'create_pr_from_spec',
    ],
    Validate: ['audit', 'reconcile_spec', 'validate_team_results', 'audit_claude_config'],
    'Document & Ship': [
        'generate_docs',
        'summarize_spec',
        'generate_adr',
        'generate_checklist',
        'generate_changelog',
        'create_release',
    ],
};
function findPhaseForTool(toolName) {
    for (const [phase, tools] of Object.entries(SDD_PHASES)) {
        if (tools.includes(toolName)) {
            return phase;
        }
    }
    return 'Other';
}
function parseSpecId(dirName) {
    const match = /^(SPEC-\d+)/.exec(dirName);
    return match?.[1] ?? dirName;
}
function slugify(name) {
    return name
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-|-$/g, '');
}
function parseSpecType(title) {
    const lower = title.toLowerCase();
    if (lower.includes('refactor')) {
        return 'refactor';
    }
    if (lower.includes('fix') || lower.includes('bug')) {
        return 'fix';
    }
    if (lower.includes('test')) {
        return 'test';
    }
    if (lower.includes('doc')) {
        return 'docs';
    }
    return 'feature';
}
function extractTitle(metadata, lines, fallback) {
    if (typeof metadata.title === 'string') {
        return metadata.title;
    }
    const titleLine = lines.find((l) => l.startsWith('# '));
    const titleMatch = titleLine ? /^#\s+(?:SPEC-\d+:\s*)?(.+)/.exec(titleLine) : null;
    return titleMatch?.[1]?.trim() ?? fallback;
}
function extractStatus(metadata, lines) {
    if (typeof metadata.status === 'string') {
        return metadata.status;
    }
    const statusLine = lines.find((l) => l.includes('**Status:**'));
    const statusMatch = statusLine ? /\*\*Status:\*\*\s*(\w[\w-]*)/.exec(statusLine) : null;
    return statusMatch?.[1] ?? 'draft';
}
function countCriteria(lines) {
    let done = 0;
    let total = 0;
    for (const line of lines) {
        if (/^\s*-\s*\[x\]/i.test(line)) {
            done++;
            total++;
        }
        else if (/^\s*-\s*\[\s\]/.test(line)) {
            total++;
        }
    }
    return { done, total };
}
function extractTags(metadata, lines) {
    if (Array.isArray(metadata.tags)) {
        return metadata.tags;
    }
    const tagsIndex = lines.findIndex((l) => /^##\s+Tags/.test(l));
    if (tagsIndex < 0) {
        return [];
    }
    const tags = [];
    for (let i = tagsIndex + 1; i < lines.length; i++) {
        const tagLine = lines[i];
        if (tagLine === undefined || tagLine.startsWith('##')) {
            break;
        }
        const tagMatch = /^\s*-\s*(.+)/.exec(tagLine);
        if (tagMatch?.[1]) {
            tags.push(tagMatch[1].trim());
        }
    }
    return tags;
}
/**
 * Parse a single spec directory into a DocSpecSummary.
 */
async function parseSpecDir(specDirPath, specDirName) {
    const specFilePath = join(specDirPath, 'spec.md');
    let rawContent;
    try {
        rawContent = await readFile(specFilePath, 'utf-8');
    }
    catch {
        return null;
    }
    const { metadata, body } = parseFrontmatter(rawContent);
    const lines = body.split('\n');
    const criteria = countCriteria(lines);
    const id = typeof metadata.id === 'string' ? metadata.id : parseSpecId(specDirName);
    const title = extractTitle(metadata, lines, specDirName);
    const specType = typeof metadata.type === 'string' ? metadata.type : parseSpecType(title);
    const category = typeof metadata.category === 'string' ? metadata.category : specType;
    return {
        id,
        title,
        slug: slugify(specDirName),
        status: extractStatus(metadata, lines),
        category,
        type: specType,
        criteriaTotal: criteria.total,
        criteriaDone: criteria.done,
        tags: extractTags(metadata, lines),
        specPath: specFilePath,
    };
}
export async function collectSpecs(projectPath) {
    const specsDir = join(projectPath, 'planu', 'specs');
    const results = [];
    let entries;
    try {
        entries = await readdir(specsDir);
    }
    catch {
        return [];
    }
    // Flat layout: planu/specs/SPEC-XXX-slug/spec.md
    for (const entry of entries) {
        const entryPath = join(specsDir, entry);
        const entryStat = await stat(entryPath).catch(() => null);
        if (!entryStat?.isDirectory()) {
            continue;
        }
        const summary = await parseSpecDir(entryPath, entry);
        if (summary) {
            results.push(summary);
        }
    }
    results.sort((a, b) => {
        const numA = parseInt(/\d+/.exec(a.id)?.[0] ?? '0', 10);
        const numB = parseInt(/\d+/.exec(b.id)?.[0] ?? '0', 10);
        return numA - numB;
    });
    return results;
}
export function collectTools(projectPath) {
    const plansPath = join(projectPath, 'src', 'config', 'license-plans.json');
    const plans = JSON.parse(readFileSync(plansPath, 'utf-8'));
    const entries = [];
    for (const tool of plans.freeTools) {
        entries.push({
            name: tool,
            description: tool.replace(/_/g, ' '),
            phase: findPhaseForTool(tool),
            tier: 'free',
        });
    }
    for (const tool of plans.proTools) {
        entries.push({
            name: tool,
            description: tool.replace(/_/g, ' '),
            phase: findPhaseForTool(tool),
            tier: 'pro',
        });
    }
    return entries;
}
async function countLinesInDir(dirPath) {
    let total = 0;
    let entries;
    try {
        entries = await readdir(dirPath);
    }
    catch {
        return 0;
    }
    for (const entry of entries) {
        const fullPath = join(dirPath, entry);
        const entryStat = await stat(fullPath).catch(() => null);
        if (!entryStat) {
            continue;
        }
        if (entryStat.isDirectory()) {
            total += await countLinesInDir(fullPath);
        }
        else if (entry.endsWith('.ts')) {
            const content = await readFile(fullPath, 'utf-8').catch(() => '');
            total += content.split('\n').length;
        }
    }
    return total;
}
async function countTestFiles(testsDir) {
    let count = 0;
    let entries;
    try {
        entries = await readdir(testsDir);
    }
    catch {
        return 0;
    }
    for (const entry of entries) {
        const fullPath = join(testsDir, entry);
        const entryStat = await stat(fullPath).catch(() => null);
        if (!entryStat) {
            continue;
        }
        if (entryStat.isDirectory()) {
            count += await countTestFiles(fullPath);
        }
        else if (entry.endsWith('.test.ts')) {
            count++;
        }
    }
    return count;
}
export async function collectMetrics(projectPath) {
    const specs = await collectSpecs(projectPath);
    const tools = collectTools(projectPath);
    const specsByStatus = {};
    for (const spec of specs) {
        specsByStatus[spec.status] = (specsByStatus[spec.status] ?? 0) + 1;
    }
    const toolsByPhase = {};
    for (const tool of tools) {
        toolsByPhase[tool.phase] = (toolsByPhase[tool.phase] ?? 0) + 1;
    }
    const srcDir = join(projectPath, 'src');
    const layers = ['tools', 'engine', 'types', 'storage'];
    const locByLayer = {};
    for (const layer of layers) {
        locByLayer[layer] = await countLinesInDir(join(srcDir, layer));
    }
    const testCount = await countTestFiles(join(projectPath, 'tests'));
    return {
        totalSpecs: specs.length,
        specsByStatus,
        totalTools: tools.length,
        toolsByPhase,
        locByLayer,
        testCount,
    };
}
//# sourceMappingURL=data-collector.js.map