/* The one Switching Loader host (evolve-suite#1302): a single `useSwitchingLoader` instance from the shell global
   build, mounted ONCE at the app root (app.jsx, beside the ToastProvider), never inside Shell / OnboardingShell:
   PSA.switchWorkspace calls `resetShellContext`, the shell context goes null, and a host gated on that context
   unmounted mid-switch, so `show()` landed on the dying instance and the navigation was lost (dev QA case 13).
   It renders the loader and its persistent live region, and publishes `window.enterWithLoader(spec, go)` for the code
   that is not a component (the workspace switcher's handler, more-app-actions' deps), and
   `window.holdWithLoader(spec, work)` for a switch that stays on the page (evolve-suite#1343).

   Each published function is STABLE per host (it reads the current `show` through a ref) and is removed on unmount, but
   only if it is still THIS host's: a stale host must neither swallow a navigation (`SwitchLoader.enter` falls back to
   `go()` when none is published) nor clear a newer host's function.

   ONE LOADER AT A TIME: both functions share one slot. While a loader is up, a second call to either is DROPPED: nothing
   is shown, and a dropped `enterWithLoader`'s `go()` never runs. So a navigation asked for while a held loader is up is
   lost, by design (the shell ignores a second `show` too).

   FAIL-SAFE, for the navigation that DID take the slot: if the host unmounts while its loader is still waiting out its
   minimum, the pending `go()` runs at once (the shell's own onComplete would never fire from a dead component). The
   shell keeps the loader up until the page unloads (`go` is a full navigation). On a bfcache restore the shell clears
   its loader and never calls onComplete, so the host frees the slot then and does NOT run what was waiting: that
   navigation, if its minimum had not passed when the page was left, never happens. */
function SwitchLoaderHost() {
  const { loader, show, dismiss } = window.EvolveShell.useSwitchingLoader();
  const showRef = React.useRef(show);
  showRef.current = show;
  const dismissRef = React.useRef(dismiss);
  dismissRef.current = dismiss;
  // The navigation waiting on the loader's minimum (or a held loader's take-down, below), or null. Cleared when it runs
  // (from the shell's onComplete), and on a bfcache restore (the effect below), where it does not run.
  const pendingRef = React.useRef(null);
  const enterRef = React.useRef(null);
  if (enterRef.current === null) {
    enterRef.current = (spec, go) => {
      // One loader per entry (the shell ignores a second `show`), so a second go() is dropped here too.
      if (pendingRef.current !== null) return;
      const run = () => {
        if (pendingRef.current === run) pendingRef.current = null;
        // A loader WAS shown and Edge is leaving behind it: leave a stamp so the arrival (Edge → Suite door → Edge cannot carry
        // a Suite Referer) skips its own hold. Not stamped on `SwitchLoader.enter`'s no-host fallback: no loader was shown there.
        window.SwitchLoader.stampLeaving();
        go();
      };
      pendingRef.current = run;
      showRef.current(spec, run);
    };
  }
  window.enterWithLoader = enterRef.current;
  // evolve-suite#1343 — the loader AROUND `work` (a promise), for a workspace switch that stays in Edge. The shell's own
  // stay-on-the-page shape: it calls `done` only once `work` has resolved AND the loader has had its minimum
  // (SWITCHING_LOADER_MIN_MS, counted from this display), and `done` is where the loader comes down, since no page load
  // will end it. Nothing is navigated to, so there is no leaving stamp: Edge's next arrival holds as usual.
  const holdRef = React.useRef(null);
  if (holdRef.current === null) {
    holdRef.current = (spec, work) => {
      // One loader per entry, as above: the same slot, so a navigation asked for while this is up is dropped too.
      if (pendingRef.current !== null) return;
      const free = () => { if (pendingRef.current === done) pendingRef.current = null; };
      const done = () => {
        free();
        dismissRef.current();
      };
      pendingRef.current = done;
      showRef.current(spec, done, work);
      // A `work` that REJECTS never reaches `done`: the shell takes the loader down itself and calls nothing. Free the
      // slot here, or every later loader would be dropped as "one is already up". The failure stays the caller's to handle.
      work.catch(free);
    };
  }
  window.holdWithLoader = holdRef.current;
  React.useEffect(() => {
    const published = enterRef.current;
    const publishedHold = holdRef.current;
    // A bfcache restore (`pageshow` with `persisted`): the shell clears its loader and never calls onComplete, so
    // nothing else would free the slot, and every later loader and every navigation behind one would be dropped as "one
    // is already up" until a reload. The pending function is NOT run: a navigation asked for before the page was left
    // must not fire on return, and a held loader is already down.
    const onPageShow = (event) => { if (event.persisted) pendingRef.current = null; };
    window.addEventListener("pageshow", onPageShow);
    return () => {
      window.removeEventListener("pageshow", onPageShow);
      if (window.enterWithLoader === published) delete window.enterWithLoader;
      if (window.holdWithLoader === publishedHold) delete window.holdWithLoader;
      if (pendingRef.current !== null) pendingRef.current();
    };
  }, []);
  return loader;
}

window.SwitchLoaderHost = SwitchLoaderHost;
