"use strict";
/**
 * JSONL Parser for Claude Code Usage Analytics
 *
 * High-performance streaming parser for ~/.claude/projects/ JSONL files.
 * Replaces better-ccusage dependency with optimized custom implementation.
 *
 * Key features:
 * - Streaming line-by-line parsing (memory efficient)
 * - Only parses "assistant" entries with usage data
 * - Parallel file processing with configurable concurrency
 * - Graceful error handling for malformed entries
 */
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.countJsonlFiles = exports.scanProjectsDirectory = exports.findProjectDirectories = exports.getDefaultProjectsDir = exports.parseProjectDirectory = exports.parseJsonlFile = exports.parseUsageEntry = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const readline = __importStar(require("readline"));
const claude_config_path_1 = require("../utils/claude-config-path");
const DEFAULT_SCAN_CONCURRENCY = 10;
const MAX_SCAN_CONCURRENCY = 64;
// ============================================================================
// CORE PARSING FUNCTIONS
// ============================================================================
/**
 * Parse a single JSONL line into RawUsageEntry if valid
 * Returns null for non-assistant entries or entries without usage data
 */
function toNonNegativeNumber(value) {
    const numeric = typeof value === 'number' ? value : Number(value);
    if (!Number.isFinite(numeric) || numeric < 0) {
        return 0;
    }
    return numeric;
}
function parseRequiredNonNegativeNumber(value) {
    const numeric = typeof value === 'number' ? value : Number(value);
    if (!Number.isFinite(numeric) || numeric < 0) {
        return null;
    }
    return numeric;
}
function isValidTimestamp(value) {
    return typeof value === 'string' && Number.isFinite(Date.parse(value));
}
function parseUsageEntry(line, projectPath) {
    // Strip UTF-8 BOM if present (can occur on first line of some files)
    const cleanLine = line.replace(/^\uFEFF/, '').trim();
    if (!cleanLine)
        return null;
    try {
        const entry = JSON.parse(cleanLine);
        // Only process assistant entries with usage data
        if (entry.type !== 'assistant')
            return null;
        if (!entry.message?.usage)
            return null;
        if (!entry.message?.model)
            return null;
        const usage = entry.message.usage;
        const assistant = entry;
        const inputTokens = parseRequiredNonNegativeNumber(usage.input_tokens);
        const outputTokens = parseRequiredNonNegativeNumber(usage.output_tokens);
        if (inputTokens === null || outputTokens === null || !isValidTimestamp(assistant.timestamp)) {
            return null;
        }
        const normalizedProjectPath = typeof assistant.cwd === 'string' && assistant.cwd.trim().length > 0
            ? assistant.cwd
            : projectPath;
        return {
            inputTokens,
            outputTokens,
            cacheCreationTokens: toNonNegativeNumber(usage.cache_creation_input_tokens),
            cacheReadTokens: toNonNegativeNumber(usage.cache_read_input_tokens),
            model: assistant.message.model,
            sessionId: assistant.sessionId || '',
            timestamp: assistant.timestamp,
            projectPath: normalizedProjectPath,
            version: assistant.version,
            target: typeof entry.target === 'string'
                ? entry.target
                : undefined,
        };
    }
    catch {
        // Malformed JSON - skip silently
        return null;
    }
}
exports.parseUsageEntry = parseUsageEntry;
/**
 * Stream-parse a single JSONL file
 * Yields RawUsageEntry for each valid assistant entry
 */
async function parseJsonlFile(filePath, projectPath) {
    const entries = [];
    let fileStream = null;
    let rl = null;
    try {
        fileStream = fs.createReadStream(filePath, { encoding: 'utf8' });
        rl = readline.createInterface({
            input: fileStream,
            crlfDelay: Infinity,
        });
        for await (const line of rl) {
            const entry = parseUsageEntry(line, projectPath);
            if (entry) {
                entries.push(entry);
            }
        }
    }
    catch {
        // File read/stream error - return whatever was parsed so far
    }
    finally {
        rl?.close();
        fileStream?.destroy();
    }
    return entries;
}
exports.parseJsonlFile = parseJsonlFile;
function decodeProjectPath(projectDir) {
    const raw = path.basename(projectDir).replace(/-/g, '/');
    const safeSegments = raw
        .split('/')
        .filter((segment) => segment && segment !== '.' && segment !== '..');
    return `/${safeSegments.join('/')}`;
}
/**
 * Parse all JSONL files in a single project directory
 */
async function parseProjectDirectory(projectDir) {
    const entries = [];
    // Get project path from directory name (e.g., "-home-kai-project" -> "/home/kai/project")
    const projectPath = decodeProjectPath(projectDir);
    try {
        const files = await fs.promises.readdir(projectDir);
        const jsonlFiles = files.filter((f) => f.endsWith('.jsonl'));
        // Parse files sequentially within a project to avoid too many open handles
        for (const file of jsonlFiles) {
            const filePath = path.join(projectDir, file);
            const fileEntries = await parseJsonlFile(filePath, projectPath);
            entries.push(...fileEntries);
        }
    }
    catch {
        // Directory access error - skip silently
    }
    return entries;
}
exports.parseProjectDirectory = parseProjectDirectory;
// ============================================================================
// DIRECTORY SCANNING
// ============================================================================
/**
 * Get default Claude projects directory
 */
function getDefaultProjectsDir() {
    return path.join((0, claude_config_path_1.getClaudeConfigDir)(), 'projects');
}
exports.getDefaultProjectsDir = getDefaultProjectsDir;
/**
 * Find all project directories under ~/.claude/projects/
 */
function findProjectDirectories(projectsDir) {
    const dir = projectsDir || getDefaultProjectsDir();
    try {
        const entries = fs.readdirSync(dir, { withFileTypes: true });
        return entries
            .filter((entry) => entry.isDirectory())
            .map((entry) => path.join(dir, entry.name));
    }
    catch {
        return [];
    }
}
exports.findProjectDirectories = findProjectDirectories;
/**
 * Scan all projects and parse all JSONL files
 * Main entry point for usage data extraction
 *
 * @param options - Parser configuration
 * @returns All parsed usage entries from all projects
 */
async function scanProjectsDirectory(options = {}) {
    const requestedConcurrency = options.concurrency;
    const concurrency = typeof requestedConcurrency === 'number' &&
        Number.isInteger(requestedConcurrency) &&
        requestedConcurrency > 0
        ? Math.min(requestedConcurrency, MAX_SCAN_CONCURRENCY)
        : DEFAULT_SCAN_CONCURRENCY;
    const { projectsDir } = options;
    const allEntries = [];
    const projectDirs = findProjectDirectories(projectsDir);
    if (projectDirs.length === 0) {
        return allEntries;
    }
    // Process projects in batches for controlled concurrency
    for (let i = 0; i < projectDirs.length; i += concurrency) {
        const batch = projectDirs.slice(i, i + concurrency);
        const batchResults = await Promise.all(batch.map((dir) => parseProjectDirectory(dir)));
        for (const entries of batchResults) {
            allEntries.push(...entries);
        }
    }
    // Filter by date if specified
    if (options.minDate) {
        const minTime = options.minDate.getTime();
        return allEntries.filter((entry) => {
            const entryTime = Date.parse(entry.timestamp);
            return Number.isFinite(entryTime) && entryTime >= minTime;
        });
    }
    return allEntries;
}
exports.scanProjectsDirectory = scanProjectsDirectory;
/**
 * Get count of JSONL files across all projects (for progress reporting)
 */
function countJsonlFiles(projectsDir) {
    const projectDirs = findProjectDirectories(projectsDir);
    let count = 0;
    for (const dir of projectDirs) {
        try {
            const files = fs.readdirSync(dir);
            count += files.filter((f) => f.endsWith('.jsonl')).length;
        }
        catch {
            // Skip inaccessible directories
        }
    }
    return count;
}
exports.countJsonlFiles = countJsonlFiles;
//# sourceMappingURL=jsonl-parser.js.map