/**
 * engine/living-spec/auto-updater.ts — SPEC-170: Auto-update specs from code changes
 *
 * Pure functions that analyse a git-style code diff, map changed functions to
 * acceptance criteria, and propose (or apply) spec updates.
 *
 * Design:
 * - mapDiffToAcs: heuristic matching between diff functions and AC text
 * - generateSpecUpdate: applies proposals onto existing spec content
 * - autoUpdateSpecFromCode: orchestrates the full pipeline
 */
// ---------------------------------------------------------------------------
// Diff parsing
// ---------------------------------------------------------------------------
/**
 * Extract function-level changes from a unified diff string.
 * Detects `+function`, `-function`, `+async function`, `+export function`, etc.
 */
export function parseDiffFunctions(diff) {
    const functions = [];
    const lines = diff.split('\n');
    const funcPattern = /^([+-])\s*(?:export\s+)?(?:async\s+)?(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(?)/;
    for (const line of lines) {
        const match = funcPattern.exec(line);
        if (!match) {
            continue;
        }
        const prefix = match[1];
        const name = match[2] ?? match[3] ?? '';
        if (!name) {
            continue;
        }
        const changeType = prefix === '+' ? 'added' : 'removed';
        const snippet = line.slice(1).trim();
        // Merge with previous entry if same name appears as both removed then added (= modified)
        const existing = functions.find((f) => f.name === name);
        if (existing) {
            existing.changeType = 'modified';
        }
        else {
            functions.push({ name, changeType, snippet });
        }
    }
    return functions;
}
// ---------------------------------------------------------------------------
// AC extraction helpers
// ---------------------------------------------------------------------------
/**
 * Extract acceptance criteria lines from spec markdown content.
 * Looks for ALL checklist items (`- [ ] ...` or `- [x] ...`) in the document,
 * regardless of section. Returns them with sequential numeric counters.
 *
 * Note: differs from the shared `extractAcceptanceCriteria` (ac-extractor.ts) which
 * requires an explicit "Acceptance Criteria" section header and is used for diffing.
 */
export function extractAcsFromSpec(specContent) {
    const acs = [];
    let counter = 0;
    for (const line of specContent.split('\n')) {
        const acMatch = /^-\s+\[[ xX]\]\s+(.+)$/.exec(line.trim());
        if (acMatch) {
            counter++;
            acs.push({ number: counter, description: (acMatch[1] ?? '').trim() });
        }
    }
    return acs;
}
// ---------------------------------------------------------------------------
// Keyword-based confidence scoring
// ---------------------------------------------------------------------------
/**
 * Compute a similarity score (0-1) between a function name and an AC description.
 * Uses word overlap and camelCase decomposition.
 */
function scoreSimilarity(funcName, acDescription) {
    // Decompose camelCase into words
    const funcWords = funcName
        .replace(/([A-Z])/g, ' $1')
        .toLowerCase()
        .split(/[\s_-]+/)
        .filter(Boolean);
    const acWords = acDescription
        .toLowerCase()
        .replace(/[^a-z0-9\s]/g, ' ')
        .split(/\s+/)
        .filter((w) => w.length > 2);
    if (funcWords.length === 0 || acWords.length === 0) {
        return 0;
    }
    const funcSet = new Set(funcWords);
    const matches = acWords.filter((w) => funcSet.has(w)).length;
    return matches / Math.max(funcWords.length, acWords.length);
}
/**
 * Determine confidence level from a numeric score.
 */
function scoreToConfidence(score) {
    if (score >= 0.4) {
        return 'high';
    }
    if (score >= 0.2) {
        return 'medium';
    }
    return 'low';
}
// ---------------------------------------------------------------------------
// Core mapping
// ---------------------------------------------------------------------------
/**
 * Map diff functions to acceptance criteria using keyword heuristics.
 *
 * For each changed function:
 * - If it matches one or more ACs → emit 'mark-covered' or 'update-progress'
 * - If removed and matches an AC → emit 'flag-obsolete'
 * - If no AC matches at all → emit 'suggest-new-ac'
 */
export function mapDiffToAcs(functions, acs, config = { minConfidence: 'low', suggestNewAcs: true, flagObsolete: true }) {
    const mappings = [];
    const confidenceOrder = { high: 3, medium: 2, low: 1 };
    const minOrder = confidenceOrder[config.minConfidence];
    for (const fn of functions) {
        let bestScore = 0;
        let bestAc = null;
        for (const ac of acs) {
            const score = scoreSimilarity(fn.name, ac.description);
            if (score > bestScore) {
                bestScore = score;
                bestAc = ac;
            }
        }
        const confidence = scoreToConfidence(bestScore);
        const order = confidenceOrder[confidence];
        if (bestAc !== null && order >= minOrder) {
            const updateType = resolveUpdateType(fn.changeType, confidence);
            if (config.flagObsolete || updateType !== 'flag-obsolete') {
                mappings.push({
                    diffFunction: fn,
                    acNumber: bestAc.number,
                    acDescription: bestAc.description,
                    updateType,
                    confidence,
                });
            }
        }
        else if (fn.changeType === 'added' && config.suggestNewAcs) {
            // New function with no matching AC → suggest a new AC
            mappings.push({
                diffFunction: fn,
                acNumber: 0,
                acDescription: '',
                updateType: 'suggest-new-ac',
                confidence: 'low',
            });
        }
    }
    return mappings;
}
/** Determine the update type from change type and confidence. */
function resolveUpdateType(changeType, confidence) {
    if (changeType === 'removed') {
        return 'flag-obsolete';
    }
    if (confidence === 'high') {
        return 'mark-covered';
    }
    return 'update-progress';
}
// ---------------------------------------------------------------------------
// Spec content patching
// ---------------------------------------------------------------------------
/**
 * Build SpecUpdateProposals from AcMappings.
 */
export function buildProposals(mappings, specId) {
    const proposals = [];
    for (const mapping of mappings) {
        if (mapping.updateType === 'suggest-new-ac') {
            proposals.push({
                acNumber: 0,
                description: `New function "${mapping.diffFunction.name}" has no matching AC — consider adding one`,
                action: 'suggest-new-ac',
                patch: `- [ ] [AUTO] Implement \`${mapping.diffFunction.name}\``,
            });
            continue;
        }
        if (mapping.updateType === 'flag-obsolete') {
            proposals.push({
                acNumber: mapping.acNumber,
                description: `Function "${mapping.diffFunction.name}" was removed — AC-${mapping.acNumber} may be obsolete`,
                action: 'flag-obsolete',
                patch: `<!-- POSSIBLY OBSOLETE: AC-${mapping.acNumber} (${mapping.acDescription}) — function "${mapping.diffFunction.name}" was removed. Verify. -->`,
            });
            continue;
        }
        if (mapping.updateType === 'mark-covered') {
            proposals.push({
                acNumber: mapping.acNumber,
                description: `Function "${mapping.diffFunction.name}" implements AC-${mapping.acNumber} — mark as covered`,
                action: 'mark-covered',
                patch: `<!-- AUTO-COVERED by ${specId} via \`${mapping.diffFunction.name}\` -->`,
            });
            continue;
        }
        // update-progress
        proposals.push({
            acNumber: mapping.acNumber,
            description: `Function "${mapping.diffFunction.name}" modified — AC-${mapping.acNumber} progress updated`,
            action: 'update-progress',
            patch: `<!-- PROGRESS UPDATE: AC-${mapping.acNumber} affected by \`${mapping.diffFunction.name}\` change -->`,
        });
    }
    return proposals;
}
/**
 * Apply proposals onto existing spec content and return the updated string.
 *
 * Strategy: append a changelog section at the end of the spec with all patches.
 * Marking individual ACs in-place would require complex line manipulation, so
 * instead we append a structured "Auto-sync changelog" section.
 */
export function generateSpecUpdate(proposals, specContent) {
    if (proposals.length === 0) {
        return specContent;
    }
    const now = new Date().toISOString();
    const lines = [
        '',
        '<!-- AUTO-SYNC CHANGELOG — generated by auto_update_spec -->',
        `<!-- ${now} -->`,
    ];
    for (const proposal of proposals) {
        lines.push(`<!-- [${proposal.action.toUpperCase()}] AC-${proposal.acNumber || 'NEW'}: ${proposal.description} -->`);
        if (proposal.patch) {
            lines.push(proposal.patch);
        }
    }
    lines.push('<!-- END AUTO-SYNC CHANGELOG -->');
    return `${specContent}\n${lines.join('\n')}\n`;
}
// ---------------------------------------------------------------------------
// Main orchestrator
// ---------------------------------------------------------------------------
/**
 * Analyse a code diff and produce auto-update proposals for a spec.
 *
 * @param specId - The spec to update.
 * @param codeDiff - Unified diff string. If empty, returns empty result.
 * @param specContent - Current markdown content of the spec.
 * @param autoApply - When true, apply proposals and return the updated content.
 * @param config - Optional configuration overrides.
 */
export function autoUpdateSpecFromCode(specId, codeDiff, specContent, autoApply = false, config = { minConfidence: 'low', suggestNewAcs: true, flagObsolete: true }) {
    const analyzedAt = new Date().toISOString();
    if (!codeDiff.trim()) {
        return {
            specId,
            mappings: [],
            proposals: [],
            updatedSpecContent: null,
            applied: false,
            summary: 'No code diff provided — nothing to analyse.',
            analyzedAt,
        };
    }
    const functions = parseDiffFunctions(codeDiff);
    const acs = extractAcsFromSpec(specContent);
    const mappings = mapDiffToAcs(functions, acs, config);
    const proposals = buildProposals(mappings, specId);
    let updatedSpecContent = null;
    let applied = false;
    if (autoApply && proposals.length > 0) {
        updatedSpecContent = generateSpecUpdate(proposals, specContent);
        applied = true;
    }
    const summary = buildSummary(functions, mappings, proposals, applied);
    return {
        specId,
        mappings,
        proposals,
        updatedSpecContent,
        applied,
        summary,
        analyzedAt,
    };
}
// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------
function buildSummary(functions, mappings, proposals, applied) {
    const added = functions.filter((f) => f.changeType === 'added').length;
    const modified = functions.filter((f) => f.changeType === 'modified').length;
    const removed = functions.filter((f) => f.changeType === 'removed').length;
    const newAcCount = proposals.filter((p) => p.action === 'suggest-new-ac').length;
    const coveredCount = proposals.filter((p) => p.action === 'mark-covered').length;
    const obsoleteCount = proposals.filter((p) => p.action === 'flag-obsolete').length;
    const progressCount = proposals.filter((p) => p.action === 'update-progress').length;
    const parts = [
        `Analysed ${functions.length} function change(s): ${added} added, ${modified} modified, ${removed} removed.`,
        `Mapped to ${mappings.length} AC(s).`,
        `Proposals: ${coveredCount} mark-covered, ${progressCount} update-progress, ${obsoleteCount} flag-obsolete, ${newAcCount} suggest-new-ac.`,
    ];
    if (applied) {
        parts.push('Changes applied to spec content.');
    }
    else if (proposals.length > 0) {
        parts.push('Use autoApply=true to apply changes.');
    }
    return parts.join(' ');
}
//# sourceMappingURL=auto-updater.js.map