/*
 * 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 { listTasks } from './task-state.js';
import { historicalEstimateStats, listEstimates, readEstimate } from './workflow-benchmark.js';
const TOKENS_PER_DAY = 50_000;
const CENTS_PER_DAY = 150;
const MINUTES_PER_DAY = 480;
const PHASE_FRACTION = 0.4;
const SIZING_DEFAULTS = {
    xs: 0.25,
    s: 0.5,
    m: 1,
    l: 2,
    xl: 5,
};
function resolveConfidence(dataPoints) {
    if (dataPoints >= 7)
        return 'high';
    if (dataPoints >= 3)
        return 'medium';
    return 'low';
}
function recencyWeightedDays(estimates, sizingLabel) {
    const relevant = sizingLabel ? estimates.filter((e) => e.sizingLabel === sizingLabel) : estimates;
    if (relevant.length === 0)
        return 0;
    const sorted = [...relevant].sort((a, b) => new Date(a.declaredAt).getTime() - new Date(b.declaredAt).getTime());
    let weightedSum = 0;
    let totalWeight = 0;
    for (let i = 0; i < sorted.length; i++) {
        const weight = i + 1;
        const days = sorted[i].aiGuidedDays ?? sorted[i].aiUnguidedEstimateDays;
        weightedSum += days * weight;
        totalWeight += weight;
    }
    return totalWeight > 0 ? weightedSum / totalWeight : 0;
}
function applyCalibrationDrift(baseDays, cwd) {
    const stats = historicalEstimateStats(cwd);
    if (stats.count < 2 || stats.calibrationNote === null)
        return baseDays;
    const overMatch = stats.calibrationNote.match(/ran (\d+)% over/);
    const underMatch = stats.calibrationNote.match(/ran (\d+)% under/);
    if (overMatch) {
        const pct = parseInt(overMatch[1], 10);
        return baseDays * (1 - pct / 200);
    }
    if (underMatch) {
        const pct = parseInt(underMatch[1], 10);
        return baseDays * (1 + pct / 200);
    }
    return baseDays;
}
function daysToBudget(days, scope, dataPoints, sizingLabel) {
    return {
        scope,
        tokens: Math.round(days * TOKENS_PER_DAY),
        costCents: Math.round(days * CENTS_PER_DAY),
        durationMinutes: Math.round(days * MINUTES_PER_DAY),
        confidence: resolveConfidence(dataPoints),
        dataPoints,
        sizingLabel,
    };
}
function projectTaskBudget(cwd, taskId) {
    const estimates = listEstimates(cwd);
    let sizingLabel;
    if (taskId) {
        const taskEstimate = readEstimate(cwd, taskId);
        sizingLabel = taskEstimate?.sizingLabel;
    }
    const dataPoints = estimates.length;
    let baseDays;
    if (dataPoints > 0) {
        baseDays = recencyWeightedDays(estimates, sizingLabel);
        if (baseDays === 0) {
            baseDays = recencyWeightedDays(estimates);
        }
    }
    else {
        baseDays = SIZING_DEFAULTS[sizingLabel ?? 'm'];
    }
    if (dataPoints >= 2) {
        baseDays = applyCalibrationDrift(baseDays, cwd);
    }
    return daysToBudget(baseDays, 'task', dataPoints, sizingLabel);
}
export function projectBudget(cwd, options) {
    const { scope, taskId } = options;
    if (scope === 'phase') {
        const taskProjection = projectTaskBudget(cwd, taskId);
        return {
            ...taskProjection,
            scope: 'phase',
            tokens: Math.round(taskProjection.tokens * PHASE_FRACTION),
            costCents: Math.round(taskProjection.costCents * PHASE_FRACTION),
            durationMinutes: Math.round(taskProjection.durationMinutes * PHASE_FRACTION),
        };
    }
    if (scope === 'task') {
        return projectTaskBudget(cwd, taskId);
    }
    const openTasks = listTasks(cwd, { status: 'open' });
    const claimedTasks = listTasks(cwd, { status: 'claimed' });
    const tasks = [...openTasks, ...claimedTasks];
    if (tasks.length === 0) {
        return daysToBudget(0, 'sprint', 0);
    }
    const estimates = listEstimates(cwd);
    const dataPoints = estimates.length;
    let totalDays = 0;
    for (const task of tasks) {
        const taskProjection = projectTaskBudget(cwd, task.id);
        totalDays += taskProjection.durationMinutes / MINUTES_PER_DAY;
    }
    return daysToBudget(totalDays, 'sprint', dataPoints);
}
//# sourceMappingURL=budget-projection.js.map