/**
 * router.js — Graph-based routing for el-ui.
 *
 * Routes are nodes in the graph. Navigation activates the target route node,
 * which spreads through the graph to any components subscribed to routing.
 * This means route changes propagate with the same activation semantics as
 * state changes — route-dependent components update automatically.
 *
 * Route nodes have:
 *   type: 'route'
 *   name: the path string
 *   content: the path string
 *   importance: starts at 0.5, boosted on activation (recently visited routes
 *               become more salient — models browser history behavior)
 */

export class Router {
    /**
     * @param {import('./graph.js').Graph} graph
     * @param {Record<string, any>} routes  path → Component class
     */
    constructor(graph, routes) {
        this.graph = graph;
        this.routes = routes;
        this.currentPath = window.location.pathname;
        this._routeNodes = {};
        this._subscribers = [];

        // Seed each route as a node in the graph
        for (const [path] of Object.entries(routes)) {
            const id = graph.seed({ type: 'route', name: path, content: path, importance: 0.3 });
            this._routeNodes[path] = id;
        }

        // Connect route nodes in path order (parent → child activation chain)
        // e.g., / → /about → /about/team
        const paths = Object.keys(routes).sort();
        for (let i = 0; i < paths.length - 1; i++) {
            const parent = paths[i];
            const child = paths[i + 1];
            if (child.startsWith(parent) && parent !== child) {
                graph.connect(this._routeNodes[parent], this._routeNodes[child], {
                    weight: 0.8,
                    relation: 'subroute',
                });
            }
        }

        // Listen for browser back/forward
        window.addEventListener('popstate', () => {
            this._activate(window.location.pathname);
        });
    }

    /**
     * Navigate to a path, update history, and activate the route graph node.
     * @param {string} path
     * @param {boolean} [replace=false]  use replaceState instead of pushState
     */
    navigate(path, replace = false) {
        if (path === this.currentPath) return;

        if (replace) {
            history.replaceState({}, '', path);
        } else {
            history.pushState({}, '', path);
        }

        this._activate(path);
    }

    /**
     * Get the Component class for the current path.
     * Falls back to the '*' wildcard route if present.
     * @returns {any}
     */
    currentComponent() {
        return this.routes[this.currentPath] ?? this.routes['*'] ?? null;
    }

    /**
     * Subscribe to route changes.
     * @param {function(string): void} callback  called with the new path
     * @returns {function} unsubscribe
     */
    subscribe(callback) {
        this._subscribers.push(callback);
        return () => {
            const idx = this._subscribers.indexOf(callback);
            if (idx >= 0) this._subscribers.splice(idx, 1);
        };
    }

    /**
     * Generate an href-compatible link string.
     * @param {string} path
     * @returns {string}
     */
    href(path) {
        return path;
    }

    /** @private */
    _activate(path) {
        const prevPath = this.currentPath;
        this.currentPath = path;

        // Find the best matching route
        const matchedPath = this._matchPath(path);
        if (matchedPath && this._routeNodes[matchedPath]) {
            // Spreading activation from the new route node
            this.graph.activate(this._routeNodes[matchedPath]);
            // Boost importance of the newly visited route
            const node = this.graph.get(this._routeNodes[matchedPath]);
            if (node) node.importance = Math.min(1.0, node.importance + 0.2);
        }

        // Notify route subscribers
        if (path !== prevPath) {
            this._subscribers.forEach(cb => cb(path));
        }
    }

    /** @private */
    _matchPath(path) {
        // Exact match first
        if (this.routes[path]) return path;
        // Prefix match (longest prefix wins)
        const candidates = Object.keys(this.routes)
            .filter(r => r !== '*' && path.startsWith(r))
            .sort((a, b) => b.length - a.length);
        if (candidates.length > 0) return candidates[0];
        // Wildcard
        return '*';
    }
}
