/**
 * Analytics Collector
 *
 * Core class for collecting and storing usage analytics events.
 * Implements append-only JSONL storage with automatic rotation.
 */
import * as fs from 'fs';
import * as path from 'path';
import { DEFAULT_ANALYTICS_CONFIG, parseAnalyticsEvent, } from './types.js';
import { maskCredentials, maskCredentialsInData } from '../../utils/credential-masker.js';
/**
 * Singleton analytics collector for tracking SpecWeave usage
 */
export class AnalyticsCollector {
    constructor(projectRoot, config) {
        this.initialized = false;
        this.config = { ...DEFAULT_ANALYTICS_CONFIG, ...config };
        this.analyticsDir = path.join(projectRoot, '.specweave', 'state', 'analytics');
        this.eventsFile = path.join(this.analyticsDir, 'events.jsonl');
    }
    /**
     * Get or create an instance for the given project root
     */
    static getInstance(projectRoot, config) {
        const root = path.resolve(projectRoot || process.cwd());
        let instance = AnalyticsCollector.instances.get(root);
        if (!instance) {
            instance = new AnalyticsCollector(root, config);
            AnalyticsCollector.instances.set(root, instance);
        }
        return instance;
    }
    /**
     * Reset all instances (for testing)
     */
    static reset() {
        AnalyticsCollector.instances.clear();
    }
    /**
     * Initialize the analytics directory
     */
    ensureInitialized() {
        if (this.initialized)
            return;
        try {
            if (!fs.existsSync(this.analyticsDir)) {
                fs.mkdirSync(this.analyticsDir, { recursive: true });
            }
            this.initialized = true;
        }
        catch {
            // Silently fail - analytics should never break the main flow
            // Leave initialized = false so next track() retries
        }
    }
    /**
     * Track a command invocation
     */
    trackCommand(name, options = {}) {
        this.track({
            type: 'command',
            name,
            plugin: options.plugin,
            increment: options.increment,
            duration: options.duration,
            success: options.success ?? true,
            error: options.error,
            metadata: options.args ? { args: options.args } : undefined,
        });
    }
    /**
     * Track a skill activation
     */
    trackSkill(name, options = {}) {
        this.track({
            type: 'skill',
            name,
            plugin: options.plugin,
            increment: options.increment,
            duration: options.duration,
            success: options.success ?? true,
            error: options.error,
        });
    }
    /**
     * Track an agent spawn
     */
    trackAgent(name, options = {}) {
        this.track({
            type: 'agent',
            name,
            plugin: options.plugin,
            increment: options.increment,
            duration: options.duration,
            success: options.success ?? true,
            error: options.error,
            metadata: options.description ? { description: options.description } : undefined,
        });
    }
    /**
     * Track a generic event
     */
    track(event) {
        if (!this.config.enabled)
            return;
        this.ensureInitialized();
        if (!this.initialized)
            return;
        const fullEvent = {
            ...event,
            timestamp: new Date().toISOString(),
        };
        this.appendEvent(sanitizeAnalyticsEvent(fullEvent));
        this.checkRotation();
    }
    /**
     * Append an event to the JSONL file
     */
    appendEvent(event) {
        try {
            const line = JSON.stringify(event) + '\n';
            fs.appendFileSync(this.eventsFile, line, 'utf-8');
        }
        catch {
            // Silently fail - analytics should never break the main flow
        }
    }
    /**
     * Check if log rotation is needed
     */
    checkRotation() {
        try {
            if (!fs.existsSync(this.eventsFile))
                return;
            const stats = fs.statSync(this.eventsFile);
            if (stats.size > this.config.maxLogSizeBytes) {
                this.rotateLog();
            }
        }
        catch {
            // Silently fail
        }
    }
    /**
     * Rotate the log file, keeping only recent events
     */
    rotateLog() {
        try {
            const cutoffDate = new Date();
            cutoffDate.setDate(cutoffDate.getDate() - this.config.retentionDays);
            const cutoffTimestamp = cutoffDate.toISOString();
            const content = fs.readFileSync(this.eventsFile, 'utf-8');
            const lines = content.trim().split('\n').filter(Boolean);
            const recentLines = [];
            const oldLines = [];
            for (const line of lines) {
                try {
                    const event = parseAnalyticsEvent(JSON.parse(line));
                    if (!event)
                        continue;
                    (event.timestamp >= cutoffTimestamp ? recentLines : oldLines).push(line);
                }
                catch {
                    // Skip malformed lines
                }
            }
            // Archive old events if any
            if (oldLines.length > 0) {
                const archiveDir = path.join(this.analyticsDir, 'archive');
                if (!fs.existsSync(archiveDir)) {
                    fs.mkdirSync(archiveDir, { recursive: true });
                }
                const archiveFile = path.join(archiveDir, `events-${new Date().toISOString().split('T')[0]}.jsonl`);
                fs.appendFileSync(archiveFile, oldLines.join('\n') + '\n', 'utf-8');
            }
            // Atomic write: temp file then rename to prevent data loss from concurrent writers
            const tempFile = `${this.eventsFile}.tmp.${Date.now()}`;
            try {
                fs.writeFileSync(tempFile, recentLines.join('\n') + '\n', 'utf-8');
                fs.renameSync(tempFile, this.eventsFile);
            }
            catch {
                try {
                    fs.unlinkSync(tempFile);
                }
                catch { /* ignore cleanup failure */ }
            }
        }
        catch {
            // Silently fail
        }
    }
    /**
     * Read all events from the log
     */
    readEvents() {
        this.ensureInitialized();
        if (!this.initialized)
            return [];
        try {
            if (!fs.existsSync(this.eventsFile))
                return [];
            const content = fs.readFileSync(this.eventsFile, 'utf-8');
            const lines = content.trim().split('\n').filter(Boolean);
            const events = [];
            for (const line of lines) {
                try {
                    const event = parseAnalyticsEvent(JSON.parse(line));
                    if (event)
                        events.push(event);
                }
                catch {
                    // Skip malformed lines
                }
            }
            return events;
        }
        catch {
            return [];
        }
    }
    /**
     * Read events filtered by date range and type
     */
    readEventsFiltered(options) {
        const events = this.readEvents();
        // Normalize date-only strings for inclusive date range filtering
        const since = options.since && !options.since.includes('T')
            ? options.since + 'T00:00:00.000Z' : options.since;
        const until = options.until && !options.until.includes('T')
            ? options.until + 'T23:59:59.999Z' : options.until;
        return events.filter((event) => {
            if (since && event.timestamp < since)
                return false;
            if (until && event.timestamp > until)
                return false;
            if (options.type && event.type !== options.type)
                return false;
            if (options.plugin && event.plugin !== options.plugin)
                return false;
            return true;
        });
    }
    /**
     * Get the analytics directory path
     */
    getAnalyticsDir() {
        return this.analyticsDir;
    }
    /**
     * Check if analytics is enabled
     */
    isEnabled() {
        return this.config.enabled;
    }
    /**
     * Disable analytics collection
     */
    disable() {
        this.config.enabled = false;
    }
    /**
     * Enable analytics collection
     */
    enable() {
        this.config.enabled = true;
    }
}
AnalyticsCollector.instances = new Map();
function sanitizeAnalyticsEvent(event) {
    return {
        ...event,
        error: event.error ? maskCredentials(event.error) : event.error,
        metadata: event.metadata ? maskCredentialsInData(event.metadata) : event.metadata,
    };
}
/**
 * Convenience function to track a command
 */
export function trackCommand(name, options) {
    try {
        AnalyticsCollector.getInstance().trackCommand(name, options);
    }
    catch {
        // Never throw from analytics
    }
}
/**
 * Convenience function to track a skill
 */
export function trackSkill(name, options) {
    try {
        AnalyticsCollector.getInstance().trackSkill(name, options);
    }
    catch {
        // Never throw from analytics
    }
}
/**
 * Convenience function to track an agent
 */
export function trackAgent(name, options) {
    try {
        AnalyticsCollector.getInstance().trackAgent(name, options);
    }
    catch {
        // Never throw from analytics
    }
}
//# sourceMappingURL=analytics-collector.js.map