/**
 * Plugin Hot Reload System
 *
 * Provides file watching and hot reloading capabilities for plugins.
 * Allows developers to see changes immediately without restarting.
 */
import { EventEmitter } from 'events';
import * as path from 'path';
import * as fs from 'fs';
/**
 * Default hot reload configuration
 */
export const DEFAULT_HOT_RELOAD_CONFIG = {
    enabled: true,
    debounceMs: 300,
    watchPatterns: ['**/*.js', '**/*.ts', '**/*.json'],
    ignorePatterns: ['**/node_modules/**', '**/.git/**', '**/dist/**'],
    reloadOnManifestChange: true,
    reloadOnSourceChange: true,
    showNotifications: true,
};
/**
 * Plugin Hot Reloader
 *
 * Watches plugin directories for changes and triggers reloads.
 */
export class PluginHotReloader extends EventEmitter {
    config;
    watchedPlugins = new Map();
    reloadCallback = null;
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_HOT_RELOAD_CONFIG, ...config };
    }
    /**
     * Set the reload callback function
     */
    onReload(callback) {
        this.reloadCallback = callback;
    }
    /**
     * Start watching a plugin directory
     */
    watch(pluginId, pluginPath, manifest) {
        if (!this.config.enabled)
            return;
        // Stop existing watcher if any
        this.unwatch(pluginId);
        try {
            const watcher = fs.watch(pluginPath, { recursive: true }, (eventType, filename) => {
                if (filename) {
                    this.handleFileChange(pluginId, pluginPath, filename, eventType);
                }
            });
            watcher.on('error', (error) => {
                this.emit('error', { pluginId, error });
            });
            this.watchedPlugins.set(pluginId, {
                id: pluginId,
                path: pluginPath,
                watcher,
                manifest,
                lastReload: new Date(),
                pendingReload: null,
            });
            this.emit('watching', { pluginId, pluginPath });
        }
        catch (error) {
            this.emit('error', { pluginId, error });
        }
    }
    /**
     * Stop watching a plugin
     */
    unwatch(pluginId) {
        const watched = this.watchedPlugins.get(pluginId);
        if (watched) {
            if (watched.pendingReload) {
                clearTimeout(watched.pendingReload);
            }
            watched.watcher.close();
            this.watchedPlugins.delete(pluginId);
            this.emit('unwatched', { pluginId });
        }
    }
    /**
     * Stop watching all plugins
     */
    unwatchAll() {
        for (const pluginId of this.watchedPlugins.keys()) {
            this.unwatch(pluginId);
        }
    }
    /**
     * Handle file change event
     */
    handleFileChange(pluginId, pluginPath, filename, eventType) {
        const watched = this.watchedPlugins.get(pluginId);
        if (!watched)
            return;
        // Check if file should be ignored
        if (this.shouldIgnore(filename))
            return;
        // Check if file matches watch patterns
        if (!this.shouldWatch(filename))
            return;
        const changedFile = path.join(pluginPath, filename);
        const changeType = eventType === 'rename'
            ? (fs.existsSync(changedFile) ? 'add' : 'unlink')
            : 'change';
        // Check reload conditions
        const isManifest = filename === 'manifest.json' || filename === 'package.json';
        if (isManifest && !this.config.reloadOnManifestChange)
            return;
        if (!isManifest && !this.config.reloadOnSourceChange)
            return;
        // Debounce reload
        if (watched.pendingReload) {
            clearTimeout(watched.pendingReload);
        }
        watched.pendingReload = setTimeout(() => {
            this.triggerReload(pluginId, changedFile, changeType);
        }, this.config.debounceMs);
    }
    /**
     * Trigger a plugin reload
     */
    async triggerReload(pluginId, changedFile, changeType) {
        const watched = this.watchedPlugins.get(pluginId);
        if (!watched)
            return;
        watched.pendingReload = null;
        const event = {
            pluginId,
            pluginPath: watched.path,
            changedFile,
            changeType,
            timestamp: new Date(),
        };
        this.emit('reload-start', event);
        try {
            // Call the reload callback
            if (this.reloadCallback) {
                await this.reloadCallback(pluginId);
            }
            watched.lastReload = new Date();
            this.emit('reload-complete', event);
            if (this.config.showNotifications) {
                this.emit('notification', {
                    type: 'success',
                    message: `Plugin ${pluginId} reloaded`,
                    file: changedFile,
                });
            }
        }
        catch (error) {
            this.emit('reload-error', { ...event, error });
            if (this.config.showNotifications) {
                this.emit('notification', {
                    type: 'error',
                    message: `Failed to reload plugin ${pluginId}`,
                    error: error instanceof Error ? error.message : String(error),
                });
            }
        }
    }
    /**
     * Check if a file should be ignored
     */
    shouldIgnore(filename) {
        for (const pattern of this.config.ignorePatterns) {
            if (this.matchPattern(filename, pattern)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Check if a file matches watch patterns
     */
    shouldWatch(filename) {
        for (const pattern of this.config.watchPatterns) {
            if (this.matchPattern(filename, pattern)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Simple glob pattern matching
     */
    matchPattern(filename, pattern) {
        // Convert glob pattern to regex
        const regexPattern = pattern
            .replace(/\*\*/g, '.*')
            .replace(/\*/g, '[^/]*')
            .replace(/\?/g, '.')
            .replace(/\./g, '\\.');
        const regex = new RegExp(`^${regexPattern}$`);
        return regex.test(filename) || regex.test(path.basename(filename));
    }
    /**
     * Get list of watched plugins
     */
    getWatchedPlugins() {
        return Array.from(this.watchedPlugins.keys());
    }
    /**
     * Check if a plugin is being watched
     */
    isWatching(pluginId) {
        return this.watchedPlugins.has(pluginId);
    }
    /**
     * Get watch status for a plugin
     */
    getWatchStatus(pluginId) {
        const watched = this.watchedPlugins.get(pluginId);
        if (!watched) {
            return { watching: false };
        }
        return {
            watching: true,
            lastReload: watched.lastReload,
            path: watched.path,
        };
    }
    /**
     * Force reload a plugin
     */
    async forceReload(pluginId) {
        const watched = this.watchedPlugins.get(pluginId);
        if (watched) {
            await this.triggerReload(pluginId, watched.path, 'change');
        }
        else if (this.reloadCallback) {
            await this.reloadCallback(pluginId);
        }
    }
    /**
     * Update configuration
     */
    updateConfig(config) {
        this.config = { ...this.config, ...config };
    }
    /**
     * Get current configuration
     */
    getConfig() {
        return { ...this.config };
    }
    /**
     * Shutdown the hot reloader
     */
    shutdown() {
        this.unwatchAll();
        this.removeAllListeners();
    }
}
// Singleton instance
let hotReloaderInstance = null;
/**
 * Get hot reloader instance
 */
export function getPluginHotReloader(config) {
    if (!hotReloaderInstance) {
        hotReloaderInstance = new PluginHotReloader(config);
    }
    return hotReloaderInstance;
}
/**
 * Reset hot reloader
 */
export function resetPluginHotReloader() {
    if (hotReloaderInstance) {
        hotReloaderInstance.shutdown();
        hotReloaderInstance = null;
    }
}
export default PluginHotReloader;
//# sourceMappingURL=hot-reload.js.map