/**
 * Global Command Deduplication System
 *
 * Prevents ANY command/tool from being invoked twice within a configurable time window.
 * Tracks all SlashCommand, Write, Edit, and other tool invocations.
 *
 * Architecture:
 * - File-based cache: `.specweave/state/command-invocations.json`
 * - Hash-based deduplication: command + args → unique fingerprint
 * - Time-windowed checks: Configurable window (default: 1000ms)
 * - Automatic cleanup: Removes old entries to prevent bloat
 *
 * Usage:
 * ```typescript
 * import { CommandDeduplicator } from './command-deduplicator.js';
 *
 * const dedup = new CommandDeduplicator();
 * const isDuplicate = await dedup.checkDuplicate('sw:do', ['0031']);
 *
 * if (isDuplicate) {
 *   console.log('⚠️  Duplicate invocation blocked!');
 *   return;
 * }
 *
 * await dedup.recordInvocation('sw:do', ['0031']);
 * // ... execute command
 * ```
 *
 * @module core/deduplication
 */
import * as fs from '../../utils/fs-native.js';
import * as path from 'path';
import * as crypto from 'crypto';
/**
 * Global command deduplication system
 */
export class CommandDeduplicator {
    /**
     * Create new deduplicator instance
     *
     * @param config - Configuration options
     * @param projectRoot - Project root directory (default: process.cwd())
     */
    constructor(config = {}, projectRoot = process.cwd()) {
        this.projectRoot = projectRoot;
        this.lastCleanupCheck = 0;
        this.config = {
            windowMs: config.windowMs ?? 1000,
            cachePath: config.cachePath ?? path.join(projectRoot, '.specweave', 'state', 'command-invocations.json'),
            maxCacheSize: config.maxCacheSize ?? 1000,
            debug: config.debug ?? false,
            cleanupIntervalMs: config.cleanupIntervalMs ?? 60000
        };
        this.cache = this.loadCache();
    }
    /**
     * Check if command invocation is a duplicate
     *
     * @param command - Command name (e.g., 'sw:do')
     * @param args - Command arguments
     * @returns true if duplicate detected, false otherwise
     */
    async checkDuplicate(command, args = []) {
        const fingerprint = this.createFingerprint(command, args);
        const now = Date.now();
        const windowStart = now - this.config.windowMs;
        // Check for recent invocations with same fingerprint
        const recentDuplicates = this.cache.invocations.filter(inv => inv.fingerprint === fingerprint &&
            inv.timestamp >= windowStart);
        if (recentDuplicates.length > 0) {
            const mostRecent = recentDuplicates[recentDuplicates.length - 1];
            const timeSince = now - mostRecent.timestamp;
            if (this.config.debug) {
                console.log(`[CommandDeduplicator] 🚫 DUPLICATE DETECTED!`);
                console.log(`  Command: ${command}`);
                console.log(`  Args: ${JSON.stringify(args)}`);
                console.log(`  Time since last: ${timeSince}ms`);
                console.log(`  Window: ${this.config.windowMs}ms`);
            }
            // Increment duplicate counter
            this.cache.totalDuplicatesBlocked++;
            await this.saveCache();
            return true;
        }
        return false;
    }
    /**
     * Record command invocation
     *
     * @param command - Command name
     * @param args - Command arguments
     */
    async recordInvocation(command, args = []) {
        const fingerprint = this.createFingerprint(command, args);
        const now = Date.now();
        const record = {
            fingerprint,
            command,
            args,
            timestamp: now,
            date: new Date(now).toISOString()
        };
        this.cache.invocations.push(record);
        this.cache.totalInvocations++;
        if (this.config.debug) {
            console.log(`[CommandDeduplicator] ✅ Recorded invocation: ${command} ${args.join(' ')}`);
        }
        // Trigger cleanup if needed
        if (now - this.lastCleanupCheck > this.config.cleanupIntervalMs) {
            await this.cleanup();
            this.lastCleanupCheck = now;
        }
        await this.saveCache();
    }
    /**
     * Create unique fingerprint for command + args
     *
     * @param command - Command name
     * @param args - Command arguments
     * @returns SHA256 hash of command + args
     */
    createFingerprint(command, args) {
        const data = JSON.stringify({ command, args });
        return crypto.createHash('sha256').update(data).digest('hex');
    }
    /**
     * Load cache from disk
     *
     * @returns Invocation cache
     */
    loadCache() {
        try {
            if (fs.existsSync(this.config.cachePath)) {
                const data = fs.readJsonSync(this.config.cachePath);
                if (this.config.debug) {
                    console.log(`[CommandDeduplicator] 📂 Loaded cache: ${data.invocations.length} invocations`);
                }
                return data;
            }
        }
        catch (error) {
            if (this.config.debug) {
                console.log(`[CommandDeduplicator] ⚠️  Failed to load cache: ${error}`);
            }
        }
        // Return empty cache
        return {
            invocations: [],
            lastCleanup: Date.now(),
            totalInvocations: 0,
            totalDuplicatesBlocked: 0
        };
    }
    /**
     * Save cache to disk
     */
    async saveCache() {
        try {
            await fs.ensureDir(path.dirname(this.config.cachePath));
            await fs.writeJson(this.config.cachePath, this.cache, { spaces: 2 });
            if (this.config.debug) {
                console.log(`[CommandDeduplicator] 💾 Saved cache: ${this.cache.invocations.length} invocations`);
            }
        }
        catch (error) {
            console.error(`[CommandDeduplicator] ❌ Failed to save cache: ${error}`);
        }
    }
    /**
     * Clean up old invocation records
     *
     * Removes records older than 10x the deduplication window to prevent cache bloat.
     */
    async cleanup() {
        const now = Date.now();
        const cutoff = now - (this.config.windowMs * 10); // Keep 10x window
        const before = this.cache.invocations.length;
        this.cache.invocations = this.cache.invocations.filter(inv => inv.timestamp >= cutoff);
        const after = this.cache.invocations.length;
        // Also enforce max cache size
        if (this.cache.invocations.length > this.config.maxCacheSize) {
            this.cache.invocations = this.cache.invocations.slice(-this.config.maxCacheSize);
        }
        this.cache.lastCleanup = now;
        if (this.config.debug && before > after) {
            console.log(`[CommandDeduplicator] 🧹 Cleanup: Removed ${before - after} old records`);
        }
        await this.saveCache();
    }
    /**
     * Get statistics about deduplication
     *
     * @returns Statistics object
     */
    getStats() {
        return {
            totalInvocations: this.cache.totalInvocations,
            totalDuplicatesBlocked: this.cache.totalDuplicatesBlocked,
            currentCacheSize: this.cache.invocations.length,
            lastCleanup: new Date(this.cache.lastCleanup).toISOString()
        };
    }
    /**
     * Clear all cached invocations (useful for testing)
     */
    async clear() {
        this.cache = {
            invocations: [],
            lastCleanup: Date.now(),
            totalInvocations: 0,
            totalDuplicatesBlocked: 0
        };
        await this.saveCache();
        if (this.config.debug) {
            console.log(`[CommandDeduplicator] 🗑️  Cache cleared`);
        }
    }
}
/**
 * Global singleton instance (convenience)
 */
let globalInstance = null;
/**
 * Get global deduplicator instance
 *
 * @param config - Configuration (only used on first call)
 * @returns Global deduplicator instance
 */
export function getGlobalDeduplicator(config) {
    if (!globalInstance) {
        globalInstance = new CommandDeduplicator(config);
    }
    return globalInstance;
}
/**
 * Convenience function: Check if command is duplicate
 *
 * @param command - Command name
 * @param args - Command arguments
 * @returns true if duplicate, false otherwise
 */
export async function isDuplicate(command, args = []) {
    return await getGlobalDeduplicator().checkDuplicate(command, args);
}
/**
 * Convenience function: Record command invocation
 *
 * @param command - Command name
 * @param args - Command arguments
 */
export async function recordCommand(command, args = []) {
    await getGlobalDeduplicator().recordInvocation(command, args);
}
//# sourceMappingURL=command-deduplicator.js.map