/**
 * The sidebar primitive.
 *
 * shadcn's `sidebar` block, with its API kept intact and two deliberate
 * changes to its internals:
 *
 *   1. The desktop pane is `sticky`, not `fixed`. Upstream renders an
 *      invisible width-holding div next to a `fixed inset-y-0` pane so the
 *      pane can slide fully off-canvas. PIG only ever wants the icon rail, and
 *      a sticky pane in a flex row gets the same collapse animation from one
 *      element instead of two — and, unlike `inset-y-0`, it can start below a
 *      full-width application header. That header is the whole point of the
 *      layout, so the fixed variant was not usable as shipped.
 *   2. Every control clears 44px, and the icon rail is 64px rather than
 *      shadcn's 48px so that a 44px button still has gutters. A 32px icon
 *      button is the one thing in the upstream block that fails PIG's own
 *      touch-target rule, and the rail is reachable on a tablet.
 *
 * Colours come from `--sidebar-*` in index.css, which alias the existing
 * surface and accent variables rather than introducing a second palette — so
 * the sidebar re-tints with the user's chosen accent and needs no dark-mode
 * pass of its own.
 *
 * `collapsible="offcanvas"`, the `floating` and `inset` variants and the
 * submenu parts are not implemented, because nothing here uses them and an
 * unexercised variant is a variant that is quietly broken.
 */
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { PanelLeft } from 'lucide-react';

import { cn } from '@/lib/utils';
import { useIsMobile } from '@/hooks/use-media-query';
import { Button } from './index';
import { Separator } from './separator';
import { Skeleton } from './skeleton';
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './sheet';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip';

const SIDEBAR_COOKIE_NAME = 'pig_sidebar_state';
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
export const SIDEBAR_WIDTH = '16rem';
export const SIDEBAR_WIDTH_MOBILE = '18rem';
/**
 * 64px, not shadcn's 48px. A menu button collapses to a 44px square — PIG's
 * touch minimum — and the group padding around it is 8px a side, so 48px
 * would clip it against the border.
 */
export const SIDEBAR_WIDTH_ICON = '4rem';
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';

interface SidebarContextValue {
  state: 'expanded' | 'collapsed';
  open: boolean;
  setOpen: (open: boolean) => void;
  openMobile: boolean;
  setOpenMobile: (open: boolean) => void;
  isMobile: boolean;
  toggleSidebar: () => void;
}

const SidebarContext = React.createContext<SidebarContextValue | null>(null);

export function useSidebar(): SidebarContextValue {
  const context = React.useContext(SidebarContext);
  if (!context) throw new Error('useSidebar must be used within a SidebarProvider.');
  return context;
}

export const SidebarProvider = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<'div'> & {
    defaultOpen?: boolean;
    open?: boolean;
    onOpenChange?: (open: boolean) => void;
  }
>(
  (
    {
      defaultOpen = true,
      open: openProp,
      onOpenChange: setOpenProp,
      className,
      style,
      children,
      ...props
    },
    ref,
  ) => {
    const isMobile = useIsMobile();
    const [openMobile, setOpenMobile] = React.useState(false);
    const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
    const open = openProp ?? internalOpen;

    const setOpen = React.useCallback(
      (value: boolean) => {
        if (setOpenProp) setOpenProp(value);
        else setInternalOpen(value);
        // A cookie as well as whatever the caller persists: it is the only
        // store the document can read before React has mounted, so a future
        // server-rendered or inlined first paint has the width already.
        document.cookie = `${SIDEBAR_COOKIE_NAME}=${value}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}; samesite=lax`;
      },
      [setOpenProp],
    );

    const toggleSidebar = React.useCallback(() => {
      if (isMobile) setOpenMobile((current) => !current);
      else setOpen(!open);
    }, [isMobile, open, setOpen]);

    React.useEffect(() => {
      const onKeyDown = (event: KeyboardEvent) => {
        if (event.key.toLowerCase() !== SIDEBAR_KEYBOARD_SHORTCUT) return;
        if (!event.metaKey && !event.ctrlKey) return;
        event.preventDefault();
        toggleSidebar();
      };
      window.addEventListener('keydown', onKeyDown);
      return () => window.removeEventListener('keydown', onKeyDown);
    }, [toggleSidebar]);

    const value = React.useMemo<SidebarContextValue>(
      () => ({
        state: open ? 'expanded' : 'collapsed',
        open,
        setOpen,
        isMobile,
        openMobile,
        setOpenMobile,
        toggleSidebar,
      }),
      [open, setOpen, isMobile, openMobile, toggleSidebar],
    );

    return (
      <SidebarContext.Provider value={value}>
        <TooltipProvider delayDuration={0}>
          <div
            ref={ref}
            style={
              {
                '--sidebar-width': SIDEBAR_WIDTH,
                '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
                ...style,
              } as React.CSSProperties
            }
            className={cn('group/sidebar-wrapper flex min-h-dvh w-full', className)}
            {...props}
          >
            {children}
          </div>
        </TooltipProvider>
      </SidebarContext.Provider>
    );
  },
);
SidebarProvider.displayName = 'SidebarProvider';

export const Sidebar = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<'div'> & {
    side?: 'left' | 'right';
    collapsible?: 'icon' | 'none';
  }
>(({ side = 'left', collapsible = 'icon', className, children, ...props }, ref) => {
  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();

  if (collapsible === 'none') {
    return (
      <div
        ref={ref}
        className={cn(
          'flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground',
          className,
        )}
        {...props}
      >
        {children}
      </div>
    );
  }

  if (isMobile) {
    return (
      <Sheet open={openMobile} onOpenChange={setOpenMobile}>
        <SheetContent
          data-sidebar="sidebar"
          data-mobile="true"
          side={side}
          // The Sheet's own close button is suppressed: the sidebar header
          // carries one that does not overlap the account switcher.
          className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden sm:max-w-[--sidebar-width]"
          style={{ '--sidebar-width': SIDEBAR_WIDTH_MOBILE } as React.CSSProperties}
        >
          <SheetHeader className="sr-only">
            <SheetTitle>Navigation</SheetTitle>
            <SheetDescription>Move between the PIG workspaces.</SheetDescription>
          </SheetHeader>
          <div className="flex h-full w-full flex-col pb-[var(--safe-bottom)] pt-[var(--safe-top)]">
            {children}
          </div>
        </SheetContent>
      </Sheet>
    );
  }

  return (
    <div
      ref={ref}
      className={cn(
        'group relative hidden shrink-0 self-start overflow-hidden bg-sidebar text-sidebar-foreground lg:flex lg:flex-col',
        side === 'left' ? 'border-r border-sidebar-border' : 'border-l border-sidebar-border',
        // The whole collapse animation is this one declaration. Width is
        // driven by data-state, so nothing measures anything in JavaScript.
        'transition-[width] duration-200 ease-linear',
        'w-[calc(var(--sidebar-width)+var(--safe-left))] pl-[var(--safe-left)]',
        'data-[state=collapsed]:w-[calc(var(--sidebar-width-icon)+var(--safe-left))]',
        className,
      )}
      style={{
        position: 'sticky',
        top: 'var(--sidebar-offset-top, 0px)',
        height: 'calc(100dvh - var(--sidebar-offset-top, 0px))',
      }}
      data-state={state}
      data-collapsible={state === 'collapsed' ? collapsible : ''}
      data-side={side}
      {...props}
    >
      {children}
    </div>
  );
});
Sidebar.displayName = 'Sidebar';

export const SidebarTrigger = React.forwardRef<
  HTMLButtonElement,
  React.ComponentProps<typeof Button>
>(({ className, onClick, ...props }, ref) => {
  const { toggleSidebar, state, isMobile } = useSidebar();
  return (
    <Button
      ref={ref}
      type="button"
      variant="ghost"
      size="icon"
      className={cn('shrink-0 text-muted', className)}
      aria-label={
        isMobile ? 'Open navigation' : state === 'expanded' ? 'Collapse sidebar' : 'Expand sidebar'
      }
      aria-expanded={isMobile ? undefined : state === 'expanded'}
      onClick={(event) => {
        onClick?.(event);
        toggleSidebar();
      }}
      {...props}
    >
      <PanelLeft className="size-5" aria-hidden />
    </Button>
  );
});
SidebarTrigger.displayName = 'SidebarTrigger';

/**
 * The hit strip along the sidebar's outer edge.
 *
 * Wide enough to hit with a mouse without being a visible control, which is
 * how every editor-style sidebar behaves and how people expect to collapse one
 * without hunting for the button.
 */
export const SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<'button'>>(
  ({ className, ...props }, ref) => {
    const { toggleSidebar, state } = useSidebar();
    return (
      <button
        ref={ref}
        type="button"
        tabIndex={-1}
        aria-hidden
        onClick={toggleSidebar}
        title={state === 'expanded' ? 'Collapse sidebar' : 'Expand sidebar'}
        className={cn(
          'absolute inset-y-0 right-0 z-20 hidden w-3 cursor-w-resize transition-colors lg:block',
          'after:absolute after:inset-y-0 after:right-0 after:w-[2px] hover:after:bg-sidebar-border',
          'group-data-[state=collapsed]:cursor-e-resize',
          className,
        )}
        {...props}
      />
    );
  },
);
SidebarRail.displayName = 'SidebarRail';

export const SidebarInset = React.forwardRef<HTMLElement, React.ComponentProps<'main'>>(
  ({ className, ...props }, ref) => (
    // min-w-0 is not optional: this is a flex child holding tables and
    // tabular-nums figures, and without it the page scrolls sideways.
    <main ref={ref} className={cn('relative flex min-w-0 flex-1 flex-col', className)} {...props} />
  ),
);
SidebarInset.displayName = 'SidebarInset';

export const SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      data-sidebar="header"
      className={cn('flex flex-col gap-2 p-2', className)}
      {...props}
    />
  ),
);
SidebarHeader.displayName = 'SidebarHeader';

export const SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      data-sidebar="footer"
      className={cn('mt-auto flex flex-col gap-2 p-2', className)}
      {...props}
    />
  ),
);
SidebarFooter.displayName = 'SidebarFooter';

export const SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      data-sidebar="content"
      className={cn(
        'flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto overflow-x-hidden',
        // A scrollbar inside a 64px rail eats a third of it, and the rail has
        // nothing that needs scrolling anyway.
        'group-data-[collapsible=icon]:overflow-hidden',
        className,
      )}
      {...props}
    />
  ),
);
SidebarContent.displayName = 'SidebarContent';

export const SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      data-sidebar="group"
      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
      {...props}
    />
  ),
);
SidebarGroup.displayName = 'SidebarGroup';

export const SidebarGroupLabel = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<'div'> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
  const Comp = asChild ? Slot : 'div';
  return (
    <Comp
      ref={ref}
      data-sidebar="group-label"
      className={cn(
        // The Label spec, not a sixth micro-label variant. This heading is the
        // most-repeated small-caps text in the product — four of them on all 16
        // routes — and it was the last one off the scale: 10px/600/0.16em in
        // `text-muted/80`, which measured 3.28:1 in light theme. `text-muted` at
        // 11px/500/0.06em is the one definition everything else already uses,
        // and it measures 4.83:1.
        'flex h-8 shrink-0 items-center rounded-md px-3 text-[11px] font-medium uppercase tracking-[0.06em] text-muted',
        'transition-[margin,opacity] duration-200 ease-linear',
        // Pulled up rather than hidden, so the icons above and below do not
        // jump as the label fades out.
        'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
        className,
      )}
      {...props}
    />
  );
});
SidebarGroupLabel.displayName = 'SidebarGroupLabel';

export const SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => (
    <div ref={ref} data-sidebar="group-content" className={cn('w-full', className)} {...props} />
  ),
);
SidebarGroupContent.displayName = 'SidebarGroupContent';

export const SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<'ul'>>(
  ({ className, ...props }, ref) => (
    <ul
      ref={ref}
      data-sidebar="menu"
      className={cn('flex w-full min-w-0 flex-col gap-0.5', className)}
      {...props}
    />
  ),
);
SidebarMenu.displayName = 'SidebarMenu';

export const SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<'li'>>(
  ({ className, ...props }, ref) => (
    <li
      ref={ref}
      data-sidebar="menu-item"
      className={cn('group/menu-item relative', className)}
      {...props}
    />
  ),
);
SidebarMenuItem.displayName = 'SidebarMenuItem';

const sidebarMenuButtonVariants = cva(
  'peer/menu-button flex w-full min-h-[44px] items-center gap-3 overflow-hidden rounded-xl px-3 text-left text-sm font-medium outline-none ' +
    'transition-[background-color,color,width,padding] duration-200 ' +
    'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground ' +
    'focus-visible:ring-2 focus-visible:ring-sidebar-ring ' +
    'disabled:pointer-events-none disabled:opacity-50 ' +
    'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[active=true]:shadow-sm ' +
    // Collapsed: a square 44px target centred in the 64px rail. The label is
    // still in the DOM for screen readers; `overflow-hidden` on the pane and
    // `truncate` here keep it from reflowing during the animation.
    'group-data-[collapsible=icon]:!size-11 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:!px-0 ' +
    // `sr-only`, not `hidden`. The label is the button's accessible name, and
    // removing it from the tree leaves an icon-only control that a screen
    // reader announces as "button" — the tooltip is a hover affordance and
    // does not name anything. sr-only takes no layout space, so the icon
    // still centres in the rail.
    'group-data-[collapsible=icon]:[&>span:last-child]:sr-only ' +
    '[&>svg]:size-4 [&>svg]:shrink-0 [&>span]:min-w-0 [&>span]:truncate',
  {
    variants: {
      variant: {
        default: 'text-muted',
        outline: 'border border-sidebar-border bg-sidebar text-muted',
      },
      size: {
        default: '',
        lg: 'min-h-[52px]',
      },
    },
    defaultVariants: { variant: 'default', size: 'default' },
  },
);

export const SidebarMenuButton = React.forwardRef<
  HTMLButtonElement,
  React.ComponentProps<'button'> &
    VariantProps<typeof sidebarMenuButtonVariants> & {
      asChild?: boolean;
      isActive?: boolean;
      /** Shown as a tooltip only while the rail is collapsed. */
      tooltip?: string;
    }
>(({ asChild = false, isActive = false, variant, size, tooltip, className, ...props }, ref) => {
  const Comp = asChild ? Slot : 'button';
  const { isMobile, state } = useSidebar();

  const button = (
    <Comp
      ref={ref}
      data-sidebar="menu-button"
      data-active={isActive}
      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
      {...props}
    />
  );

  // No tooltip when the label is already visible: a tooltip repeating the text
  // beside it is noise, and on mobile it fires on tap and eats the navigation.
  if (!tooltip || state !== 'collapsed' || isMobile) return button;

  return (
    <Tooltip>
      <TooltipTrigger asChild>{button}</TooltipTrigger>
      <TooltipContent side="right" align="center">
        {tooltip}
      </TooltipContent>
    </Tooltip>
  );
});
SidebarMenuButton.displayName = 'SidebarMenuButton';

export const SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      data-sidebar="menu-badge"
      className={cn(
        'nums pointer-events-none absolute right-3 top-1/2 h-5 min-w-5 -translate-y-1/2 select-none',
        'flex items-center justify-center rounded-full bg-surface-2 px-1.5 text-[11px] font-medium text-muted',
        'group-data-[collapsible=icon]:hidden',
        className,
      )}
      {...props}
    />
  ),
);
SidebarMenuBadge.displayName = 'SidebarMenuBadge';

export function SidebarMenuSkeleton({
  className,
  showIcon = true,
  ...props
}: React.ComponentProps<'div'> & { showIcon?: boolean }) {
  return (
    <div
      data-sidebar="menu-skeleton"
      className={cn('flex h-11 items-center gap-3 rounded-xl px-3', className)}
      {...props}
    >
      {showIcon ? <Skeleton className="size-4 shrink-0 rounded-md" /> : null}
      <Skeleton className="h-4 max-w-[--skeleton-width] flex-1 group-data-[collapsible=icon]:hidden" />
    </div>
  );
}

export const SidebarSeparator = React.forwardRef<
  React.ElementRef<typeof Separator>,
  React.ComponentProps<typeof Separator>
>(({ className, ...props }, ref) => (
  <Separator
    ref={ref}
    data-sidebar="separator"
    className={cn('mx-2 w-auto bg-sidebar-border', className)}
    {...props}
  />
));
SidebarSeparator.displayName = 'SidebarSeparator';
