// kernel/lifecycle.js — the MountContext a feature module receives.
//
// Part of the feature-module architecture (docs/architecture/webui-architecture.md).
// A feature exports mount(ctx)/unmount(ctx); the SPA shell drives them and hands
// in this context. In this first form the content helpers delegate to the SPA
// shell (so rendering is byte-identical to the legacy handlers), plus a teardown
// ledger: any listener registered via ctx.on() or subscription via ctx.sub() is
// auto-released on unmount, so a feature can't leak document listeners or live
// streams across navigations. Shared services are injected as ctx.services
// (kernel/js/services.js).
(function () {
  const LP = (window.LP = window.LP || {});

  class MountContext {
    constructor(shell, route) {
      this.shell = shell;                  // the LibrePortalSPAClean instance
      this.route = route || {};
      this.path = this.route.path || (location.pathname + location.search);
      // Injected DI container — the cross-cutting services (tasks/live/auth/
      // data/notify/theme/modal/router). Lazy getters onto the live singletons.
      this.services = (window.LP && window.LP.services) || {};
      this._ac = new AbortController();     // backs ctx.on() — abort kills all
      this._unsubs = [];                    // backs ctx.sub() — SSE/bus/timer handles
    }

    // Lazy-load the feature's controller scripts in array order (idempotent
    // across re-mounts). Sequential, not parallel, so a feature can list a
    // base file before files that augment it (e.g. a class definition before
    // prototype-extension modules) — order in the array is honoured.
    async loadScripts(list) {
      for (const src of (list || [])) {
        await this.shell.loadScript(src);
      }
    }

    // Fetch an HTML fragment (same path the legacy handlers used).
    loadFragment(url) {
      return this.shell.fetchContent(url);
    }

    // Inject fragment HTML into #main-content + set the document title +
    // refresh nav highlighting — identical to the legacy loadContent().
    setContent(html, title) {
      return this.shell.loadContent(html, title);
    }

    // SPA navigation (for redirects from within a feature). addToHistory mirrors
    // navigate()'s second arg so a feature can do a no-history redirect exactly
    // like the legacy handlers (e.g. handleAppDetail's navigate('/apps', false)).
    nav(path, addToHistory = true) {
      return this.shell.navigate(path, addToHistory);
    }

    // addEventListener bound to this mount's AbortController — auto-removed on
    // teardown. target = window | document | a bus | an element.
    on(target, event, fn, opts) {
      target.addEventListener(event, fn, Object.assign({}, opts, { signal: this._ac.signal }));
    }

    // Track an arbitrary unsubscribe fn (SSE handle, bus off(), clearInterval
    // wrapper) so it's called on teardown.
    sub(unsub) {
      if (typeof unsub === 'function') this._unsubs.push(unsub);
      return unsub;
    }

    // Release everything opened during this mount. Called by the shell after
    // the feature's own unmount().
    teardown() {
      try { this._ac.abort(); } catch (_) {}
      this._unsubs.forEach(u => { try { u(); } catch (_) {} });
      this._unsubs.length = 0;
    }
  }

  LP.kernel = LP.kernel || {};
  LP.kernel.MountContext = MountContext;
})();
