'use client';

import {useEffect, useRef} from 'react';
import {MobileNav} from '@astryxdesign/core/MobileNav';
import {SideNavItem, SideNavSection} from '@astryxdesign/core/SideNav';
import {Divider} from '@astryxdesign/core/Divider';
import {usePathname} from 'next/navigation';
import {BrandLogo} from '@/shared/components/brand/BrandLogo';
import {AccountSheetTrigger} from './AccountSheet';
import {NAV_DRAWER_ID, useSidebar} from './SidebarProvider';
import {FOOTER_NAV, PRIMARY_NAV, isNavActive} from './nav-config';

/**
 * The mobile drawer: navigation only, mirroring the sidebar.
 *
 * ── The five ways out ─────────────────────────────────────────────────────
 * Every one of them resolves to the same `setDrawerOpen(false)` in
 * SidebarProvider, so none can leave the others stale:
 *
 *   close button   the X below, enlarged to 44px
 *   backdrop       MobileNav closes on a click that lands on the <dialog>
 *   Escape         the native `cancel` event, routed through onOpenChange
 *   nav item       onClick, below — the href still does the navigating
 *   swipe left     the handlers at the bottom of this file
 *
 * Focus trapping, the top-layer backdrop and focus restoration to the
 * hamburger come from `<dialog>.showModal()` and are not reimplemented here.
 *
 * ── Why the close button is restyled ──────────────────────────────────────
 * MobileNav always renders its own ghost X after the `header` slot, so adding
 * a second one would give the drawer two close buttons. Instead the built-in
 * is enlarged in place to a 44px target around a 24px glyph. It is a flex
 * sibling ABOVE the scroll container, not inside it, so it stays put while
 * the list scrolls without needing `position: sticky` at all.
 *
 * The selector reaches through MobileNav's DOM (dialog > drawer > header row)
 * because that row carries no themeProps of its own. It is scoped to this one
 * component's className, and every value in it is a spacing token.
 */
const DRAWER = [
  // 80% of the viewport — enough of the page stays visible behind the
  // backdrop that the drawer reads as temporary. `width` below caps it at
  // 320px, so on a 430px phone it is 320px, not 344px.
  '[&>div]:w-4/5',
  // The close button: 44px, up from MobileNav's default 32px ghost square.
  '[&>div>*:first-child>button]:size-11',
  // Every glyph in the drawer to 24px. The second rule is for registry icons
  // (the close X), which render as a span whose inner <svg> is sized in em —
  // resizing only the span leaves a 20px glyph floating in a 24px box.
  '[&_.astryx-icon]:size-6',
  '[&_.astryx-icon>svg]:size-6',
  // 48px rows. Astryx's nav items are 32px, which is a mouse target on a
  // surface that only ever sees thumbs.
  '[&_.astryx-side-nav-item]:h-12',
].join(' ');

/** Past this much horizontal travel a drag is a dismissal, not a scroll. */
const SWIPE_CLOSE_PX = 60;
/** Beyond this much vertical travel it was a scroll that drifted sideways. */
const SWIPE_DRIFT_PX = 45;

export function MobileMenu() {
  const pathname = usePathname();
  const {isDrawerOpen, setDrawerOpen, closeDrawer} = useSidebar();
  const touchStart = useRef<{x: number; y: number} | null>(null);

  // Automatically close mobile menu on route change
  useEffect(() => {
    closeDrawer();
  }, [pathname, closeDrawer]);

  return (
    <MobileNav
      id={NAV_DRAWER_ID}
      label="Navigation"
      side="start"
      width={320}
      isOpen={isDrawerOpen}
      onOpenChange={setDrawerOpen}
      className={DRAWER}
      // The lockup, not a text title: the drawer is the only place on mobile
      // where the brand can appear at full size, and the header row is going
      // to be there for the close button regardless.
      header={<BrandLogo height={26} />}
      // Swipe-to-dismiss, deliberately without live drag: following the finger
      // means fighting the dialog's own transform for the 250ms of the close
      // transition, and a threshold gesture is indistinguishable from it at
      // the speed people actually flick a drawer shut.
      onTouchStart={(e) => {
        const t = e.touches[0];
        touchStart.current = t ? {x: t.clientX, y: t.clientY} : null;
      }}
      onTouchEnd={(e) => {
        const start = touchStart.current;
        const end = e.changedTouches[0];
        touchStart.current = null;
        if (!start || !end) return;
        const dx = end.clientX - start.x;
        const dy = Math.abs(end.clientY - start.y);
        if (dx < -SWIPE_CLOSE_PX && dy < SWIPE_DRIFT_PX) {
          closeDrawer();
        }
      }}
    >
      <SideNavSection title="Workspace" isHeaderHidden>
        {PRIMARY_NAV.map((item) => (
          <SideNavItem
            key={item.href}
            label={item.label}
            icon={item.icon}
            href={item.href}
            isSelected={isNavActive(pathname, item.href)}
            // Closes on select. The href still does the navigating — this only
            // dismisses the sheet so the destination is actually visible.
            onClick={closeDrawer}
          />
        ))}
      </SideNavSection>

      <Divider />

      {/* The same chip as the rail's, in the same last position. It closes the
          drawer and asks the shell for the sheet — a sheet opened from inside
          this <dialog> could not outlive it. See AccountSheet. */}
      <AccountSheetTrigger />
    </MobileNav>
  );
}
