"use strict";
/**
 * Claude Quota Response Normalization Helpers
 *
 * Parses Anthropic policy-limits or OAuth-usage payloads into normalized windows and core usage summary.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildClaudeCoreUsageSummary = exports.pickMostRestrictiveClaudeWeeklyWindow = exports.isClaudeWeeklyRateLimitType = exports.buildClaudeQuotaWindows = void 0;
const percentage_1 = require("../../utils/percentage");
// Distinguishes epoch milliseconds from seconds (1e12 ~= 2001-09-09T01:46:40Z).
const EPOCH_MS_THRESHOLD = 1e12;
function asString(value) {
    return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
}
function asBoolean(value) {
    if (typeof value === 'boolean')
        return value;
    if (typeof value === 'string') {
        if (value === 'true')
            return true;
        if (value === 'false')
            return false;
    }
    return undefined;
}
function asNumber(value) {
    if (typeof value === 'number' && isFinite(value))
        return value;
    if (typeof value === 'string') {
        const parsed = Number(value);
        return isFinite(parsed) ? parsed : null;
    }
    return null;
}
function normalizeTimestamp(value) {
    const asNum = asNumber(value);
    if (asNum !== null) {
        const millis = asNum > EPOCH_MS_THRESHOLD ? asNum : asNum * 1000;
        const date = new Date(millis);
        return isNaN(date.getTime()) ? null : date.toISOString();
    }
    const str = asString(value);
    if (!str)
        return null;
    // Numeric strings can be either epoch seconds or epoch milliseconds.
    if (/^\d+$/.test(str)) {
        const numeric = Number(str);
        if (isFinite(numeric)) {
            const millis = numeric > EPOCH_MS_THRESHOLD ? numeric : numeric * 1000;
            const date = new Date(millis);
            return isNaN(date.getTime()) ? null : date.toISOString();
        }
    }
    const date = new Date(str);
    return isNaN(date.getTime()) ? null : date.toISOString();
}
function getClaudeWindowLabel(rateLimitType) {
    switch (rateLimitType) {
        case 'five_hour':
            return 'Session limit';
        case 'seven_day':
            return 'Weekly limit';
        case 'seven_day_opus':
            return 'Opus limit';
        case 'seven_day_sonnet':
            return 'Sonnet limit';
        case 'seven_day_oauth_apps':
            return 'OAuth apps limit';
        case 'seven_day_cowork':
            return 'Cowork limit';
        case 'overage':
            return 'Extra usage';
        default:
            if (!rateLimitType)
                return 'Unknown limit';
            return rateLimitType
                .split(/[_-]+/g)
                .filter((part) => part.length > 0)
                .map((part) => /^\d+$/.test(part) ? part : `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
                .join(' ');
    }
}
function clampUnit(value) {
    return Math.max(0, Math.min(1, value));
}
function normalizeUtilization(raw, unit) {
    const utilizationRaw = asNumber(raw['utilization']);
    const usedPercentRaw = asNumber(raw['usedPercent'] ?? raw['used_percent']);
    const remainingPercentRaw = asNumber(raw['remainingPercent'] ?? raw['remaining_percent']);
    if (utilizationRaw !== null) {
        const usedPercent = unit === 'ratio'
            ? (0, percentage_1.clampPercent)(clampUnit(utilizationRaw) * 100)
            : (0, percentage_1.clampPercent)(utilizationRaw);
        return {
            utilization: usedPercent / 100,
            usedPercent,
            remainingPercent: (0, percentage_1.clampPercent)(100 - usedPercent),
        };
    }
    if (usedPercentRaw !== null) {
        const usedPercent = (0, percentage_1.clampPercent)(usedPercentRaw);
        return {
            utilization: usedPercent / 100,
            usedPercent,
            remainingPercent: (0, percentage_1.clampPercent)(100 - usedPercent),
        };
    }
    if (remainingPercentRaw !== null) {
        const remainingPercent = (0, percentage_1.clampPercent)(remainingPercentRaw);
        const usedPercent = (0, percentage_1.clampPercent)(100 - remainingPercent);
        return {
            utilization: usedPercent / 100,
            usedPercent,
            remainingPercent,
        };
    }
    return {
        utilization: null,
        usedPercent: 0,
        remainingPercent: 100,
    };
}
function normalizeRateLimitType(value, fallbackKey) {
    const direct = asString(value);
    if (direct)
        return direct;
    if (fallbackKey)
        return fallbackKey;
    return 'unknown';
}
function toObject(value) {
    if (typeof value !== 'object' || value === null || Array.isArray(value))
        return null;
    return value;
}
function normalizeRestriction(raw, fallbackKey, unit) {
    const rateLimitType = normalizeRateLimitType(raw['rateLimitType'] ?? raw['rate_limit_type'] ?? raw['claim'] ?? raw['claimAbbrev'], fallbackKey);
    if (!rateLimitType || rateLimitType === 'unknown')
        return null;
    const status = asString(raw['status']) || 'unknown';
    const resetAt = normalizeTimestamp(raw['resetsAt'] ?? raw['resets_at'] ?? raw['resetAt'] ?? raw['reset_at']) ||
        null;
    const overageResetsAt = normalizeTimestamp(raw['overageResetsAt'] ??
        raw['overage_resets_at'] ??
        raw['overageResetAt'] ??
        raw['overage_reset_at']) || null;
    const { utilization, usedPercent, remainingPercent } = normalizeUtilization(raw, unit);
    return {
        rateLimitType,
        label: getClaudeWindowLabel(rateLimitType),
        status,
        utilization,
        usedPercent,
        remainingPercent,
        resetAt,
        surpassedThreshold: asBoolean(raw['surpassedThreshold'] ?? raw['surpassed_threshold']),
        severity: asString(raw['severity']) || undefined,
        overageStatus: asString(raw['overageStatus'] ?? raw['overage_status']) || undefined,
        overageResetsAt,
        overageDisabledReason: asString(raw['overageDisabledReason'] ?? raw['overage_disabled_reason']) || undefined,
        isUsingOverage: asBoolean(raw['isUsingOverage'] ?? raw['is_using_overage']),
        hasExtraUsageEnabled: asBoolean(raw['hasExtraUsageEnabled'] ?? raw['has_extra_usage_enabled']),
    };
}
/**
 * Parse raw policy limits response into normalized windows.
 * Supports both policy-limits `restrictions` payloads and OAuth usage payloads
 * keyed by window name (`five_hour`, `seven_day`, `seven_day_sonnet`, ...).
 */
function isClaudeOAuthUsageWindowCandidate(key, raw) {
    if (key === 'extra_usage')
        return false;
    if (asNumber(raw['utilization']) === null)
        return false;
    const resetAt = raw['resetsAt'] ?? raw['resets_at'] ?? raw['resetAt'] ?? raw['reset_at'] ?? null;
    return resetAt !== null && resetAt !== undefined;
}
function buildClaudeQuotaWindows(payload) {
    const rawRestrictions = payload['restrictions'];
    const windows = [];
    if (Array.isArray(rawRestrictions)) {
        for (const item of rawRestrictions) {
            const raw = toObject(item);
            if (!raw)
                continue;
            // policy-limits restrictions[] returns utilization as a 0..1 ratio.
            const window = normalizeRestriction(raw, undefined, 'ratio');
            if (window)
                windows.push(window);
        }
    }
    else if (toObject(rawRestrictions)) {
        for (const [key, value] of Object.entries(rawRestrictions)) {
            const raw = toObject(value);
            if (!raw)
                continue;
            // policy-limits restrictions{} returns utilization as a 0..1 ratio.
            const window = normalizeRestriction(raw, key, 'ratio');
            if (window)
                windows.push(window);
        }
    }
    else if (toObject(payload)) {
        for (const [key, value] of Object.entries(payload)) {
            const raw = toObject(value);
            if (!raw)
                continue;
            if (!isClaudeOAuthUsageWindowCandidate(key, raw))
                continue;
            // OAuth /oauth/usage payloads return utilization as percent 0..100.
            const window = normalizeRestriction(raw, key, 'percent');
            if (window)
                windows.push(window);
        }
        // Some responses may contain a single restriction object directly,
        // matching the policy-limits ratio shape.
        if (windows.length === 0) {
            const direct = normalizeRestriction(payload, undefined, 'ratio');
            if (direct)
                windows.push(direct);
        }
    }
    const seen = new Set();
    const unique = [];
    for (const window of windows) {
        const key = `${window.rateLimitType}:${window.resetAt ?? ''}:${window.status}`;
        if (seen.has(key))
            continue;
        seen.add(key);
        unique.push(window);
    }
    return unique.sort((a, b) => a.rateLimitType.localeCompare(b.rateLimitType));
}
exports.buildClaudeQuotaWindows = buildClaudeQuotaWindows;
function toEpochMs(iso) {
    if (!iso)
        return null;
    const value = new Date(iso).getTime();
    return isNaN(value) ? null : value;
}
function pickMostRestrictiveWeekly(windows) {
    if (windows.length === 0)
        return null;
    return [...windows].sort((a, b) => {
        if (a.remainingPercent !== b.remainingPercent) {
            return a.remainingPercent - b.remainingPercent;
        }
        const aReset = toEpochMs(a.resetAt);
        const bReset = toEpochMs(b.resetAt);
        if (aReset === null && bReset === null)
            return 0;
        if (aReset === null)
            return 1;
        if (bReset === null)
            return -1;
        return aReset - bReset;
    })[0];
}
function mapCoreWindow(window) {
    if (!window)
        return null;
    return {
        rateLimitType: window.rateLimitType,
        label: window.label,
        remainingPercent: window.remainingPercent,
        resetAt: window.resetAt,
        status: window.status,
    };
}
const WEEKLY_RATE_LIMIT_TYPES = new Set([
    'seven_day',
    'seven_day_opus',
    'seven_day_sonnet',
    'seven_day_oauth_apps',
    'seven_day_cowork',
]);
function isClaudeWeeklyRateLimitType(rateLimitType) {
    return WEEKLY_RATE_LIMIT_TYPES.has(rateLimitType);
}
exports.isClaudeWeeklyRateLimitType = isClaudeWeeklyRateLimitType;
function pickMostRestrictiveClaudeWeeklyWindow(windows) {
    const weeklyCandidates = windows.filter((window) => isClaudeWeeklyRateLimitType(window.rateLimitType));
    return pickMostRestrictiveWeekly(weeklyCandidates);
}
exports.pickMostRestrictiveClaudeWeeklyWindow = pickMostRestrictiveClaudeWeeklyWindow;
/**
 * Build explicit 5h + weekly usage summary from Claude policy windows.
 */
function buildClaudeCoreUsageSummary(windows) {
    if (!windows || windows.length === 0) {
        return { fiveHour: null, weekly: null };
    }
    const fiveHourWindow = windows.find((window) => window.rateLimitType === 'five_hour') || null;
    const weeklyWindow = pickMostRestrictiveClaudeWeeklyWindow(windows);
    if (fiveHourWindow && weeklyWindow) {
        return {
            fiveHour: mapCoreWindow(fiveHourWindow),
            weekly: mapCoreWindow(weeklyWindow),
        };
    }
    // Fallback: infer shortest/longest reset windows from non-overage limits.
    const nonOverage = windows.filter((window) => window.rateLimitType !== 'overage');
    const withReset = nonOverage
        .map((window) => ({
        window,
        resetMs: toEpochMs(window.resetAt),
    }))
        .filter((entry) => entry.resetMs !== null)
        .sort((a, b) => a.resetMs - b.resetMs);
    const inferredWeekly = weeklyWindow ||
        [...withReset].reverse().find((entry) => entry.window !== fiveHourWindow)?.window ||
        pickMostRestrictiveWeekly(nonOverage.filter((window) => window !== fiveHourWindow));
    const inferredFiveHour = fiveHourWindow ||
        withReset.find((entry) => entry.window !== inferredWeekly)?.window ||
        pickMostRestrictiveWeekly(nonOverage.filter((window) => window !== inferredWeekly));
    return {
        fiveHour: mapCoreWindow(inferredFiveHour),
        weekly: mapCoreWindow(inferredWeekly),
    };
}
exports.buildClaudeCoreUsageSummary = buildClaudeCoreUsageSummary;
//# sourceMappingURL=quota-fetcher-claude-normalizer.js.map