// engine/velocity/measure-velocity.ts — Compute VelocityProfile from real project actuals (SPEC-555)
// Pure async function: reads specs from storage, computes calibrated day estimates.
import { hashProjectPath } from '../../storage/index.js';
import { listSpecs } from '../../storage/spec-store.js';
// ---------------------------------------------------------------------------
// Default profile used when insufficient data is available
// ---------------------------------------------------------------------------
const DEFAULT_VELOCITY_PROFILE = {
    specsPerDay: 2,
    hoursPerDay: 4,
    avgHoursByDifficulty: { 1: 1, 2: 2, 3: 4, 4: 8, 5: 16 },
    calibrationDate: new Date().toISOString(),
    windowDays: 30,
    sampleSize: 0,
    confidence: 'low',
};
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function classifyConfidence(sampleSize) {
    if (sampleSize > 20) {
        return 'high';
    }
    if (sampleSize >= 5) {
        return 'medium';
    }
    return 'low';
}
function safeAvg(values) {
    if (values.length === 0) {
        return 0;
    }
    return values.reduce((a, b) => a + b, 0) / values.length;
}
// ---------------------------------------------------------------------------
// measureVelocity
// ---------------------------------------------------------------------------
/**
 * Compute a VelocityProfile from recent done specs with actuals.
 * Gracefully returns a low-confidence default when data is insufficient.
 * SPEC-555
 */
export async function measureVelocity(projectPath, windowDays = 30) {
    const projectId = hashProjectPath(projectPath);
    const allSpecs = await listSpecs(projectId).catch(() => []);
    const windowStart = new Date();
    windowStart.setUTCDate(windowStart.getUTCDate() - windowDays);
    // Filter: done specs inside the window that have real actuals
    const doneSpecs = allSpecs.filter((s) => {
        if (s.status !== 'done') {
            return false;
        }
        if (!s.actuals || s.actuals.devHours <= 0) {
            return false;
        }
        const doneAt = new Date(s.updatedAt);
        return doneAt >= windowStart;
    });
    const sampleSize = doneSpecs.length;
    if (sampleSize < 3) {
        return {
            ...DEFAULT_VELOCITY_PROFILE,
            calibrationDate: new Date().toISOString(),
            windowDays,
            sampleSize,
        };
    }
    // specsPerDay = completed specs / window days
    const specsPerDay = sampleSize / windowDays;
    // hoursPerDay = sum of actual devHours / window days
    const totalActualHours = doneSpecs.reduce((acc, s) => acc + (s.actuals?.devHours ?? 0), 0);
    const hoursPerDay = totalActualHours / windowDays;
    // avgHoursByDifficulty: group actuals by difficulty
    const hoursByDiff = { 1: [], 2: [], 3: [], 4: [], 5: [] };
    for (const s of doneSpecs) {
        const diff = s.difficulty;
        const hours = s.actuals?.devHours ?? 0;
        if (diff >= 1 && diff <= 5) {
            hoursByDiff[diff].push(hours);
        }
    }
    // Merge with defaults so every difficulty level has a value
    const defaults = DEFAULT_VELOCITY_PROFILE.avgHoursByDifficulty;
    const avgHoursByDifficulty = {
        1: hoursByDiff[1].length > 0 ? safeAvg(hoursByDiff[1]) : defaults[1],
        2: hoursByDiff[2].length > 0 ? safeAvg(hoursByDiff[2]) : defaults[2],
        3: hoursByDiff[3].length > 0 ? safeAvg(hoursByDiff[3]) : defaults[3],
        4: hoursByDiff[4].length > 0 ? safeAvg(hoursByDiff[4]) : defaults[4],
        5: hoursByDiff[5].length > 0 ? safeAvg(hoursByDiff[5]) : defaults[5],
    };
    return {
        specsPerDay,
        hoursPerDay,
        avgHoursByDifficulty,
        calibrationDate: new Date().toISOString(),
        windowDays,
        sampleSize,
        confidence: classifyConfidence(sampleSize),
    };
}
// ---------------------------------------------------------------------------
// Conversion helpers
// ---------------------------------------------------------------------------
/**
 * Convert estimated devHours to calendar days using a VelocityProfile.
 * Returns 1 decimal place.
 * SPEC-555
 */
export function devHoursToCalendarDays(hours, profile) {
    const effectiveHoursPerDay = profile.hoursPerDay > 0 ? profile.hoursPerDay : 4;
    return Math.round((hours / effectiveHoursPerDay) * 10) / 10;
}
/**
 * Format a human-readable calendar estimate string.
 * Example: "~0.5 days at your velocity (9.0 SPECs/day, high confidence)"
 * SPEC-555
 */
export function formatCalendarEstimate(hours, profile) {
    const days = devHoursToCalendarDays(hours, profile);
    const velocityInfo = `${profile.specsPerDay.toFixed(1)} SPECs/day`;
    return `~${String(days)} day${days !== 1 ? 's' : ''} at your velocity (${velocityInfo}, ${profile.confidence} confidence)`;
}
//# sourceMappingURL=measure-velocity.js.map