// tools/spec-split-handler.ts — Handlers for analyze_spec_size and split_spec (SPEC-289)
import { readFile, mkdir } from 'node:fs/promises';
import { atomicWriteFile } from '../engine/safety/atomic-write-file.js';
import { dirname } from 'node:path';
import { specStore } from '../storage/index.js';
import { analyzeSpecSize, buildChildSpecs } from '../engine/spec-splitter.js';
import { generateProgressContent } from '../engine/progress-writer.js';
import { detectOverSpec } from '../engine/complexity-budget/index.js';
// ---------------------------------------------------------------------------
// analyze_spec_size
// ---------------------------------------------------------------------------
export async function handleAnalyzeSpecSize(input) {
    const { projectId, specId } = input;
    const spec = await specStore.getSpec(projectId, specId);
    if (!spec) {
        return {
            content: [{ type: 'text', text: `Spec "${specId}" not found in project "${projectId}"` }],
            isError: true,
        };
    }
    // Read spec.md to extract real acceptance criteria
    const specWithCriteria = await enrichSpecWithCriteria(spec);
    const analysis = analyzeSpecSize(specWithCriteria);
    // SPEC-614: over-spec detection (criteria/hours mismatch + criteria/files ratio)
    const filesCount = spec.impactAnalysis?.affectedFiles.length ?? 0;
    const overSpec = detectOverSpec(analysis.criteriaCount, filesCount, analysis.estimatedHours);
    const lines = [
        `# Spec Size Analysis — ${specId}`,
        '',
        `**Spec**: ${spec.title}`,
        `**Criteria count**: ${String(analysis.criteriaCount)}`,
        `**Estimated hours**: ${String(analysis.estimatedHours)}h`,
        `**Criteria/files ratio**: ${overSpec.criteriaToFilesRatio.toFixed(2)}`,
        '',
    ];
    if (overSpec.isOverSpecified) {
        lines.push('## Over-specification Warnings');
        lines.push('');
        for (const w of overSpec.warnings) {
            lines.push(`- **${w.type}**: ${w.message}`);
            lines.push(`  - Suggestion: ${w.suggestion}`);
        }
        lines.push('');
    }
    if (analysis.shouldSplit) {
        lines.push('## Split Recommended');
        lines.push('');
        lines.push('**Reasons:**');
        for (const reason of analysis.reasons) {
            lines.push(`- ${reason}`);
        }
        lines.push('');
        if (analysis.ambiguousCriteria.length > 0) {
            lines.push('## Ambiguous Criteria Detected');
            lines.push('');
            for (const ac of analysis.ambiguousCriteria) {
                lines.push(`- ${ac}`);
            }
            lines.push('');
        }
        lines.push('## Suggested Splits');
        lines.push('');
        for (const [i, proposal] of analysis.suggestedSplits.entries()) {
            lines.push(`### ${String(i + 1)}. ${proposal.title}`);
            lines.push(proposal.description);
            if (proposal.dependsOn.length > 0) {
                lines.push(`**Depends on**: splits ${proposal.dependsOn.map((d) => String(d + 1)).join(', ')}`);
            }
            lines.push('');
        }
        lines.push('Run `split_spec` to execute the division.');
    }
    else {
        lines.push('## No Split Needed');
        lines.push('');
        lines.push('This spec is appropriately sized and does not require splitting.');
    }
    return {
        content: [
            {
                type: 'text',
                text: lines.join('\n'),
            },
        ],
        structuredContent: {
            specId: analysis.specId,
            specTitle: analysis.specTitle,
            shouldSplit: analysis.shouldSplit,
            reasons: analysis.reasons,
            suggestedSplits: analysis.suggestedSplits,
            criteriaCount: analysis.criteriaCount,
            estimatedHours: analysis.estimatedHours,
            ambiguousCriteria: analysis.ambiguousCriteria,
            overSpecification: {
                isOverSpecified: overSpec.isOverSpecified,
                warnings: overSpec.warnings,
                criteriaToFilesRatio: overSpec.criteriaToFilesRatio,
            },
        },
    };
}
// ---------------------------------------------------------------------------
// split_spec
// ---------------------------------------------------------------------------
export async function handleSplitSpec(input) {
    const { projectId, specId, proposedSplits } = input;
    const spec = await specStore.getSpec(projectId, specId);
    if (!spec) {
        return {
            content: [{ type: 'text', text: `Spec "${specId}" not found in project "${projectId}"` }],
            isError: true,
        };
    }
    // Resolve proposals: use provided or auto-generate
    let proposals;
    if (proposedSplits && proposedSplits.length > 0) {
        proposals = proposedSplits;
    }
    else {
        const specWithCriteria = await enrichSpecWithCriteria(spec);
        const analysis = analyzeSpecSize(specWithCriteria);
        proposals = analysis.suggestedSplits;
    }
    if (proposals.length === 0) {
        return {
            content: [
                {
                    type: 'text',
                    text: `No split proposals available for spec "${specId}". Run analyze_spec_size first.`,
                },
            ],
            isError: true,
        };
    }
    // Load all existing specs to determine next available IDs
    const allSpecs = await specStore.listSpecs(projectId);
    const maxId = Math.max(0, ...allSpecs.map((s) => {
        const match = /^SPEC-(\d+)$/.exec(s.id);
        return match?.[1] ? parseInt(match[1], 10) : 0;
    }));
    const nextIdFn = (index) => {
        const num = maxId + index + 1;
        return `SPEC-${String(num).padStart(3, '0')}`;
    };
    // Build child specs
    const childSpecs = buildChildSpecs(spec, proposals, nextIdFn);
    const childIds = childSpecs.map((c) => c.id);
    // Persist child specs and create their directories.
    // Technical and Progress content are embedded in the unified spec.md
    // (SPEC-1010 PR-C: stop writing legacy technical.md / progress.md files).
    for (const child of childSpecs) {
        await mkdir(dirname(child.specPath), { recursive: true });
        const content = buildChildUnifiedContent(child, spec);
        await atomicWriteFile(child.specPath, content);
        // NOTE: child.technicalPath / child.progressPath remain populated in the
        // in-memory Spec object for type compatibility (PR-D will remove fields).
        await specStore.createSpec(projectId, child);
    }
    // Mark original as superseded
    // SPEC-720: Split supersedes the original spec — use __internalSetStatus for status,
    // then updateSpec for non-status fields separately.
    await specStore.__internalSetStatus(projectId, specId, 'superseded');
    await specStore.updateSpec(projectId, specId, {
        reviewNotes: [...(spec.reviewNotes ?? []), `Superseded by: ${childIds.join(', ')}`],
    });
    const lines = [
        `# Split Complete — ${specId}`,
        '',
        `**Original spec** \`${specId}\` has been marked as \`superseded\`.`,
        '',
        `## Child Specs Created (${String(childSpecs.length)})`,
        '',
    ];
    for (const child of childSpecs) {
        lines.push(`- \`${child.id}\` — ${child.title}`);
    }
    lines.push('');
    lines.push('Each child spec inherits tags and target from the original.');
    lines.push('Implement child specs in dependency order.');
    return {
        content: [{ type: 'text', text: lines.join('\n') }],
        structuredContent: {
            originalSpecId: specId,
            supersededBy: childIds,
            childSpecIds: childIds,
            childTitles: childSpecs.map((c) => c.title),
            message: `Spec ${specId} split into ${String(childSpecs.length)} child specs`,
        },
    };
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
/** Read spec.md and extract acceptance criteria lines as reviewNotes proxy */
async function enrichSpecWithCriteria(spec) {
    /* v8 ignore start -- filesystem reads tested via integration */
    try {
        const content = await readFile(spec.specPath, 'utf-8');
        const criteria = extractAcFromContent(content);
        return {
            ...spec,
            reviewNotes: criteria.length > 0 ? criteria : (spec.reviewNotes ?? []),
        };
    }
    catch {
        return spec;
    }
    /* v8 ignore stop */
}
/** Extract acceptance criteria from spec.md content */
function extractAcFromContent(content) {
    const acSection = /##\s+Acceptance Criteria\s*\n([\s\S]*?)(?=\n##\s|$)/i.exec(content);
    if (!acSection?.[1]) {
        return [];
    }
    return acSection[1]
        .split('\n')
        .map((line) => line.replace(/^[-*]\s*\[[ x]\]\s*/i, '').trim())
        .filter((line) => line.length > 10);
}
/**
 * Build a unified spec.md for a child spec that includes ## Technical and
 * ## Progress sections inline (SPEC-1010 PR-C: no separate technical.md /
 * progress.md files are written).
 */
function buildChildUnifiedContent(child, original) {
    const specBody = buildChildSpecContent(child, original);
    const technicalBody = buildChildTechnicalContent(child, original);
    const progressBody = generateProgressContent(child);
    // Strip any YAML frontmatter that technical/progress generators might emit
    const stripFm = (s) => s.replace(/^---\n[\s\S]*?\n---\n?/, '').trim();
    return [
        specBody.trimEnd(),
        '',
        '## Technical',
        '',
        stripFm(technicalBody),
        '',
        '## Progress',
        '',
        stripFm(progressBody),
        '',
    ].join('\n');
}
/** Generate minimal spec.md content for a child spec */
function buildChildSpecContent(child, original) {
    const now = new Date().toISOString().split('T')[0] ?? '';
    return [
        '---',
        `id: ${child.id}`,
        `title: "${child.title}"`,
        `type: ${child.type}`,
        `scope: ${child.scope}`,
        `target: ${child.target}`,
        `status: draft`,
        `difficulty: ${String(child.difficulty)}`,
        `risk: ${child.risk}`,
        `tags: [${child.tags.map((t) => JSON.stringify(t)).join(', ')}]`,
        `created: ${now}`,
        '---',
        '',
        `# ${child.id}: ${child.title}`,
        '',
        '## Description',
        '',
        child.reviewNotes?.[0] ?? `Split from ${original.id}: ${original.title}`,
        '',
        '## Acceptance Criteria',
        '',
        '- [ ] Implementation matches spec requirements',
        '- [ ] Tests written and passing',
        '',
        '## Out of Scope',
        '',
        `- Criteria addressed by sibling specs split from ${original.id}`,
        '',
    ].join('\n');
}
/** Generate minimal technical.md content for a child spec */
function buildChildTechnicalContent(child, original) {
    return [
        `# ${child.id}: ${child.title} — Technical`,
        '',
        `## Parent Spec`,
        '',
        `Split from \`${original.id}\`: ${original.title}`,
        '',
        '## Estimation',
        '',
        `| Dev Hours | ${String(child.estimation.devHours)}h |`,
        `|-----------|--------|`,
        `| Review Hours | ${String(child.estimation.reviewHours)}h |`,
        '',
    ].join('\n');
}
//# sourceMappingURL=spec-split-handler.js.map