/**
 * Managed Router — orchestrates module mount/unmount on route changes.
 *
 * When the router navigates to a route:
 * 1. Unmounts the previous module (destroy instance, unregister from GlobalContext)
 * 2. Mounts the new module (create instance with namespaced state, register)
 *
 * Module names are used as state prefixes (lowercased) for isolation.
 */

import type { IEngine, HypenModuleDefinition } from "./app.js";
import { HypenModuleInstance, HypenApp } from "./app.js";
import type { HypenRouter, RouteState } from "./router.js";
import type { HypenGlobalContext } from "./context.js";
import { createLogger } from "./logger.js";

const log = createLogger("ManagedRouter");

export interface RouteDefinition {
  /** Route path pattern (e.g., "/", "/profile/:id") */
  path: string;
  /** Component name — used to look up in app registry */
  component: string;
  /** Inline module definition (alternative to registry lookup) */
  module?: HypenModuleDefinition;
}

export class ManagedRouter {
  private router: HypenRouter;
  private engine: IEngine;
  private registry: HypenApp;
  private globalContext: HypenGlobalContext;
  private routes: RouteDefinition[] = [];
  private activeModule: HypenModuleInstance | null = null;
  private activeRoute: RouteDefinition | null = null;
  private unsubscribe: (() => void) | null = null;
  /** Cached instances for modules with persist=true */
  private persistedModules = new Map<string, HypenModuleInstance>();

  constructor(
    router: HypenRouter,
    engine: IEngine,
    registry: HypenApp,
    globalContext: HypenGlobalContext
  ) {
    this.router = router;
    this.engine = engine;
    this.registry = registry;
    this.globalContext = globalContext;
  }

  /**
   * Add a route definition.
   */
  addRoute(route: RouteDefinition): this {
    this.routes.push(route);
    return this;
  }

  /**
   * Start listening for route changes and mount the initial route.
   */
  start(): void {
    this.unsubscribe = this.router.onNavigate((routeState: RouteState) => {
      this.handleRouteChange(routeState);
    });
  }

  /**
   * Stop listening and unmount the active module.
   * Also destroys all persisted modules.
   */
  stop(): void {
    if (this.unsubscribe) {
      this.unsubscribe();
      this.unsubscribe = null;
    }
    this.unmountActive();

    // Destroy all persisted modules on full stop
    for (const [moduleId, instance] of this.persistedModules) {
      log.debug(`Destroying persisted module on stop: ${moduleId}`);
      instance.destroy();
      this.globalContext.unregisterModule(moduleId);
    }
    this.persistedModules.clear();

    // Clean up router event listeners
    this.router.dispose();
  }

  /**
   * Get the currently active module instance.
   */
  getActiveModule(): HypenModuleInstance | null {
    return this.activeModule;
  }

  /**
   * Get the currently active route.
   */
  getActiveRoute(): RouteDefinition | null {
    return this.activeRoute;
  }

  private handleRouteChange(routeState: RouteState): void {
    const path = routeState.currentPath;
    const matched = this.matchRoute(path);

    if (!matched) {
      log.debug(`No route matched for path: ${path}`);
      this.unmountActive();
      return;
    }

    // If same route, no need to remount
    if (this.activeRoute && this.activeRoute.path === matched.path) {
      return;
    }

    // Unmount old, mount new
    this.unmountActive();
    this.mount(matched);
  }

  private matchRoute(path: string): RouteDefinition | null {
    for (const route of this.routes) {
      if (this.router.matchPath(route.path, path) !== null) {
        return route;
      }
    }
    return null;
  }

  private mount(route: RouteDefinition): void {
    // Look up module definition: inline first, then registry
    const definition = route.module || this.registry.get(route.component);
    if (!definition) {
      log.debug(`No module definition found for component: ${route.component}`);
      this.activeRoute = route;
      return;
    }

    // Ensure the definition has a name for state namespacing
    const namedDef = {
      ...definition,
      name: definition.name || route.component.toLowerCase(),
    };

    const moduleId = namedDef.name!.toLowerCase();

    // Check for a persisted instance first
    const persisted = this.persistedModules.get(moduleId);
    if (persisted) {
      log.debug(`Restoring persisted module: ${moduleId} for route: ${route.path}`);
      this.activeModule = persisted;
      this.activeRoute = route;
      return;
    }

    log.debug(`Mounting module: ${namedDef.name} for route: ${route.path}`);

    const instance = new HypenModuleInstance(
      this.engine,
      namedDef as HypenModuleDefinition<object>,
      this.router,
      this.globalContext
    );

    // Register in global context under the module name (lowercase)
    this.globalContext.registerModule(moduleId, instance);

    this.activeModule = instance;
    this.activeRoute = route;
  }

  private unmountActive(): void {
    if (this.activeModule && this.activeRoute) {
      const definition = this.activeRoute.module || this.registry.get(this.activeRoute.component);
      const moduleId = (definition?.name || this.activeRoute.component).toLowerCase();
      const persist = definition?.persist === true;

      if (persist) {
        log.debug(`Persisting module: ${moduleId} (persist=true)`);
        this.persistedModules.set(moduleId, this.activeModule);
        // Keep registered in GlobalContext so other modules can still access its state
      } else {
        log.debug(`Unmounting module: ${moduleId}`);
        this.activeModule.destroy();
        this.globalContext.unregisterModule(moduleId);
      }
    }

    this.activeModule = null;
    this.activeRoute = null;
  }
}
