/**
 * Closure Metrics - Telemetry for External Tool Sync Operations
 *
 * Tracks closure success/failure rates for GitHub, JIRA, and ADO.
 * Persists metrics to .specweave/state/closure-metrics.json
 *
 * Usage:
 *   const metrics = new ClosureMetrics(projectRoot);
 *   metrics.recordClosure('github', 100, true); // Success
 *   metrics.recordClosure('jira', 'TEST-123', false, 'Rate limited'); // Failure
 *   metrics.getSummary(); // Get aggregated stats
 */
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { join } from 'path';
import { consoleLogger } from '../utils/logger.js';
const METRICS_VERSION = 1;
const MAX_RECORDS = 1000; // Keep last 1000 records
const MAX_RECENT_ERRORS = 10; // Keep last 10 errors per tool
export class ClosureMetrics {
    constructor(options) {
        this.operationStartTime = 0;
        this.projectRoot = options.projectRoot;
        this.currentIncrementId = options.incrementId || 'unknown';
        this.logger = options.logger || consoleLogger;
        const stateDir = join(this.projectRoot, '.specweave/state');
        if (!existsSync(stateDir)) {
            mkdirSync(stateDir, { recursive: true });
        }
        this.metricsFile = join(stateDir, 'closure-metrics.json');
    }
    /**
     * Start timing an operation
     */
    startOperation() {
        this.operationStartTime = Date.now();
    }
    /**
     * Record a closure operation result
     */
    recordClosure(tool, itemId, success, error) {
        const durationMs = this.operationStartTime > 0
            ? Date.now() - this.operationStartTime
            : undefined;
        const record = {
            tool,
            itemId,
            incrementId: this.currentIncrementId,
            success,
            error,
            timestamp: new Date().toISOString(),
            durationMs,
        };
        const metrics = this.loadMetrics();
        metrics.records.push(record);
        // Trim to max records
        if (metrics.records.length > MAX_RECORDS) {
            metrics.records = metrics.records.slice(-MAX_RECORDS);
        }
        // Update summary
        this.updateSummary(metrics);
        this.saveMetrics(metrics);
        // Reset timer
        this.operationStartTime = 0;
    }
    /**
     * Record multiple closures in batch
     */
    recordBatch(tool, results) {
        const metrics = this.loadMetrics();
        for (const result of results) {
            const record = {
                tool,
                itemId: result.itemId,
                incrementId: this.currentIncrementId,
                success: result.success,
                error: result.error,
                timestamp: new Date().toISOString(),
            };
            metrics.records.push(record);
        }
        // Trim to max records
        if (metrics.records.length > MAX_RECORDS) {
            metrics.records = metrics.records.slice(-MAX_RECORDS);
        }
        this.updateSummary(metrics);
        this.saveMetrics(metrics);
    }
    /**
     * Get metrics summary
     */
    getSummary() {
        const metrics = this.loadMetrics();
        return metrics.summary;
    }
    /**
     * Get recent failures for alerting
     */
    getRecentFailures(hours = 24) {
        const metrics = this.loadMetrics();
        const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
        return metrics.records.filter(r => !r.success && r.timestamp >= cutoff);
    }
    /**
     * Check if failure rate exceeds threshold (for alerting)
     */
    isFailureRateHigh(tool, threshold = 0.2) {
        const summary = this.getSummary();
        const toolMetrics = summary[tool];
        // Only alert if we have enough data (at least 5 attempts)
        if (toolMetrics.totalAttempts < 5) {
            return false;
        }
        const failureRate = 1 - toolMetrics.successRate;
        return failureRate > threshold;
    }
    /**
     * Format summary for display
     */
    formatSummary() {
        const summary = this.getSummary();
        const lines = [];
        lines.push('📊 Closure Sync Metrics');
        lines.push('═══════════════════════════════════════');
        const formatTool = (name, metrics) => {
            const result = [];
            if (metrics.totalAttempts === 0) {
                result.push(`  ${name}: No data`);
                return result;
            }
            const successPct = (metrics.successRate * 100).toFixed(1);
            const statusIcon = metrics.successRate >= 0.9 ? '✅' : metrics.successRate >= 0.7 ? '⚠️' : '❌';
            result.push(`  ${name}: ${statusIcon} ${successPct}% success (${metrics.successful}/${metrics.totalAttempts})`);
            if (metrics.avgDurationMs) {
                result.push(`       Avg duration: ${metrics.avgDurationMs.toFixed(0)}ms`);
            }
            if (metrics.lastFailure && metrics.recentErrors.length > 0) {
                result.push(`       Last error: ${metrics.recentErrors[0]}`);
            }
            return result;
        };
        lines.push(...formatTool('GitHub', summary.github));
        lines.push(...formatTool('JIRA  ', summary.jira));
        lines.push(...formatTool('ADO   ', summary.ado));
        lines.push('───────────────────────────────────────');
        const overallPct = (summary.overall.successRate * 100).toFixed(1);
        lines.push(`  Overall: ${overallPct}% success (${summary.overall.successful}/${summary.overall.totalAttempts})`);
        lines.push(`  Last updated: ${summary.lastUpdated}`);
        return lines.join('\n');
    }
    /**
     * Reset metrics (for testing or fresh start)
     */
    reset() {
        const metrics = this.createEmptyMetrics();
        this.saveMetrics(metrics);
    }
    // ========================================================================
    // Private Methods
    // ========================================================================
    loadMetrics() {
        if (!existsSync(this.metricsFile)) {
            return this.createEmptyMetrics();
        }
        try {
            const content = readFileSync(this.metricsFile, 'utf-8');
            const metrics = JSON.parse(content);
            // Version migration if needed
            if (metrics.version !== METRICS_VERSION) {
                this.logger.log('📊 Migrating metrics to new version...');
                return this.createEmptyMetrics();
            }
            return metrics;
        }
        catch (error) {
            this.logger.log(`⚠️ Failed to load metrics: ${error}`);
            return this.createEmptyMetrics();
        }
    }
    saveMetrics(metrics) {
        try {
            writeFileSync(this.metricsFile, JSON.stringify(metrics, null, 2));
        }
        catch (error) {
            this.logger.log(`⚠️ Failed to save metrics: ${error}`);
        }
    }
    createEmptyMetrics() {
        const emptyToolMetrics = {
            totalAttempts: 0,
            successful: 0,
            failed: 0,
            successRate: 1,
            recentErrors: [],
        };
        return {
            version: METRICS_VERSION,
            records: [],
            summary: {
                github: { ...emptyToolMetrics },
                jira: { ...emptyToolMetrics },
                ado: { ...emptyToolMetrics },
                overall: {
                    totalAttempts: 0,
                    successful: 0,
                    failed: 0,
                    successRate: 1,
                },
                lastUpdated: new Date().toISOString(),
            },
        };
    }
    updateSummary(metrics) {
        const tools = ['github', 'jira', 'ado'];
        // Calculate per-tool metrics
        for (const tool of tools) {
            const toolRecords = metrics.records.filter(r => r.tool === tool);
            const successful = toolRecords.filter(r => r.success).length;
            const failed = toolRecords.filter(r => !r.success).length;
            const total = toolRecords.length;
            // Get durations for average calculation
            const durations = toolRecords
                .filter(r => r.durationMs !== undefined)
                .map(r => r.durationMs);
            const avgDurationMs = durations.length > 0
                ? durations.reduce((a, b) => a + b, 0) / durations.length
                : undefined;
            // Get recent errors
            const recentErrors = toolRecords
                .filter(r => !r.success && r.error)
                .slice(-MAX_RECENT_ERRORS)
                .map(r => r.error)
                .reverse();
            // Find last success/failure timestamps
            const lastSuccessRecord = toolRecords.filter(r => r.success).pop();
            const lastFailureRecord = toolRecords.filter(r => !r.success).pop();
            metrics.summary[tool] = {
                totalAttempts: total,
                successful,
                failed,
                successRate: total > 0 ? successful / total : 1,
                lastSuccess: lastSuccessRecord?.timestamp,
                lastFailure: lastFailureRecord?.timestamp,
                avgDurationMs,
                recentErrors,
            };
        }
        // Calculate overall metrics
        const allRecords = metrics.records;
        const totalSuccessful = allRecords.filter(r => r.success).length;
        const totalFailed = allRecords.filter(r => !r.success).length;
        const totalAttempts = allRecords.length;
        metrics.summary.overall = {
            totalAttempts,
            successful: totalSuccessful,
            failed: totalFailed,
            successRate: totalAttempts > 0 ? totalSuccessful / totalAttempts : 1,
        };
        metrics.summary.lastUpdated = new Date().toISOString();
    }
}
/**
 * Factory function for creating metrics instance
 */
export function createClosureMetrics(projectRoot, incrementId, logger) {
    return new ClosureMetrics({ projectRoot, incrementId, logger });
}
//# sourceMappingURL=closure-metrics.js.map