"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.scanCodexNativeUsageEntries = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const readline = __importStar(require("readline"));
const config_manager_1 = require("../../utils/config-manager");
const codex_dashboard_service_1 = require("../services/codex-dashboard-service");
const CODEX_NATIVE_USAGE_CACHE_VERSION = 1;
function isCliProxyBackedProvider(value) {
    return value === 'cliproxy' || value === 'ccs_runtime';
}
function isObject(value) {
    return typeof value === 'object' && value !== null;
}
function asString(value) {
    return typeof value === 'string' && value.trim().length > 0 ? value : null;
}
function asNumber(value) {
    return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0;
}
function hasUsage(snapshot) {
    return snapshot.inputTokens > 0 || snapshot.cacheReadTokens > 0 || snapshot.outputTokens > 0;
}
function normalizeTokenSnapshot(value) {
    if (!isObject(value))
        return null;
    return {
        inputTokens: asNumber(value.input_tokens),
        cacheReadTokens: asNumber(value.cached_input_tokens),
        outputTokens: asNumber(value.output_tokens) + asNumber(value.reasoning_output_tokens),
    };
}
function subtractSnapshots(current, previous) {
    return {
        inputTokens: Math.max(0, current.inputTokens - previous.inputTokens),
        cacheReadTokens: Math.max(0, current.cacheReadTokens - previous.cacheReadTokens),
        outputTokens: Math.max(0, current.outputTokens - previous.outputTokens),
    };
}
function snapshotsEqual(left, right) {
    return (left.inputTokens === right.inputTokens &&
        left.cacheReadTokens === right.cacheReadTokens &&
        left.outputTokens === right.outputTokens);
}
async function collectRolloutFiles(dir) {
    if (!fs.existsSync(dir))
        return [];
    const files = [];
    for (const entry of await fs.promises.readdir(dir, { withFileTypes: true })) {
        const entryPath = path.join(dir, entry.name);
        if (entry.isDirectory()) {
            files.push(...(await collectRolloutFiles(entryPath)));
            continue;
        }
        if (entry.isFile() && entry.name.startsWith('rollout-') && entry.name.endsWith('.jsonl')) {
            files.push(entryPath);
        }
    }
    return files.sort();
}
function getDefaultCacheDir() {
    return path.join((0, config_manager_1.getCcsDir)(), 'cache');
}
function getCacheFilePath(cacheDir, includeCliproxySessions) {
    return path.join(cacheDir, includeCliproxySessions
        ? 'codex-native-usage-with-cliproxy-v1.json'
        : 'codex-native-usage-v1.json');
}
function isCachedRolloutFile(value) {
    if (!isObject(value))
        return false;
    return (typeof value.path === 'string' &&
        typeof value.size === 'number' &&
        typeof value.mtimeMs === 'number' &&
        Array.isArray(value.entries));
}
function isCodexNativeUsageCache(value) {
    if (!isObject(value))
        return false;
    if (value.version !== CODEX_NATIVE_USAGE_CACHE_VERSION)
        return false;
    if (typeof value.includeCliproxySessions !== 'boolean')
        return false;
    if (typeof value.generatedAt !== 'number')
        return false;
    if (!isObject(value.files))
        return false;
    return Object.values(value.files).every(isCachedRolloutFile);
}
function readUsageCache(cacheDir, includeCliproxySessions) {
    try {
        const cachePath = getCacheFilePath(cacheDir, includeCliproxySessions);
        if (!fs.existsSync(cachePath))
            return null;
        const parsed = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
        if (!isCodexNativeUsageCache(parsed))
            return null;
        if (parsed.includeCliproxySessions !== includeCliproxySessions)
            return null;
        return parsed;
    }
    catch {
        return null;
    }
}
function writeUsageCache(cacheDir, cache) {
    try {
        fs.mkdirSync(cacheDir, { recursive: true });
        const cachePath = getCacheFilePath(cacheDir, cache.includeCliproxySessions);
        const tempPath = `${cachePath}.${process.pid}.tmp`;
        fs.writeFileSync(tempPath, JSON.stringify(cache), 'utf8');
        fs.renameSync(tempPath, cachePath);
    }
    catch {
        // Best-effort only.
    }
}
function getMtimeMsFingerprint(stats) {
    return stats.mtimeMs;
}
function hasMatchingFingerprint(cached, filePath, stats) {
    return (!!cached &&
        cached.path === filePath &&
        cached.size === stats.size &&
        cached.mtimeMs === getMtimeMsFingerprint(stats));
}
async function parseRolloutFile(filePath, includeCliproxySessions) {
    const entries = [];
    let sessionId = '';
    let projectPath = '';
    let version;
    let modelProvider;
    let model = 'unknown-codex-model';
    let previousTotal = null;
    const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
    const reader = readline.createInterface({ input: stream, crlfDelay: Infinity });
    try {
        for await (const line of reader) {
            if (!line.trim())
                continue;
            let parsed;
            try {
                parsed = JSON.parse(line);
            }
            catch {
                continue;
            }
            if (parsed.type === 'session_meta' && isObject(parsed.payload)) {
                sessionId = asString(parsed.payload.id) ?? sessionId;
                projectPath = asString(parsed.payload.cwd) ?? projectPath;
                version = asString(parsed.payload.cli_version) ?? version;
                modelProvider = asString(parsed.payload.model_provider) ?? modelProvider;
                continue;
            }
            if (parsed.type === 'turn_context' && isObject(parsed.payload)) {
                model = asString(parsed.payload.model) ?? model;
                projectPath = asString(parsed.payload.cwd) ?? projectPath;
                continue;
            }
            if (parsed.type !== 'event_msg' ||
                !isObject(parsed.payload) ||
                parsed.payload.type !== 'token_count' ||
                !sessionId ||
                (isCliProxyBackedProvider(modelProvider) && !includeCliproxySessions)) {
                continue;
            }
            const totalSnapshot = normalizeTokenSnapshot(parsed.payload.info && isObject(parsed.payload.info)
                ? parsed.payload.info.total_token_usage
                : null);
            const lastSnapshot = normalizeTokenSnapshot(parsed.payload.info && isObject(parsed.payload.info)
                ? parsed.payload.info.last_token_usage
                : null);
            if (!totalSnapshot)
                continue;
            if (previousTotal && snapshotsEqual(totalSnapshot, previousTotal))
                continue;
            const delta = previousTotal === null
                ? lastSnapshot && hasUsage(lastSnapshot)
                    ? lastSnapshot
                    : totalSnapshot
                : subtractSnapshots(totalSnapshot, previousTotal);
            previousTotal = totalSnapshot;
            if (!hasUsage(delta))
                continue;
            const timestamp = asString(parsed.timestamp);
            if (!timestamp)
                continue;
            entries.push({
                inputTokens: delta.inputTokens,
                outputTokens: delta.outputTokens,
                cacheCreationTokens: 0,
                cacheReadTokens: delta.cacheReadTokens,
                model,
                sessionId,
                timestamp,
                projectPath: projectPath || '/',
                version,
                target: 'codex',
            });
        }
    }
    finally {
        reader.close();
        stream.destroy();
    }
    return entries;
}
async function scanCodexNativeUsageEntries(options = {}) {
    const includeCliproxySessions = options.includeCliproxySessions === true;
    const { baseDir } = (0, codex_dashboard_service_1.resolveCodexConfigPaths)({
        env: options.env,
        homeDir: options.homeDir,
    });
    const rolloutFiles = await collectRolloutFiles(path.join(baseDir, 'sessions'));
    const entries = [];
    if (options.disableCache === true) {
        for (const filePath of rolloutFiles) {
            entries.push(...(await parseRolloutFile(filePath, includeCliproxySessions)));
        }
        return entries;
    }
    const cacheDir = options.cacheDir ?? getDefaultCacheDir();
    const previousCache = readUsageCache(cacheDir, includeCliproxySessions);
    const nextCache = {
        version: CODEX_NATIVE_USAGE_CACHE_VERSION,
        includeCliproxySessions,
        generatedAt: Date.now(),
        files: {},
    };
    for (const filePath of rolloutFiles) {
        const stats = await fs.promises.stat(filePath);
        const cached = previousCache?.files[filePath];
        if (hasMatchingFingerprint(cached, filePath, stats)) {
            entries.push(...cached.entries);
            nextCache.files[filePath] = cached;
            continue;
        }
        const fileEntries = await parseRolloutFile(filePath, includeCliproxySessions);
        entries.push(...fileEntries);
        nextCache.files[filePath] = {
            path: filePath,
            size: stats.size,
            mtimeMs: getMtimeMsFingerprint(stats),
            entries: fileEntries,
        };
    }
    writeUsageCache(cacheDir, nextCache);
    return entries;
}
exports.scanCodexNativeUsageEntries = scanCodexNativeUsageEntries;
//# sourceMappingURL=codex-native-usage-collector.js.map