import { NavEntry, labelForPath, labelFromDocumentTitle } from "./navLabels";

/**
 * The back-button history state machine, kept free of React and of the real
 * `sessionStorage` so it can be reasoned about (and tested) directly.
 *
 * Storage is per-tab and survives full page loads, which is what lets the back
 * button work across this site's hard navigations (server-rendered forms, the
 * language toggle) and not just client-side transitions.
 */

export const PREV_KEY = "ye:nav:prev";
export const CURRENT_KEY = "ye:nav:current";

/** The subset of the Storage interface we need; lets callers pass a fake. */
export interface EntryStore {
  getItem(key: string): string | null;
  setItem(key: string, value: string): void;
}

export function readEntry(store: EntryStore, key: string): NavEntry | null {
  try {
    const raw = store.getItem(key);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    if (typeof parsed?.href === "string" && typeof parsed?.label === "string") {
      return { href: parsed.href, label: parsed.label, explicit: parsed.explicit === true };
    }
    return null;
  } catch {
    // Private browsing / storage disabled. The back button falls back to the
    // parent route, which is still correct — just less specific.
    return null;
  }
}

export function writeEntry(store: EntryStore, key: string, entry: NavEntry): void {
  try {
    store.setItem(key, JSON.stringify(entry));
  } catch {
    /* no-op, see readEntry */
  }
}

function pathOf(href: string): string {
  return href.split("?")[0];
}

/**
 * Called on every path change (including first mount, which is what makes hard
 * navigations work). Shifts the stored "current" entry into "previous" and
 * records the new current. Returns the entry the back button should point at,
 * or null when there is no history and the caller should fall back to the
 * parent route.
 *
 * `pageLabel` is the name the page supplied for itself via NavLabel (the
 * program/workshop title). When absent the label is derived from the URL and
 * later upgraded by captureTitle.
 */
export function recordNavigation(
  store: EntryStore,
  pathname: string,
  search: string,
  pageLabel?: string | null,
): NavEntry | null {
  const href = pathname + search;
  const current = readEntry(store, CURRENT_KEY);

  // Same path with a different query (language toggle, franchiseeId) is not a
  // navigation. Refresh the stored href but never let a page become its own
  // "previous".
  if (current && pathOf(current.href) === pathname) {
    writeEntry(store, CURRENT_KEY, { ...current, href });
    return readEntry(store, PREV_KEY);
  }

  if (current) {
    writeEntry(store, PREV_KEY, current);
  }
  writeEntry(
    store,
    CURRENT_KEY,
    pageLabel
      ? { href, label: pageLabel, explicit: true }
      : { href, label: labelForPath(pathname) },
  );

  return current ?? readEntry(store, PREV_KEY);
}

/**
 * Upgrades the current entry's label once Next has applied the route's
 * metadata, for pages that did not name themselves via NavLabel. A page-supplied
 * label always wins — the document title may carry an SEO tail the button
 * should not show.
 */
export function captureTitle(store: EntryStore, pathname: string, documentTitle: string): void {
  const label = labelFromDocumentTitle(documentTitle);
  if (!label) return;

  const current = readEntry(store, CURRENT_KEY);
  if (!current || current.explicit) return;
  if (pathOf(current.href) !== pathname || current.label === label) return;

  writeEntry(store, CURRENT_KEY, { ...current, label });
}
