// Planu — Spec Splitter Engine
// Detects oversized specs and suggests atomic divisions
import { calculateEstimation, inferDifficulty } from './estimator.js';
// === Thresholds ===
const CRITERIA_THRESHOLD = 12;
const MODULE_THRESHOLD = 4;
const HOURS_THRESHOLD = 16;
// === Public API ===
/**
 * Analyze a spec and determine if it should be split into smaller specs.
 * Returns null if the spec is appropriately sized.
 */
export function analyzeSplit(input, specId) {
    // Trivial specs are atomic by definition
    if (input.scope === 'trivial') {
        return null;
    }
    const shouldSplit = detectOversizedSpec(input.criteriaCount, input.moduleCount, input.estimatedHours);
    if (!shouldSplit) {
        return null;
    }
    const reason = buildSplitReason(input.criteriaCount, input.moduleCount, input.estimatedHours);
    const subSpecs = generateSubSpecs(input);
    const dependencyOrder = subSpecs.map((_, i) => `${specId}-sub-${String(i + 1)}`);
    const totalEstimate = calculateTotalEstimate(subSpecs);
    return {
        originalSpecId: specId,
        reason,
        suggestedSpecs: subSpecs,
        dependencyOrder,
        totalEstimate,
    };
}
/**
 * Detect if a spec exceeds size thresholds.
 */
export function detectOversizedSpec(criteriaCount, moduleCount, estimatedHours) {
    return (criteriaCount > CRITERIA_THRESHOLD ||
        moduleCount > MODULE_THRESHOLD ||
        estimatedHours > HOURS_THRESHOLD);
}
/**
 * Format split suggestion for display based on experience level.
 */
export function formatSplitSuggestion(suggestion, level) {
    if (level === 'beginner') {
        return formatForBeginner(suggestion);
    }
    return formatForExpert(suggestion);
}
// === Internal ===
function buildSplitReason(criteria, modules, hours) {
    const reasons = [];
    if (criteria > CRITERIA_THRESHOLD) {
        reasons.push(`${String(criteria)} criteria (threshold: ${String(CRITERIA_THRESHOLD)})`);
    }
    if (modules > MODULE_THRESHOLD) {
        reasons.push(`${String(modules)} modules (threshold: ${String(MODULE_THRESHOLD)})`);
    }
    if (hours > HOURS_THRESHOLD) {
        reasons.push(`${String(hours)}h estimated (threshold: ${String(HOURS_THRESHOLD)}h)`);
    }
    return `Spec exceeds size thresholds: ${reasons.join(', ')}. Consider splitting into smaller, atomic specs.`;
}
function generateSubSpecs(input) {
    const subSpecs = [];
    // Strategy: split by target layer
    const targets = inferTargets(input.target, input.scope);
    for (const target of targets) {
        const criteriaShare = Math.ceil(input.criteriaCount / targets.length);
        const hoursShare = Math.round((input.estimatedHours / targets.length) * 10) / 10;
        const subScope = inferSubScope(input.scope);
        const dependsOn = [];
        // Shared specs should be done first
        if (target.target !== 'shared') {
            const sharedIdx = targets.findIndex((t) => t.target === 'shared');
            if (sharedIdx >= 0) {
                dependsOn.push(String(sharedIdx));
            }
        }
        // Database specs should be done before backend
        if (target.target === 'backend') {
            const dbIdx = targets.findIndex((t) => t.target === 'database');
            if (dbIdx >= 0) {
                dependsOn.push(String(dbIdx));
            }
        }
        subSpecs.push({
            suggestedTitle: `${target.label} — ${input.description.slice(0, 40)}`,
            type: input.type,
            target: target.target,
            scope: subScope,
            description: `${target.label} portion of the original spec.`,
            criteriaCount: criteriaShare,
            dependsOn,
            estimatedHours: hoursShare,
        });
    }
    return subSpecs;
}
function inferTargets(target, scope) {
    if (target === 'fullstack' && (scope === 'cross-module' || scope === 'architectural')) {
        return [
            { target: 'shared', label: 'Shared types and interfaces' },
            { target: 'database', label: 'Database schema and migrations' },
            { target: 'backend', label: 'Backend API and services' },
            { target: 'frontend', label: 'Frontend UI and integration' },
        ];
    }
    if (target === 'fullstack') {
        return [
            { target: 'backend', label: 'Backend implementation' },
            { target: 'frontend', label: 'Frontend implementation' },
        ];
    }
    // For non-fullstack, split by functionality
    return [
        { target, label: 'Core implementation' },
        { target, label: 'Tests and validation' },
    ];
}
function inferSubScope(scope) {
    const subScopeMap = {
        trivial: 'trivial',
        feature: 'feature',
        'cross-module': 'feature',
        architectural: 'cross-module',
    };
    return subScopeMap[scope];
}
function calculateTotalEstimate(subSpecs) {
    // Use first sub-spec's type/scope for the total estimate
    const firstSub = subSpecs[0];
    const type = firstSub?.type ?? 'feature';
    const scope = firstSub?.scope ?? 'feature';
    const difficulty = inferDifficulty(type, scope);
    return calculateEstimation(type, scope, difficulty);
}
function formatForBeginner(suggestion) {
    const lines = [];
    lines.push('## Why split this spec?');
    lines.push('');
    lines.push(suggestion.reason);
    lines.push('');
    lines.push('Splitting a large spec into smaller ones helps because:');
    lines.push('- Each sub-spec is easier to review and understand');
    lines.push('- You can implement and test each piece independently');
    lines.push('- If one part has issues, it does not block the others');
    lines.push('- Estimation is more accurate for smaller specs');
    lines.push('');
    lines.push('## Suggested sub-specs');
    lines.push('');
    for (const [i, sub] of suggestion.suggestedSpecs.entries()) {
        lines.push(`### ${String(i + 1)}. ${sub.suggestedTitle}`);
        lines.push('');
        lines.push(`- **Target:** ${sub.target}`);
        lines.push(`- **Scope:** ${sub.scope}`);
        lines.push(`- **Estimated hours:** ${String(sub.estimatedHours)}h`);
        lines.push(`- **Criteria:** ~${String(sub.criteriaCount)}`);
        if (sub.dependsOn.length > 0) {
            lines.push(`- **Depends on:** sub-spec(s) ${sub.dependsOn.join(', ')}`);
        }
        lines.push('');
    }
    lines.push('## What to do next');
    lines.push('');
    lines.push('1. Review the suggested sub-specs above');
    lines.push('2. Decide if you want to split or keep as one spec');
    lines.push('3. If splitting, create each sub-spec with `create_spec`');
    lines.push('4. Implement in dependency order (shared first, then dependent parts)');
    return lines.join('\n');
}
function formatForExpert(suggestion) {
    const lines = [];
    lines.push(`> ${suggestion.reason}`);
    lines.push('');
    lines.push('| # | Sub-spec | Target | Scope | Hours | Criteria | Depends On |');
    lines.push('|---|----------|--------|-------|-------|----------|------------|');
    for (const [i, sub] of suggestion.suggestedSpecs.entries()) {
        const deps = sub.dependsOn.length > 0 ? sub.dependsOn.join(', ') : '—';
        lines.push(`| ${String(i + 1)} | ${sub.suggestedTitle} | ${sub.target} | ${sub.scope} | ${String(sub.estimatedHours)}h | ${String(sub.criteriaCount)} | ${deps} |`);
    }
    lines.push('');
    lines.push('```mermaid');
    lines.push('graph LR');
    for (const [i, sub] of suggestion.suggestedSpecs.entries()) {
        const nodeId = `S${String(i + 1)}`;
        lines.push(`  ${nodeId}["${String(i + 1)}. ${sub.target}"]`);
    }
    for (const [i, sub] of suggestion.suggestedSpecs.entries()) {
        for (const dep of sub.dependsOn) {
            const depIdx = parseInt(dep, 10);
            if (!isNaN(depIdx)) {
                lines.push(`  S${String(depIdx + 1)} --> S${String(i + 1)}`);
            }
        }
    }
    lines.push('```');
    return lines.join('\n');
}
//# sourceMappingURL=spec-splitter.js.map