/** Cursor pagination state (previous-page stack) shared by all paged tables. */
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useSearchParams } from "react-router-dom";

export interface CursorPager {
  /** Cursor to pass to the backend for the current page (null = first page). */
  cursor: string | null;
  /** 1-based page number. */
  page: number;
  canPrev: boolean;
  goNext: (nextCursor: string | null) => void;
  goPrev: () => void;
  reset: () => void;
}

export interface PagerState {
  cursor: string | null;
  /** Stack of cursors that produced the previous pages (page 1 = null). */
  stack: (string | null)[];
}

export const INITIAL_PAGER_STATE: PagerState = { cursor: null, stack: [] };

/** Advance to the page identified by `nextCursor`; unchanged when null. */
export function advancePager(state: PagerState, nextCursor: string | null): PagerState {
  if (!nextCursor) return state;
  return { cursor: nextCursor, stack: [...state.stack, state.cursor] };
}

/** Go back one page; unchanged on the first page. */
export function retreatPager(state: PagerState): PagerState {
  if (state.stack.length === 0) return state;
  const stack = [...state.stack];
  const cursor = stack.pop() ?? null;
  return { cursor, stack };
}

export function pageNumber(state: PagerState): number {
  return state.stack.length + 1;
}

/**
 * Cursor pager that resets whenever `resetKey` changes (run id, filters…).
 *
 * The current cursor lives in the `?cursor=` URL param so a refresh or a
 * shared link restores the current page; the previous-page stack stays in
 * component state (Prev works within one mounted session). Only one pager
 * is mounted per page, so a single shared param is unambiguous.
 */
export function useCursorPager(resetKey: string): CursorPager {
  const [searchParams, setSearchParams] = useSearchParams();
  const cursor = searchParams.get("cursor");
  const [stack, setStack] = useState<(string | null)[]>([]);

  const writeCursor = useCallback(
    (next: string | null) => {
      setSearchParams(
        (prev) => {
          const params = new URLSearchParams(prev);
          if (next === null) params.delete("cursor");
          else params.set("cursor", next);
          return params;
        },
        { preventScrollReset: true },
      );
    },
    [setSearchParams],
  );

  // Reset on resetKey change — but not on first mount, so a refreshed or
  // deep-linked `?cursor=` survives.
  const lastResetKey = useRef(resetKey);
  useEffect(() => {
    if (lastResetKey.current === resetKey) return;
    lastResetKey.current = resetKey;
    setStack([]);
    writeCursor(null);
  }, [resetKey, writeCursor]);

  const goNext = useCallback(
    (nextCursor: string | null) => {
      if (!nextCursor) return;
      setStack((s) => [...s, cursor]);
      writeCursor(nextCursor);
    },
    [cursor, writeCursor],
  );

  const goPrev = useCallback(() => {
    if (stack.length === 0) return;
    const prev = stack[stack.length - 1];
    setStack(stack.slice(0, -1));
    writeCursor(prev);
  }, [stack, writeCursor]);

  const reset = useCallback(() => {
    setStack([]);
    writeCursor(null);
  }, [writeCursor]);

  return useMemo(
    () => ({
      cursor,
      page: stack.length + 1,
      canPrev: stack.length > 0,
      goNext,
      goPrev,
      reset,
    }),
    [cursor, stack, goNext, goPrev, reset],
  );
}
