/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { ensureDir } from './file-writer.js';
import { computeBenchmark, listEstimates } from './workflow-benchmark.js';
const HALF_LIFE_DAYS = 30;
const MIN_DATA_POINTS = 3;
const SIZING_ORDER = ['xs', 's', 'm', 'l', 'xl'];
function recencyWeight(completedAt, now) {
    const ageMs = now - new Date(completedAt).getTime();
    const ageDays = ageMs / (1000 * 60 * 60 * 24);
    return Math.pow(0.5, ageDays / HALF_LIFE_DAYS);
}
function median(values) {
    if (values.length === 0)
        return 0;
    const sorted = [...values].sort((a, b) => a - b);
    const mid = Math.floor(sorted.length / 2);
    return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
}
export function computeCalibration(cwd) {
    const now = Date.now();
    const estimates = listEstimates(cwd);
    const entries = [];
    for (const est of estimates) {
        try {
            const benchmark = computeBenchmark(cwd, est.storyId);
            if (benchmark.actualDays === null || benchmark.status !== 'complete')
                continue;
            const estimatedDays = est.aiGuidedDays ?? est.aiUnguidedEstimateDays;
            const driftPct = Math.round(((benchmark.actualDays - estimatedDays) / estimatedDays) * 100);
            const completedAt = est.declaredAt;
            entries.push({
                storyId: est.storyId,
                sizingLabel: est.sizingLabel,
                estimatedDays,
                actualDays: benchmark.actualDays,
                driftPct,
                weight: recencyWeight(completedAt, now),
                completedAt,
            });
        }
        catch {
            // skip stories where computation fails
        }
    }
    const bySizing = new Map();
    for (const entry of entries) {
        const list = bySizing.get(entry.sizingLabel) ?? [];
        list.push(entry);
        bySizing.set(entry.sizingLabel, list);
    }
    const categories = [];
    for (const sizingLabel of SIZING_ORDER) {
        const catEntries = bySizing.get(sizingLabel);
        if (!catEntries || catEntries.length === 0)
            continue;
        const drifts = catEntries.map((e) => e.driftPct);
        const totalWeight = catEntries.reduce((sum, e) => sum + e.weight, 0);
        const weightedAvgDriftPct = totalWeight > 0
            ? Math.round(catEntries.reduce((sum, e) => sum + e.driftPct * e.weight, 0) / totalWeight)
            : 0;
        categories.push({
            sizingLabel,
            count: catEntries.length,
            avgDriftPct: Math.round(drifts.reduce((a, b) => a + b, 0) / drifts.length),
            weightedAvgDriftPct,
            medianDriftPct: Math.round(median(drifts)),
            minDriftPct: Math.min(...drifts),
            maxDriftPct: Math.max(...drifts),
            entries: catEntries,
        });
    }
    const lessons = categories
        .filter((cat) => cat.count >= MIN_DATA_POINTS)
        .map((cat) => buildLesson(cat));
    return {
        categories,
        lessons,
        totalDataPoints: entries.length,
        halfLifeDays: HALF_LIFE_DAYS,
        generatedAt: new Date(now).toISOString(),
    };
}
function buildLesson(stats) {
    const drift = stats.weightedAvgDriftPct;
    let direction;
    if (drift > 10)
        direction = 'under';
    else if (drift < -10)
        direction = 'over';
    else
        direction = 'accurate';
    const magnitude = Math.abs(drift);
    let suggestion;
    if (direction === 'under') {
        suggestion = `${stats.sizingLabel.toUpperCase()} stories typically take ${magnitude}% longer than estimated. Consider increasing estimates by ~${Math.round(magnitude * 0.75)}%.`;
    }
    else if (direction === 'over') {
        suggestion = `${stats.sizingLabel.toUpperCase()} stories typically finish ${magnitude}% faster than estimated. Consider reducing estimates by ~${Math.round(magnitude * 0.75)}%.`;
    }
    else {
        suggestion = `${stats.sizingLabel.toUpperCase()} estimates are well-calibrated (within 10% drift).`;
    }
    let confidence;
    if (stats.count >= 10)
        confidence = 'high';
    else if (stats.count >= 5)
        confidence = 'medium';
    else
        confidence = 'low';
    return {
        sizingLabel: stats.sizingLabel,
        direction,
        magnitude,
        suggestion,
        confidence,
        sampleSize: stats.count,
    };
}
export function generateLessonsMarkdown(result) {
    const lines = [
        '# Velocity Calibration Lessons',
        '',
        `Generated: ${result.generatedAt}`,
        `Data points: ${result.totalDataPoints} | Half-life: ${result.halfLifeDays} days`,
        '',
    ];
    if (result.lessons.length === 0) {
        lines.push('> Insufficient data (need at least 3 completed stories per sizing category).');
        return lines.join('\n');
    }
    lines.push('## Lessons by Sizing', '');
    for (const lesson of result.lessons) {
        const icon = lesson.direction === 'under' ? '⚠️' : lesson.direction === 'over' ? '✅' : '🎯';
        lines.push(`### ${icon} ${lesson.sizingLabel.toUpperCase()} (n=${lesson.sampleSize}, confidence: ${lesson.confidence})`);
        lines.push('');
        lines.push(lesson.suggestion);
        lines.push('');
    }
    lines.push('## Raw Statistics', '');
    lines.push('| Sizing | Count | Weighted Drift | Median Drift | Range |');
    lines.push('|--------|-------|----------------|--------------|-------|');
    for (const cat of result.categories) {
        lines.push(`| ${cat.sizingLabel.toUpperCase()} | ${cat.count} | ${cat.weightedAvgDriftPct > 0 ? '+' : ''}${cat.weightedAvgDriftPct}% | ${cat.medianDriftPct > 0 ? '+' : ''}${cat.medianDriftPct}% | ${cat.minDriftPct}% to ${cat.maxDriftPct}% |`);
    }
    lines.push('');
    return lines.join('\n');
}
export function writeCalibrationLessons(cwd, result) {
    const dir = join(cwd, '.agent-workflow');
    ensureDir(dir);
    const filePath = join(dir, 'calibration-lessons.md');
    const content = generateLessonsMarkdown(result);
    writeFileSync(filePath, content, 'utf8');
    return filePath;
}
export function inlineCalibrationHint(cwd, sizingLabel) {
    try {
        const result = computeCalibration(cwd);
        const lesson = result.lessons.find((l) => l.sizingLabel === sizingLabel);
        if (!lesson)
            return null;
        if (lesson.direction === 'accurate')
            return null;
        const verb = lesson.direction === 'under' ? 'longer' : 'faster';
        return `Calibration: ${sizingLabel.toUpperCase()} stories run ~${lesson.magnitude}% ${verb} than estimated (n=${lesson.sampleSize}).`;
    }
    catch {
        return null;
    }
}
//# sourceMappingURL=velocity-calibration.js.map