'use client';

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
} from 'react';
import {useBreakpoint, isSideNavInline} from '@/shared/hooks/useBreakpoint';
import {usePersistentTriState} from '@/shared/hooks/usePersistentFlag';

/**
 * One source of truth for "where is the navigation right now".
 *
 * Before this, three components each owned a piece: SideNav held its own
 * collapse flag, the layout held the drawer flag, and AppShell injected a
 * hamburger only in its mobile top bar. Nothing could see the whole picture,
 * which is how the sidebar became unreachable — collapsed on desktop with no
 * control left to expand it, and closed on mobile with the toggle living in a
 * slot that only exists below 640px.
 *
 * Hoisting both flags here means a single button (NavMenuButton) can render in
 * the header at every width and always know what it should do, and the drawer
 * can never be open in a layout that has no way to close it.
 *
 * ── The three states ──────────────────────────────────────────────────────
 *   drawer     <640    overlay, closed by default, opened by the hamburger
 *   rail       640+    inline; collapsed (84px) or expanded (260px)
 *
 * `isDrawer` deliberately mirrors AppShell's own `mobileNav.breakpoint` of
 * 'sm' via isSideNavInline — the two must agree or you get two hamburgers or
 * none. See lib/breakpoints.
 */

/** The drawer's DOM id, so the header button's `aria-controls` resolves. */
export const NAV_DRAWER_ID = 'workspace-nav-drawer';

/** The inline rail's DOM id, for the same reason above the breakpoint. */
export const NAV_RAIL_ID = 'workspace-nav-rail';

const COLLAPSE_KEY = 'loyaly.sidenav.collapsed';

interface SidebarValue {
  /** True below 640px, where the nav is an overlay rather than a column. */
  isDrawer: boolean;
  /** Rail state. Meaningless while `isDrawer` — the drawer is never a rail. */
  isCollapsed: boolean;
  isDrawerOpen: boolean;
  setDrawerOpen: (open: boolean) => void;
  closeDrawer: () => void;
  /** Open/close below the breakpoint, collapse/expand above it. */
  toggle: () => void;
  /** Whichever element the header button currently controls. */
  controlsId: string;
}

const SidebarContext = createContext<SidebarValue | null>(null);

export function SidebarProvider({children}: {children: React.ReactNode}) {
  const bp = useBreakpoint();
  const isDrawer = !isSideNavInline(bp);

  // Tri-state, not a boolean: a tablet has to start collapsed WITHOUT that
  // being a stored preference, or the first desktop visit inherits it.
  const [storedCollapsed, setStoredCollapsed] =
    usePersistentTriState(COLLAPSE_KEY);
  const isCollapsed = storedCollapsed ?? bp === 'tablet';

  const [isMenuOpen, setMenuOpen] = useState(false);

  // DERIVED, not reset in an effect: a resize past the breakpoint would
  // otherwise leave the drawer mounted over a layout whose hamburger has
  // turned into a collapse button — a nav trap with no visible way out.
  // Gating on the current breakpoint costs no extra render pass.
  const isDrawerOpen = isDrawer && isMenuOpen;

  const closeDrawer = useCallback(() => setMenuOpen(false), []);

  const toggle = useCallback(() => {
    if (isDrawer) {
      setMenuOpen((open) => !open);
    } else {
      setStoredCollapsed(!isCollapsed);
    }
  }, [isDrawer, isCollapsed, setStoredCollapsed]);

  // Body scroll lock. MobileNav's native <dialog> already clips the scroll on
  // <html>, but iOS Safari still rubber-bands <body> underneath the top layer,
  // which slides the page behind a drawer that stays put.
  useEffect(() => {
    if (!isDrawerOpen) return;
    const previous = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => {
      document.body.style.overflow = previous;
    };
  }, [isDrawerOpen]);

  const value = useMemo(
    () => ({
      isDrawer,
      isCollapsed,
      isDrawerOpen,
      setDrawerOpen: setMenuOpen,
      closeDrawer,
      toggle,
      controlsId: isDrawer ? NAV_DRAWER_ID : NAV_RAIL_ID,
    }),
    [isDrawer, isCollapsed, isDrawerOpen, closeDrawer, toggle],
  );

  return <SidebarContext value={value}>{children}</SidebarContext>;
}

export function useSidebar(): SidebarValue {
  const ctx = useContext(SidebarContext);
  if (!ctx) {
    throw new Error('useSidebar must be used inside <SidebarProvider>');
  }
  return ctx;
}
