/**
 * Router - Strict navigation and state management
 * Enforces proper route handling and prevents direct URL manipulation
 */

import Module from './Module.js';

class Router extends Module {
    constructor(name, dependencies, config = {}) {
        super(name, ['eventBus']);

        // Validate dependencies
        if (!dependencies.eventBus) {
            throw new Error('Router requires EventBus dependency');
        }

        this._eventBus = dependencies.eventBus;
        this._routes = new Map();
        this._currentRoute = null;
        this._history = [];
        this._maxHistorySize = config.maxHistorySize || 100;
        this._defaultRoute = config.defaultRoute || '/';

        // TODO: Re-add Object.seal(this) after debugging
    }

    async init() {
        this._validateNotDestroyed();

        // Set up browser navigation handling
        if (typeof window !== 'undefined') {
            window.addEventListener('popstate', this._handlePopState.bind(this));
        }

        // Set up route change listener
        this._eventBus.on('router:navigate', (event) => {
            this.navigate(event.data.path, event.data.state);
        }, this.name);

        this._setInitialized();
    }

    async destroy() {
        this._validateNotDestroyed();

        // Clean up event listeners
        if (typeof window !== 'undefined') {
            window.removeEventListener('popstate', this._handlePopState.bind(this));
        }

        // Clear routes and history
        this._routes.clear();
        this._history = [];
        this._currentRoute = null;

        this._setDestroyed();
    }

    /**
     * Register a route with its handler
     * @param {string} path - Route path pattern
     * @param {Function} handler - Function to handle the route
     * @param {Object} options - Route options (guards, middleware, etc.)
     */
    register(path, handler, options = {}) {
        this._validateInitialized();
        this._validatePath(path);
        this._validateHandler(handler);

        if (this._routes.has(path)) {
            throw new Error(`Route ${path} is already registered`);
        }

        const route = {
            path,
            handler,
            options: {
                title: options.title || '',
                guards: options.guards || [],
                middleware: options.middleware || [],
                exact: options.exact !== false // Default to exact matching
            }
        };

        this._routes.set(path, route);

        // Emit route registered event
        this._eventBus.emit('router:route-registered', { path }, this.name);
    }

    /**
     * Unregister a route
     * @param {string} path - Route path to unregister
     */
    unregister(path) {
        this._validateInitialized();

        if (!this._routes.has(path)) {
            return false;
        }

        this._routes.delete(path);

        // Emit route unregistered event
        this._eventBus.emit('router:route-unregistered', { path }, this.name);

        return true;
    }

    /**
     * Navigate to a specific path
     * @param {string} path - Target path
     * @param {Object} state - State object to pass with navigation
     * @param {boolean} replace - Whether to replace current history entry
     */
    navigate(path, state = {}, replace = false) {
        this._validateInitialized();
        this._validatePath(path);

        const route = this._findMatchingRoute(path);
        if (!route) {
            throw new Error(`No route found for path: ${path}`);
        }

        // Run route guards
        if (!this._runGuards(route, path, state)) {
            return false;
        }

        // Update browser history
        if (replace) {
            window.history.replaceState(state, route.options.title, path);
        } else {
            window.history.pushState(state, route.options.title, path);
        }

        // Execute navigation
        this._executeRoute(route, path, state);

        return true;
    }

    /**
     * Go back in history
     */
    back() {
        this._validateInitialized();
        window.history.back();
    }

    /**
     * Go forward in history
     */
    forward() {
        this._validateInitialized();
        window.history.forward();
    }

    /**
     * Replace current route
     * @param {string} path - New path
     * @param {Object} state - New state
     */
    replace(path, state = {}) {
        this.navigate(path, state, true);
    }

    /**
     * Get current route information
     */
    getCurrentRoute() {
        return this._currentRoute ? { ...this._currentRoute } : null;
    }

    /**
     * Get navigation history
     */
    getHistory() {
        return [...this._history];
    }

    /**
     * Check if a path matches the current route
     * @param {string} path - Path to check
     */
    isCurrentRoute(path) {
        return this._currentRoute && this._currentRoute.path === path;
    }

    // Private methods
    _handlePopState(event) {
        const path = window.location.pathname + window.location.search + window.location.hash;
        const state = event.state || {};

        const route = this._findMatchingRoute(path);
        if (route) {
            this._executeRoute(route, path, state);
        } else {
            // Navigate to default route if no match
            this.navigate(this._defaultRoute);
        }
    }

    _handleCurrentRoute() {
        const path = window.location.pathname + window.location.search + window.location.hash;
        const route = this._findMatchingRoute(path);

        if (route) {
            this._executeRoute(route, path, window.history.state || {});
        } else {
            // Navigate to default route
            this.navigate(this._defaultRoute);
        }
    }

    _findMatchingRoute(path) {
        for (const [routePath, route] of this._routes) {
            if (this._pathMatches(routePath, path, route.options.exact)) {
                return route;
            }
        }
        return null;
    }

    _pathMatches(routePath, actualPath, exact = true) {
        if (exact) {
            return routePath === actualPath;
        } else {
            return actualPath.startsWith(routePath);
        }
    }

    _runGuards(route, path, state) {
        for (const guard of route.options.guards) {
            try {
                if (!guard(path, state)) {
                    return false;
                }
            } catch (error) {
                console.error('Route guard error:', error);
                return false;
            }
        }
        return true;
    }

    async _executeRoute(route, path, state) {
        // Run middleware
        for (const middleware of route.options.middleware) {
            try {
                await middleware(path, state);
            } catch (error) {
                console.error('Route middleware error:', error);
                return;
            }
        }

        // Update current route
        const routeInfo = {
            path,
            state,
            timestamp: Date.now()
        };

        this._currentRoute = routeInfo;
        this._addToHistory(routeInfo);

        // Emit route change event
        this._eventBus.emit('router:route-changed', {
            path,
            state,
            previous: this._history[this._history.length - 2] || null
        }, this.name);

        // Execute route handler
        try {
            await route.handler(path, state);
        } catch (error) {
            console.error('Route handler error:', error);

            // Emit route error event
            this._eventBus.emit('router:route-error', {
                path,
                error: error.message
            }, this.name);
        }
    }

    _addToHistory(routeInfo) {
        this._history.push(routeInfo);

        // Limit history size
        if (this._history.length > this._maxHistorySize) {
            this._history.shift();
        }
    }

    _validatePath(path) {
        if (!path || typeof path !== 'string') {
            throw new Error('Path must be a non-empty string');
        }
    }

    _validateHandler(handler) {
        if (typeof handler !== 'function') {
            throw new Error('Route handler must be a function');
        }
    }
}

// Freeze to prevent modification
Object.freeze(Router);
Object.freeze(Router.prototype);

export default Router;