/**
 * The application shell.
 *
 * Three panes on a desktop, and on a phone the same thing the phone always
 * had:
 *
 *   Header  — full width above everything, carrying the logo, where you are,
 *             the search field and Piggy. Full width rather than inset between
 *             the panes so both panes have one fixed edge to hang beneath, and
 *             so the sticky offset is a single CSS variable rather than a
 *             number repeated in three components.
 *   Left    — navigation, collapsing to a 60px icon rail. Sticky in the flex
 *             row rather than `fixed` with a matching padding on the content:
 *             a padding that has to be kept in step with a width is exactly
 *             the pair that drifts, and the flex row makes the compiler's job
 *             the browser's job.
 *   Right   — Piggy, docked from `xl` up. Below that it is the sheet or the
 *             drawer it has always been.
 *   Phone   — the bottom tab bar, unchanged, because the top of a large phone
 *             is out of thumb reach. The full navigation is additionally
 *             reachable through the sidebar's Sheet, from the header trigger.
 *
 * `lg` is still the breakpoint at which the tab bar gives way to the sidebar —
 * an iPad in portrait has the width, and a bottom bar looks lost across a
 * tablet. It is now declared once, in hooks/use-media-query.
 */
import { Outlet } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import { useIdentity } from '@/lib/identity';
import { useLayout } from '@/lib/layout';
import { visibleNav, type NavItem } from '@/lib/nav';
import { AppHeader } from './AppHeader';
import { AppSidebar } from './AppSidebar';
import { PiggyDock } from './PiggyDock';
import { SidebarInset, SidebarProvider } from './ui/sidebar';
import { cn } from './ui';

/** How much room Piggy takes when docked. Read by the dock and by nothing else. */
const DOCK_WIDTH = '22rem';

export function Shell() {
  const identity = useIdentity();
  const { sidebarOpen, setSidebarOpen, dockOpen } = useLayout();
  const items = visibleNav(identity);

  return (
    <SidebarProvider
      open={sidebarOpen}
      onOpenChange={setSidebarOpen}
      className="app-canvas flex-col bg-bg"
      style={
        {
          // Both side panes stick beneath the header and subtract it from the
          // viewport. One variable, so collapsing or resizing anything is a
          // CSS relayout and never a measurement in JavaScript.
          '--sidebar-offset-top': 'var(--app-header-h)',
          '--dock-width': DOCK_WIDTH,
        } as React.CSSProperties
      }
    >
      <AppHeader />

      <div className="flex w-full min-w-0 flex-1">
        <AppSidebar />

        <SidebarInset
          // Clears the tab bar and the home indicator beneath it. Without this
          // the last row of any list is unreachable on a phone. Four pages set
          // their own `md:pb-0` on top of this; keeping `lg` here means they
          // still have their padding between md and lg, where the tab bar is
          // very much still on screen.
          className="pb-[calc(4.5rem+var(--safe-bottom))] lg:pb-0"
        >
          <div
            className={cn(
              'mx-auto w-full min-w-0 px-4 py-5 sm:px-6 lg:px-8 lg:py-8',
              // With Piggy docked the middle pane is already a column in a
              // three-column layout; capping it at 7xl and centring it again
              // strands the content between two gutters it does not need.
              dockOpen ? 'max-w-[86rem]' : 'max-w-7xl',
            )}
          >
            <Outlet />
          </div>
        </SidebarInset>

        <PiggyDock />
      </div>

      <MobileTabBar items={items.filter((item) => item.primary)} />
    </SidebarProvider>
  );
}

/**
 * The phone tab bar. Unchanged in look and behaviour — it is the thing this
 * product is best at and the rebuild had no business touching it.
 */
function MobileTabBar({ items }: { items: NavItem[] }) {
  return (
    <nav
      className={cn(
        'fixed inset-x-0 bottom-0 z-30 border-t border-border bg-surface/95 shadow-[0_-8px_24px_hsl(var(--shadow)/0.08)] backdrop-blur-xl lg:hidden',
        'supports-[backdrop-filter]:bg-surface/80',
      )}
      style={{ paddingBottom: 'var(--safe-bottom)' }}
      aria-label="Primary"
    >
      <div className="mx-auto flex max-w-lg items-stretch justify-around">
        {items.map((item) => (
          <NavLink
            key={item.to}
            to={item.to}
            end={item.to === '/'}
            className="tap flex min-w-0 flex-1 flex-col items-center justify-center gap-0.5 py-1.5 text-[11px] font-medium text-muted"
          >
            {({ isActive }) => (
              <>
                <span
                  className={cn(
                    'grid min-h-7 min-w-12 place-items-center rounded-full transition-colors',
                    isActive ? 'bg-accent-subtle text-accent-fg' : 'text-muted',
                  )}
                >
                  <item.icon className="size-5" aria-hidden />
                </span>
                <span className={cn('truncate', isActive && 'text-accent-fg')}>{item.label}</span>
              </>
            )}
          </NavLink>
        ))}
      </div>
    </nav>
  );
}
