/**
 * Per-route scroll memory for the app's persistent scroll containers.
 *
 * The shell keeps its scrollers alive across navigation on purpose: the root
 * layout, the home page and the library layout each own a
 * `flex-1 overflow-y-auto` box that outlives the route rendered inside it. That
 * is what makes the bottom UI a flex sibling rather than a measured overlay —
 * but it also means the *element* never remounts, so its `scrollTop` survives a
 * route change and the next page opens part-way down.
 *
 * SvelteKit's own scroll restoration cannot help here: it saves and restores
 * `window` scroll, and in this app the window never scrolls at all.
 *
 * So each container gets its own memory, which reproduces normal browser
 * behaviour:
 *
 *  - **forward** (link/goto/form) — a fresh visit, always lands at the top;
 *  - **popstate** (hardware/gesture Back or Forward) — restores the offset the
 *    route was left at, so Back out of a detail page returns you to your place
 *    in the list rather than to the top of it;
 *  - **enter** (initial load) — left alone; there is nothing to leak yet.
 *
 * The decision is pure and lives here so it can be unit-tested without a DOM;
 * `scrollContainer.svelte.ts` is the thin action that applies it.
 *
 * TRACES: UR-054 | DR-156
 */

/** How a navigation should affect a persistent scroll container. */
export type NavKind = "enter" | "popstate" | "forward";

/** What to do with the container once the new route has rendered. */
export type ScrollAction =
  | { kind: "reset" }
  | { kind: "restore"; top: number }
  | { kind: "none" };

/**
 * Collapse SvelteKit's navigation types into the three cases that matter.
 *
 * `enter` is the initial load. `popstate` is a Back/Forward gesture. Everything
 * else — `link`, `goto`, `form` — is a forward move into a new page.
 */
export function classifyNavigation(nav: { type?: string | null }): NavKind {
  if (nav.type === "enter") return "enter";
  if (nav.type === "popstate") return "popstate";
  return "forward";
}

/**
 * Remembers the offset each route was left at, for one scroll container.
 *
 * One instance per container: the root scroller, the home scroller and the
 * library scroller hold different content for the same URL, so a shared map
 * would restore one container's offset into another.
 */
export class ScrollMemory {
  #offsets = new Map<string, number>();

  /** Record where `key` was scrolled to, before we navigate away from it. */
  save(key: string, top: number): void {
    this.#offsets.set(key, Math.max(0, top));
  }

  /**
   * Decide what the container should do on arriving at `key`.
   *
   * Note this does not consume the saved offset: a route can be returned to
   * more than once, and each Back should restore the same place.
   */
  decide(key: string, kind: NavKind): ScrollAction {
    if (kind === "enter") return { kind: "none" };
    if (kind === "popstate") return { kind: "restore", top: this.#offsets.get(key) ?? 0 };
    return { kind: "reset" };
  }

  /** Drop everything. Intended for tests and sign-out. */
  clear(): void {
    this.#offsets.clear();
  }
}

/**
 * The memory key for a URL.
 *
 * Path plus query: a library grid filtered by genre is a different list from
 * the unfiltered one, and returning to it should restore its own place.
 */
export function scrollKey(url: { pathname: string; search?: string }): string {
  return `${url.pathname}${url.search ?? ""}`;
}
