/**
 * 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
      }
    >
      {/*
        First in the DOM, so the first Tab of a fresh page offers it. Without
        it a keyboard reader crosses the header, the whole sidebar and the
        Piggy dock — measured at over a hundred stops on /piggy — before
        reaching the thing they came for.
      */}
      <a
        href="#page-content"
        className="sr-only text-sm font-medium text-fg focus:not-sr-only focus:absolute focus:left-2 focus:top-[max(0.5rem,var(--safe-top))] focus:z-50 focus:flex focus:min-h-11 focus:items-center focus:rounded-lg focus:bg-surface focus:px-4 focus:shadow-lg focus:ring-2 focus:ring-brand"
      >
        Skip to content
      </a>

      <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))] [@media(max-height:500px)]:pb-[var(--safe-bottom)] lg:pb-0"
        >
          <div
            id="page-content"
            tabIndex={-1}
            className={cn(
              // The one page canvas inset in the product: 16 / 24 / 32 across,
              // 24 / 32 down. Pages set their own rhythm inside it and nothing
              // else may set a page margin.
              'mx-auto w-full min-w-0 px-4 py-6 focus-visible:outline-none 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.
 *
 * With one exception, and it was a real defect. `WorkspaceRoute` and
 * `SidebarInset` both give the bar's 72px padding reserve back under
 * `(max-height: 500px)` — a landscape phone, or a phone with the keyboard up,
 * cannot spend a fifth of its screen on a bar it can reach by turning the
 * handset back — and both files' comments said the bar itself stood down at the
 * same height. It did not: it stayed `fixed` at the bottom with nothing holding
 * content clear of it, so at 852x393 it painted over the Piggy composer's
 * safety line and the lower 23px of the 44px Send button. Confirmed by
 * hit-testing, not by a screenshot: `elementFromPoint` at the Send button's
 * centre returned a tab-bar link.
 *
 * Standing down is safe because navigation does not go with it — `SidebarTrigger`
 * is in the header on every viewport below `lg` and opens the same nav sheet.
 */
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',
        '[@media(max-height:500px)]:hidden',
      )}
      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-xs font-medium text-muted"
          >
            {({ isActive }) => (
              <>
                <span
                  className={cn(
                    'grid min-h-7 min-w-12 place-items-center rounded-full transition-colors duration-1 ease-enter',
                    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>
  );
}
