// Clean SPA Router - Unified routing system for LibrePortal
class LibrePortalSPAClean {
  constructor() {
    this.routes = new Map();
    this.currentRoute = null;
    this.isLoading = false;
    this.dataLoaded = false;
    this.apps = [];
    this.categories = [];
    this.init();
  }

  async init() {
    
    // Setup routes from the feature manifest (falls back to the built-in table)
    await this.setupRoutesFromManifest();

    // Wait for DOM to be ready
    if (document.readyState === 'loading') {
      await new Promise(resolve => {
        document.addEventListener('DOMContentLoaded', resolve);
      });
    }
    
    // Wait for topbar to load first
    await this.waitForTopbar();
    
    // Load data first
    await this.loadCoreData();
    
    // Handle initial route
    this.handleInitialRoute();
    
  }

  async waitForTopbar() {
    
    // Wait for topbar component to be available and loaded
    let attempts = 0;
    const maxAttempts = 20; // 2 seconds max (reduced from 5 seconds)
    
    while (attempts < maxAttempts) {
      if (typeof TopbarComponent !== 'undefined' && TopbarComponent.loadTopbar) {
        try {
          await TopbarComponent.loadTopbar();
          return;
        } catch (error) {
          console.warn('⚠️ Topbar loading failed, retrying...', error);
        }
      }
      
      await new Promise(resolve => setTimeout(resolve, 100));
      attempts++;
    }
    
    console.warn('⚠️ Topbar failed to load after 2 seconds, continuing without topbar');
    // Don't block the entire app if topbar fails
  }

  setupRoutes() {
    // Clean route definitions with explicit handlers
    this.routes.set('/', () => this.handleDashboard());
    this.routes.set('/dashboard', () => this.handleDashboard());
    this.routes.set('/apps', () => this.handleApps());
    this.routes.set('/apps*', () => this.handleApps());      // /apps/<category> — MUST precede /app* (/apps startsWith /app)
    this.routes.set('/app', () => this.handleAppDetail());   // /app/<name>
    this.routes.set('/app*', () => this.handleAppDetail());
    this.routes.set('/admin', () => this.handleAdmin());     // Admin area (path-based: /admin/config/<x>, /admin/tools/<x>)
    this.routes.set('/admin*', () => this.handleAdmin());
    this.routes.set('/tasks', () => this.handleTasks()); // Handle /tasks without query
    this.routes.set('/tasks*', () => this.handleTasks()); // Handle /tasks with query
    // Legacy /config, /peers, /ssh, /backup, /overview are handled by
    // _legacyRedirect() at the top of navigate() (rewritten to their canonical
    // /admin/* or /apps/overview* paths before routing).

  }

  // Build the route table from the feature manifest (window.LP.features) so
  // "what pages exist" lives in one declarative place (components/manifest.dev.json)
  // instead of being hardcoded here. Route insertion order is preserved from the
  // manifest, which keeps the wildcard precedence findRouteHandler() relies on
  // (e.g. '/apps*' must be inserted before '/app*'). Falls back to the built-in
  // setupRoutes() table if the manifest is missing, empty, or names a handler
  // this class doesn't define — routing must never be left half-wired.
  async setupRoutesFromManifest() {
    try {
      const manifest = (window.LP && window.LP.features)
        ? await window.LP.features.loadManifest()
        : null;
      const entries = manifest && manifest.features;
      if (!entries || !entries.length) {
        this.setupRoutes();
        return;
      }

      // Load each feature's self-registering module (if declared) BEFORE building
      // routes, so LP.features.get(id) sees the registered mount(). This is what
      // lets index.html drop its per-feature <script> list — the manifest is the
      // one place a feature is declared. loadScript is idempotent + non-fatal: a
      // module that 404s or fails to register simply leaves that route on its
      // legacy handler.
      const modules = [...new Set(entries.map(f => f.module).filter(Boolean))];
      await Promise.all(modules.map(src =>
        this.loadScript(src).catch(err => console.warn(`[spa] feature module "${src}" failed to load:`, err))
      ));
      // A feature is routable if it has a registered module (mount) OR a legacy
      // handler. New features (e.g. updater) ship a module and no handler —
      // that's fine. Only bail to the built-in table if an entry has NEITHER.
      for (const f of entries) {
        const m = (window.LP && window.LP.features) ? window.LP.features.get(f.id) : null;
        const hasModule = m && typeof m.mount === 'function';
        const hasHandler = typeof this[f.handler] === 'function';
        if (!hasModule && !hasHandler) {
          console.warn(`[spa] feature "${f.id}" has neither a registered module nor a legacy handler — using built-in routes`);
          this.setupRoutes();
          return;
        }
      }
      this.routes.clear();
      for (const f of entries) {
        // A feature that has registered a module with a mount() is driven
        // through the kernel lifecycle; everything else still runs its legacy
        // handleX() method (strangler migration — both coexist).
        const mod = (window.LP && window.LP.features) ? window.LP.features.get(f.id) : null;
        const handler = (mod && typeof mod.mount === 'function')
          ? () => this._mountFeature(f)
          : () => this[f.handler]();
        for (const route of (f.routes || [])) {
          this.routes.set(route, handler);
        }
      }
      this._routesFromManifest = true;
    } catch (err) {
      console.error('[spa] manifest routing failed, using built-in routes:', err);
      this.setupRoutes();
    }
  }

  // Drive a feature module's mount() through the kernel lifecycle. Falls back
  // to the feature's legacy handler if no module is registered or mount throws,
  // so a broken/absent module can never leave the route dead.
  async _mountFeature(f) {
    const mod = (window.LP && window.LP.features) ? window.LP.features.get(f.id) : null;
    if (!mod || typeof mod.mount !== 'function') {
      return (typeof this[f.handler] === 'function') ? this[f.handler]() : this.showError('Page not found');
    }
    try {
      const ctx = new window.LP.kernel.MountContext(this, { path: window.location.pathname + window.location.search });
      await mod.mount(ctx);
      this._mountedFeature = { id: f.id, mod, ctx, handler: f.handler };
    } catch (err) {
      console.error(`[kernel] mount("${f.id}") failed, falling back to handler:`, err);
      this._mountedFeature = null;
      if (typeof this[f.handler] === 'function') return this[f.handler]();
      this.showError('Failed to load ' + f.id);
    }
  }

  // Unmount the currently-mounted feature (if any) before navigating away, so
  // its listeners/streams are released. Legacy handlers don't go through here,
  // so this only fires when leaving a migrated feature.
  async _unmountCurrentFeature() {
    const cur = this._mountedFeature;
    if (!cur) return;
    this._mountedFeature = null;
    try { if (typeof cur.mod.unmount === 'function') await cur.mod.unmount(cur.ctx); }
    catch (e) { console.error(`[kernel] unmount("${cur.id}") error:`, e); }
    try { cur.ctx && cur.ctx.teardown(); } catch (_) {}
  }

  async loadCoreData() {
    
    try {
      // Load apps
      if (typeof DataLoader !== 'undefined' && DataLoader.loadApps) {
        this.apps = await DataLoader.loadApps();
        window.apps = this.apps;
      }
      
      // Load categories
      if (typeof DataLoader !== 'undefined' && DataLoader.loadCategories) {
        this.categories = await DataLoader.loadCategories();
        window.categories = this.categories;
        window.sidebarCategories = this.categories; // Ensure this is always available
      }
      
      this.dataLoaded = true;
      
    } catch (error) {
      console.error('❌ Failed to load core data:', error);
      this.showError('Failed to load application data');
    }
  }

  handleInitialRoute() {
    const path = window.location.pathname + window.location.search;
    
    // Handle root path - redirect to dashboard
    if (path === '/' || path === '') {
      this.navigate('/dashboard', false);
    } else {
      this.navigate(path, false); // Don't add to history for initial load
    }
  }

  async navigate(path, addToHistory = true) {

    // Legacy URL redirects (the old /ssh, /peers, /config short URLs → the Admin
    // area). Rewritten here, at the top of navigate() and BEFORE the isLoading
    // guard, so the canonical /admin/* path is what mounts + shows in the address
    // bar. This replaces the former config-redirect/peers/ssh redirect
    // components (whose handlers re-entered navigate() and were silently no-op'd
    // by the guard below).
    const redirected = this._legacyRedirect(path);
    if (redirected && redirected !== path) {
      path = redirected;
      try { history.replaceState({ route: path }, '', path); } catch (_) {}
    }

    if (this.isLoading) {
      return;
    }

    if (this.currentRoute === path && addToHistory) {
      return;
    }

    // Unsaved-config guard — an app's config panel registers
    // window.__appConfigNavGuard while it has unsaved changes, so it can
    // intercept navigation away (Apply / Discard / Stay).
    if (typeof window.__appConfigNavGuard === 'function') {
      try {
        const decision = await window.__appConfigNavGuard(path);
        if (decision === 'stay') return;
      } catch (e) {
        console.error('Nav guard error:', e);
      }
    }

    // (The dashboard data-reload that used to live here is now folded into the
    // dashboard feature module's mount(), so it fires exactly once per
    // navigation. See components/dashboard/index.js.)

    this.isLoading = true;

    try {
      // Update browser history
      if (addToHistory) {
        history.pushState({ route: path }, '', path);
      } else {
        // Even for "don't add to history" navigations (initial route, back-compat
        // rewrites) we stamp the current entry's state with the resolved route.
        // Without this the very first history entry has state: null, so popstate
        // back to it from a later pushState'd entry hits the null guard and
        // does nothing — exactly the "back button doesn't work" symptom.
        const here = window.location.pathname + window.location.search;
        history.replaceState({ route: path }, '', here);
      }

      this.currentRoute = path;

      // Release the previously-mounted feature module (if any) before rendering
      // the next route, so live streams/listeners don't outlive their page.
      await this._unmountCurrentFeature();

      // Find and execute route handler
      const handler = this.findRouteHandler(path);
      if (handler) {
        await handler();
      } else {
        console.warn('❌ No handler found for:', path);
        this.showError('Page not found');
      }

      // Update navigation highlighting after content loads. The live topbar
      // singleton recomputes the active nav from the current path.
      window.topbar?.setActiveNav?.();

    } catch (error) {
      console.error('❌ Navigation error:', error);
      this.showError('Navigation failed');
    } finally {
      this.isLoading = false;
    }
  }

  findRouteHandler(path) {
    
    // Exact match first
    if (this.routes.has(path)) {
      return this.routes.get(path);
    }

    // Handle query parameters - strip them for matching
    let basePath = path;
    if (path.includes('?')) {
      basePath = path.split('?')[0];
    }
    
    
    // Check base path against routes
    if (this.routes.has(basePath)) {
      return this.routes.get(basePath);
    }

    // Wildcard matching for remaining cases
    for (const [route, handler] of this.routes) {
      if (route.includes('*')) {
        const pattern = route.replace('*', '');
        if (basePath.startsWith(pattern)) {
          return handler;
        }
      }
    }

    return null;
  }

  async handleDashboard() {
    
    try {
      const html = await this.fetchContent('/components/dashboard/html/dashboard-content.html');
      this.loadContent(html, 'Dashboard');
      
      // Dashboard should already be initialized by SystemLoader
      if (typeof loadInstalledApps === 'function') {
        loadInstalledApps();
      }
    } catch (error) {
      console.error('❌ Dashboard load error:', error);
      this.showError('Failed to load dashboard');
    }
  }

  // Map a legacy short URL (/ssh, /peers, /config[?=cat]) to its canonical
  // /admin/* path, or return null if it isn't a legacy redirect. Used at the top
  // of navigate(). Replaces the old config-redirect/peers/ssh handlers.
  _legacyRedirect(path) {
    const full = path || '';
    const p = full.split('?')[0];
    if (p === '/ssh' || p.startsWith('/ssh/')) return '/admin/tools/ssh-access';
    // Peers now lives under App Center › Overview › Migrate › Peers (moved out of Admin).
    if (p === '/peers' || p.startsWith('/peers/') || p === '/admin/tools/peers' || p.startsWith('/admin/tools/peers/')) return '/apps/overview/migrate/peers';
    if (p === '/config' || p.startsWith('/config/') || full.startsWith('/config?')) {
      let cat = 'overview';
      if (full.includes('?=')) cat = full.split('?=')[1] || 'overview';
      else if (full.includes('?')) cat = new URLSearchParams(full.split('?')[1]).get('config') || 'overview';
      else { const seg = p.replace(/^\/config\/?/, ''); if (seg) cat = seg; }
      return (typeof window.adminPath === 'function') ? window.adminPath(cat) : '/admin';
    }
    // The fleet Overview area lives under App Center: /apps/overview[/tab].
    // Rewrite the legacy short forms so old bookmarks/links keep working and the
    // address bar always shows the canonical path:
    //   /overview[/tab]  -> /apps/overview[/tab]
    //   /backup[/sub]    -> /apps/overview/backups[/sub]  (the backup center is
    //                       now the Overview › Backups tab, not a standalone page)
    //   /updater[/tab]   -> /apps/overview[/tab]; security/recovery/history were
    //                       folded into the Updates expander, so they land on
    //                       /apps/overview/updates.
    if (p === '/overview' || p.startsWith('/overview/')) {
      return '/apps' + p;
    }
    if (p === '/backup' || p.startsWith('/backup/')) {
      const sub = p.replace(/^\/backup\/?/, '');
      return sub ? '/apps/overview/backups/' + sub : '/apps/overview/backups';
    }
    if (p === '/updater' || p.startsWith('/updater/')) {
      const seg = p.replace(/^\/updater\/?/, '');
      if (seg === 'updates' || seg === 'improvements') return '/apps/overview/' + seg;
      if (!seg || seg === 'overview') return '/apps/overview';
      return '/apps/overview/updates';
    }
    return null;
  }

  async handleApps() {
    
    // Category from the path (/apps/<category>), else legacy ?=<cat> / ?apps=.
    const seg = window.location.pathname.replace(/^\/apps\/?/, '').split('/')[0];
    if (seg) {
        window.appsCategory = decodeURIComponent(seg);
    } else {
        const search = window.location.search || '';
        if (search.includes('?=')) {
            window.appsCategory = (window.location.pathname + search).split('?=')[1] || 'all';
        } else {
            window.appsCategory = new URLSearchParams(search).get('apps') || 'all';
        }
    }
    
    try {
      // Ensure unified layout is loaded (like the old SPA)
      if (!document.querySelector('.apps-layout')) {
        const html = await this.fetchContent('/components/apps/core/html/apps-unified-layout.html');
        this.loadContent(html, 'Applications');
      } else {
      }
      
      // Apps manager should already be initialized by SystemLoader
      if (window.appsManager) {
        await window.appsManager.initialize();
        
      } else {
        console.error('AppsManager not available - SystemLoader should have initialized it');
        throw new Error('AppsManager not initialized by SystemLoader');
      }
    } catch (error) {
      console.error('❌ Apps load error:', error);
      this.showError('Failed to load applications: ' + error.message);
    }
  }

  async handleAppDetail() {
    
    // Extract app name. Path-based /app/<name> first, then legacy ?app= / ?=name.
    const url = new URL(window.location);
    let appName = url.pathname.replace(/^\/app\/?/, '').split('/')[0];
    appName = appName ? decodeURIComponent(appName) : '';

    if (!appName) appName = url.searchParams.get('app');

    // Handle old format ?=appname&tab=tabname
    if (!appName && url.search.includes('?=')) {
      const queryPart = url.search.replace('?', '');
      const parts = queryPart.split('&');
      for (const part of parts) {
        if (part.startsWith('=')) {
          appName = part.substring(1); // Remove the '='
          break;
        }
      }
    }
    
    
    if (!appName) {
      this.navigate('/apps', false);
      return;
    }

    // Back-compat: rewrite legacy /app/<name>?tab=<tab>[&config=<sub>][&task=<id>]
    // URLs to the path-based shape (/app/<name>/<tab> | /<name>/config/<sub>)
    // before the rest of the page reads URL state. The replaceState avoids
    // leaving a stale query in the address bar for shareable links.
    const legacyTab = url.searchParams.get('tab');
    const legacyConfig = url.searchParams.get('config');
    if (legacyTab || legacyConfig) {
      const tab = legacyTab === 'logs' ? 'tasks' : (legacyTab || 'config');
      const sub = (tab === 'config') ? legacyConfig : null;
      const taskId = url.searchParams.get('task');
      const canonical = window.appPath(appName, tab, sub, taskId);
      if (canonical !== url.pathname + url.search) {
        window.history.replaceState({ route: canonical }, '', canonical);
      }
    }

    try {
      const html = await this.fetchContent('/components/apps/core/html/apps-unified-layout.html');
      this.loadContent(html, appName); // Will be updated after app data loads
      
      // AppTabbedManager should already be initialized by SystemLoader
      if (window.appTabbedManager) {
        await window.appTabbedManager.initialize();
      } else {
        console.error('AppTabbedManager not available - SystemLoader should have initialized it');
        throw new Error('AppTabbedManager not initialized by SystemLoader');
      }
      
    } catch (error) {
      console.error('❌ App detail load error:', error);
      this.showError('Failed to load application details');
    }
  }

  // Admin area. Path-based: /admin (overview), /admin/config/<category>,
  // /admin/tools/ssh-access. Reuses the config page shell + ConfigManager.
  async handleAdmin() {
    window.configCategory = window.adminCategoryFromPath(window.location.pathname);

    try {
      const html = await this.fetchContent('/components/admin/config/html/config-content.html');
      this.loadContent(html, 'Admin');

      if (window.configManager) {
        if (typeof window.configManager.renderConfig === 'function') {
          await window.configManager.renderConfig(window.configCategory || 'overview');
        }
      } else {
        console.error('ConfigManager not available - SystemLoader should have initialized it');
        throw new Error('ConfigManager not initialized by SystemLoader');
      }
    } catch (error) {
      console.error('❌ Admin load error:', error);
      this.showError('Failed to load the Admin area');
    }
  }

  // Legacy /config and /config?=<x> → the path-based /admin equivalent.
  async handleTasks() {
    
    try {
      const html = await this.fetchContent('/components/tasks/html/tasks-content.html');
      this.loadContent(html, 'Tasks');
      
      // Tasks manager should already be initialized by SystemLoader
      if (window.tasksManager) {
        await window.tasksManager.init();
      } else {
        console.warn('⚠️ TasksManager not available yet, task functionality will be limited');
        // Don't throw error - just show warning and continue
        // The task system will be available when the user actually interacts with tasks
      }
    } catch (error) {
      console.error('❌ Tasks load error:', error);
      this.showError('Failed to load tasks');
    }
  }

  async fetchContent(url) {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    return await response.text();
  }

  async loadScript(src) {
    const scriptId = src.replace(/[^a-zA-Z0-9]/g, '_');
    if (document.getElementById(scriptId)) {
      return; // Already loaded
    }

    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      script.id = scriptId;
      script.onload = resolve;
      // Remove the element on failure so the getElementById() dedupe above can't
      // make a later retry resolve instantly without re-fetching.
      script.onerror = (e) => { try { script.remove(); } catch (_) {} reject(e); };
      document.head.appendChild(script);
    });
  }

  loadContent(html, title) {
    const container = document.getElementById('main-content') || document.querySelector('.main');
    if (!container) {
      throw new Error('Content container not found');
    }

    container.innerHTML = html;
    document.title = `${title} - LibrePortal`;
    
    // Update navigation highlighting
    this.updateNavigation();
  }

  updateNavigation() {
    
    // Remove ALL active classes
    document.querySelectorAll('.nav-item').forEach(item => {
      item.classList.remove('active');
      item.classList.remove('nav-active');
    });

    // Use path-based detection only for consistency
    const path = window.location.pathname;
    
    let activeId = 'nav-dashboard'; // default
    
    // Overview + the backup center live under the App Center umbrella now;
    // /updater is redirected to /overview, so it never resolves here.
    if (path.startsWith('/app') || path.startsWith('/apps') || path.startsWith('/overview') || path.startsWith('/backup')) {
      activeId = 'nav-app-center';
    } else if (path.startsWith('/admin') || path.startsWith('/config') || path.startsWith('/ssh')) {
      activeId = 'nav-config';
    } else if (path.startsWith('/tasks')) {
      activeId = 'nav-tasks';
    } else if (path === '/' || path === '/dashboard') {
      activeId = 'nav-dashboard';
    }

    const activeElement = document.getElementById(activeId);
    if (activeElement) {
      activeElement.classList.add('nav-active');
    }
  }

  showError(message) {
    const container = document.getElementById('main-content') || document.querySelector('.main');
    if (container) {
      container.innerHTML = `
        <div class="error-page">
          <h2>Error</h2>
          <p>${message}</p>
          <button onclick="location.reload()">Reload Page</button>
        </div>
      `;
    }
  }
}

// Admin area path helpers (shared by the SPA, sidebar, overview, ssh page).
// Map a category to its path-based URL, and parse a path back to a category.
window.adminPath = function (category) {
  if (!category || category === 'overview') return '/admin/dashboard';  // the admin board
  if (category === 'system')     return '/admin/system';   // stats, not config
  if (category === 'ssh-access') return '/admin/tools/ssh-access';
  if (category === 'peers')      return '/admin/tools/peers';
  return '/admin/config/' + category;
};
window.adminCategoryFromPath = function (pathname) {
  const segs = String(pathname || '').replace(/^\/admin\/?/, '').split('/').filter(Boolean);
  // Bare /admin and /admin/dashboard both resolve to the overview board.
  if (!segs.length || segs[0] === 'overview' || segs[0] === 'dashboard') return 'overview';
  if (segs[0] === 'config') return segs[1] || 'general';
  if (segs[0] === 'tools') return segs[1] || 'overview';
  return segs[0];
};

// App-detail path helpers — mirror adminPath but with one extra optional
// segment for the config sub-tab category. The shape is:
//   /app/<name>                       — config tab, default sub-tab
//   /app/<name>/<tab>                 — non-config main tab (tasks, backups, …)
//   /app/<name>/config/<category>     — config tab with a specific sub-tab
// plus an optional ?task=<id> query for deep-linking a single task on the
// tasks tab (a transient deep link, not part of the navigation hierarchy).
window.appPath = function (appName, tab, sub, taskId) {
  if (!appName) return '/apps';
  let p = '/app/' + encodeURIComponent(appName);
  if (tab && tab !== 'config') {
    p += '/' + encodeURIComponent(tab);
  } else if (sub) {
    p += '/config/' + encodeURIComponent(sub);
  }
  if (taskId) p += '?task=' + encodeURIComponent(taskId);
  return p;
};
window.appPartsFromPath = function (pathname) {
  const segs = String(pathname || '').replace(/^\/app\/?/, '').split('/').filter(Boolean);
  const app = segs[0] ? decodeURIComponent(segs[0]) : '';
  let tab = segs[1] || 'config';
  let sub = null;
  if (tab === 'config') sub = segs[2] ? decodeURIComponent(segs[2]) : null;
  // `logs` is the legacy alias for the `tasks` main tab.
  if (tab === 'logs') tab = 'tasks';
  return { app, tab, sub };
};

// Global tasks page path helpers — bring the single-task deep link in line with
// /app/<name> and /admin/<…>: it's a path segment (/tasks/<category>/<id>), not
// a ?task= query. Task ids are guaranteed `task_<digits>_<base36>`, so the
// `task_` prefix is dropped in the URL for brevity and restored on read. A
// legacy ?task=<id> query is still parsed so older links and bookmarks resolve.
window.taskPath = function (category, taskId) {
  let p = '/tasks/' + encodeURIComponent(category || 'all');
  if (taskId) p += '/' + encodeURIComponent(String(taskId).replace(/^task_/, ''));
  return p;
};
window.taskPartsFromPath = function (pathname, search) {
  const segs = String(pathname || '').replace(/^\/tasks\/?/, '').split('/').filter(Boolean);
  const category = segs[0] || '';
  let taskId = segs[1] ? decodeURIComponent(segs[1]) : '';
  if (!taskId && search) {
    try { taskId = new URLSearchParams(search).get('task') || ''; } catch (_) {}
  }
  if (taskId && !/^task_/.test(taskId)) taskId = 'task_' + taskId;
  return { category, taskId };
};

// Global navigation function for click handlers
window.navigateToRoute = function(href) {
  if (window.spaClean) {
    
    // Convert href to clean path - handle various formats
    let route = href.replace('.html', '').replace('./', '').replace(/^\//, '');
    
    // Handle special cases
    if (route === '' || route === 'index') {
      route = '/apps'; // index goes to apps (main app center)
    } else if (route === 'dashboard') {
      route = '/dashboard';
    } else if (route === 'apps') {
      route = '/apps';
    } else if (route === 'config' || route === 'admin') {
      route = '/admin';
    } else if (route === 'tasks') {
      route = '/tasks';
    } else if (!route.startsWith('/')) {
      route = '/' + route;
    }
    
    window.spaClean.navigate(route);
  }
};

// Handle browser back/forward. Prefer the route stamped on the history
// entry's state; fall back to the URL bar so entries that somehow lack
// state (third-party history manipulation, very-early popstate before
// init() finished) still re-render the right page instead of no-op'ing.
window.addEventListener('popstate', (e) => {
  if (!window.spaClean) return;
  const route = (e.state && e.state.route) || (window.location.pathname + window.location.search);
  window.spaClean.navigate(route, false);
});

// Back-forward cache (BFCache) cooperation. Open SSE/WebSocket connections
// block the browser from snapshotting the page, so the back button ends up
// doing a full reload instead of restoring instantly. Closing every live
// bus we own on pagehide lets the snapshot happen; on pageshow with
// event.persisted === true the page just came back from BFCache and we
// reopen them. Fresh loads also fire pageshow but with persisted=false —
// we ignore those because the normal init paths already started everything.
window.addEventListener('pagehide', () => {
  try { window.taskEventBus && window.taskEventBus.stop(); } catch (_) {}
  try { window.LiveSystem && window.LiveSystem.pause && window.LiveSystem.pause(); } catch (_) {}
  try { window.servicesManager && window.servicesManager.pauseStreams && window.servicesManager.pauseStreams(); } catch (_) {}
});

window.addEventListener('pageshow', (e) => {
  if (!e.persisted) return;
  try { window.taskEventBus && window.taskEventBus.start(); } catch (_) {}
  try { window.LiveSystem && window.LiveSystem.resume && window.LiveSystem.resume(); } catch (_) {}
  // Service log tail streams aren't auto-resumed — the user can click
  // Resume on the overlay if they were tailing a log. Most BFCache restores
  // are "back to dashboard", so silently dropping the tail is fine.
});

// Handle internal link clicks
document.addEventListener('click', (e) => {
  const target = e.target.closest('a');
  if (target && target.href) {
    const href = target.getAttribute('href');
    if (href && (href.startsWith('/') || href.startsWith('./'))) {
      e.preventDefault();
      window.navigateToRoute(href);
    }
  }
});

// SPA initialization is now handled by SystemLoader
// LibrePortalSPAClean instance will be created centrally
