/**
 * @fileoverview Centralized resource cleanup manager.
 *
 * Tracks timers, intervals, watchers, and other resources that need explicit
 * cleanup. Provides a unified dispose() method that safely cleans up all
 * registered resources, even on partial failures.
 *
 * @module utils/cleanup-manager
 */
import { v4 as uuidv4 } from 'uuid';
/**
 * Centralized manager for tracking and disposing resources.
 *
 * Implements the Disposable interface for hierarchical cleanup.
 * All registered resources are cleaned up when dispose() is called.
 *
 * Features:
 * - Tracks timers, intervals, watchers, and custom cleanup functions
 * - isStopped guard to prevent callbacks firing after disposal
 * - Safe disposal that continues even if individual cleanups fail
 * - Debug logging for resource tracking
 *
 * @example
 * ```typescript
 * class MyService implements Disposable {
 *   private cleanup = new CleanupManager();
 *
 *   start() {
 *     // Timer automatically cleared on dispose
 *     this.cleanup.setTimeout(() => {
 *       if (this.cleanup.isStopped) return;  // Guard
 *       this.doWork();
 *     }, 5000, { description: 'work timer' });
 *   }
 *
 *   dispose() {
 *     this.cleanup.dispose();
 *   }
 *
 *   get isDisposed() { return this.cleanup.isDisposed; }
 * }
 * ```
 */
export class CleanupManager {
    registrations = new Map();
    _isDisposed = false;
    debugMode;
    /**
     * Creates a new CleanupManager.
     *
     * @param debug - Enable debug logging for resource tracking
     */
    constructor(debug = false) {
        this.debugMode = debug;
    }
    /**
     * Whether this manager has been disposed.
     * Check this before executing callbacks to prevent zombie operations.
     */
    get isDisposed() {
        return this._isDisposed;
    }
    /**
     * Alias for isDisposed - check before executing async callbacks.
     */
    get isStopped() {
        return this._isDisposed;
    }
    /**
     * Number of currently registered resources.
     */
    get resourceCount() {
        return this.registrations.size;
    }
    /**
     * Get counts by resource type for metrics/debugging.
     */
    get resourceCounts() {
        const counts = {
            timer: 0,
            interval: 0,
            watcher: 0,
            listener: 0,
            stream: 0,
        };
        for (const reg of this.registrations.values()) {
            counts[reg.type]++;
        }
        return counts;
    }
    /**
     * Schedule a timeout that will be automatically cleared on dispose.
     *
     * @param callback - Function to call when timeout fires
     * @param delay - Delay in milliseconds
     * @param options - Optional configuration
     * @returns Timer ID for manual clearing if needed
     */
    setTimeout(callback, delay, options) {
        const id = uuidv4();
        const timeoutId = setTimeout(() => {
            // Remove registration when timer fires naturally
            this.registrations.delete(id);
            // Don't execute if already stopped
            if (this._isDisposed)
                return;
            callback();
        }, delay);
        this.register({
            id,
            type: 'timer',
            description: options?.description || `setTimeout(${delay}ms)`,
            cleanup: () => clearTimeout(timeoutId),
            registeredAt: Date.now(),
        });
        return id;
    }
    /**
     * Schedule an interval that will be automatically cleared on dispose.
     *
     * @param callback - Function to call on each interval
     * @param delay - Interval in milliseconds
     * @param options - Optional configuration
     * @returns Interval ID for manual clearing if needed
     */
    setInterval(callback, delay, options) {
        const id = uuidv4();
        const intervalId = setInterval(() => {
            // Don't execute if stopped
            if (this._isDisposed)
                return;
            callback();
        }, delay);
        this.register({
            id,
            type: 'interval',
            description: options?.description || `setInterval(${delay}ms)`,
            cleanup: () => clearInterval(intervalId),
            registeredAt: Date.now(),
        });
        return id;
    }
    /**
     * Register a custom cleanup function.
     *
     * @param type - Type of resource for categorization
     * @param cleanup - Function to call on dispose
     * @param description - Human-readable description
     * @returns Registration ID for manual removal if needed
     */
    registerCleanup(type, cleanup, description) {
        const id = uuidv4();
        this.register({
            id,
            type,
            description,
            cleanup,
            registeredAt: Date.now(),
        });
        return id;
    }
    /**
     * Register a file system watcher for cleanup.
     *
     * @param watcher - Object with close() method (FSWatcher, chokidar, etc.)
     * @param description - Human-readable description
     * @returns Registration ID
     */
    registerWatcher(watcher, description) {
        return this.registerCleanup('watcher', () => watcher.close(), description);
    }
    /**
     * Register an event listener for cleanup.
     *
     * @param emitter - Object with removeListener/off method
     * @param event - Event name
     * @param listener - Listener function
     * @param description - Human-readable description
     * @returns Registration ID
     */
    registerListener(emitter, event, listener, description) {
        return this.registerCleanup('listener', () => {
            if (emitter.removeListener) {
                emitter.removeListener(event, listener);
            }
            else if (emitter.off) {
                emitter.off(event, listener);
            }
        }, description);
    }
    /**
     * Register a stream for cleanup.
     *
     * @param stream - Object with destroy() or close() method
     * @param description - Human-readable description
     * @returns Registration ID
     */
    registerStream(stream, description) {
        return this.registerCleanup('stream', () => {
            if (stream.destroy) {
                stream.destroy();
            }
            else if (stream.close) {
                stream.close();
            }
        }, description);
    }
    /**
     * Manually remove a registered resource.
     *
     * @param id - Registration ID returned from register methods
     * @returns True if resource was found and removed
     */
    unregister(id) {
        const reg = this.registrations.get(id);
        if (!reg)
            return false;
        try {
            reg.cleanup();
        }
        catch (err) {
            this.debug(`Error cleaning up ${reg.type} "${reg.description}": ${err}`);
        }
        this.registrations.delete(id);
        return true;
    }
    /**
     * Dispose all registered resources.
     * Safe to call multiple times (idempotent).
     * Continues cleanup even if individual resources fail.
     */
    dispose() {
        if (this._isDisposed)
            return;
        this._isDisposed = true;
        const errors = [];
        for (const reg of this.registrations.values()) {
            try {
                reg.cleanup();
                this.debug(`Cleaned up ${reg.type}: ${reg.description}`);
            }
            catch (err) {
                errors.push({ description: reg.description, error: err });
                this.debug(`Error cleaning up ${reg.type} "${reg.description}": ${err}`);
            }
        }
        this.registrations.clear();
        if (errors.length > 0) {
            console.error(`[CleanupManager] ${errors.length} errors during disposal:`, errors.map((e) => e.description).join(', '));
        }
    }
    /**
     * Get all current registrations for debugging.
     */
    getRegistrations() {
        return Array.from(this.registrations.values());
    }
    /**
     * Internal: Register a cleanup entry.
     */
    register(reg) {
        this.registrations.set(reg.id, reg);
        this.debug(`Registered ${reg.type}: ${reg.description}`);
    }
    /**
     * Internal: Debug logging.
     */
    debug(message) {
        if (this.debugMode) {
            console.log(`[CleanupManager] ${message}`);
        }
    }
}
//# sourceMappingURL=cleanup-manager.js.map