import clsx from "clsx";
import {
  Separator,
  Dialog as SheetPrimitive,
  Slot as SlotPrimitive,
  Tooltip,
} from "radix-ui";
import * as React from "react";
import { TooltipSymbol } from "../Tooltip";

import { useIsMobile } from "../../hooks/useIsMobile";
import { MaybeButton } from "../Button";
import {
  ButtonContext,
  InternalLink,
  PrefetchBehavior,
  RouteRecord,
} from "../Link";
import { Skeleton } from "../Skeleton";
import * as styles from "./styles.module.css";

const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b";

type SidebarContext = {
  state: "expanded" | "collapsed";
  open: boolean;
  setOpen: (open: boolean) => void;
  openMobile: boolean;
  setOpenMobile: (open: boolean) => void;
  isMobile: boolean;
  toggleSidebar: () => void;
};

const SidebarContext = React.createContext<SidebarContext>({
  state: "collapsed",
  open: true,
  setOpen: () => {},
  openMobile: false,
  setOpenMobile: () => {},
  isMobile: false,
  toggleSidebar: () => {},
});

function useSidebar() {
  const context = React.useContext(SidebarContext);
  return context;
}

/**
 * SidebarProvider: Manages sidebar state and context.
 */
export const Sidebar = ({
  defaultOpen = true,
  open: openProp,
  onOpenChange: setOpenProp,
  className,
  style,
  children,
  ...props
}: React.ComponentPropsWithoutRef<"div"> & {
  defaultOpen?: boolean;
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
}) => {
  const isMobile = useIsMobile();
  const [openMobile, setOpenMobile] = React.useState(false);

  const [_open, _setOpen] = React.useState(defaultOpen);
  const open = openProp ?? _open;
  const setOpen = React.useCallback(
    (value: boolean | ((value: boolean) => boolean)) => {
      const openState = typeof value === "function" ? value(open) : value;
      if (setOpenProp) {
        setOpenProp(openState);
      } else {
        _setOpen(openState);
      }
      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
    },
    [setOpenProp, open],
  );

  const toggleSidebar = React.useCallback(() => {
    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
  }, [isMobile, setOpen, setOpenMobile]);

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

  const state = open ? "expanded" : "collapsed";

  const contextValue = React.useMemo<SidebarContext>(
    () => ({
      state,
      open,
      setOpen,
      isMobile,
      openMobile,
      setOpenMobile,
      toggleSidebar,
    }),
    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
  );

  return (
    <SidebarContext.Provider value={contextValue}>
      <Tooltip.Provider delayDuration={0}>
        <div
          style={
            {
              "--sidebar-width": SIDEBAR_WIDTH,
              "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
              ...style,
            } as React.CSSProperties
          }
          className={className}
          {...props}
        >
          {children}
        </div>
      </Tooltip.Provider>
    </SidebarContext.Provider>
  );
};

/**
 * Sidebar: The main sidebar container.
 */
const SidebarImpl = ({
  side = "left",
  variant = "sidebar",
  collapsible = "offcanvas",
  className,
  children,
  ...props
}: React.ComponentPropsWithoutRef<"div"> & {
  side?: "left" | "right";
  variant?: "sidebar" | "floating" | "inset";
  collapsible?: "offcanvas" | "icon" | "none";
}) => {
  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();

  if (collapsible === "none") {
    return (
      <div data-collapsible="none" className={className} {...props}>
        {children}
      </div>
    );
  }

  if (isMobile) {
    return (
      <SheetPrimitive.Root
        open={openMobile}
        onOpenChange={setOpenMobile}
        {...props}
      >
        <SheetPrimitive.Portal>
          <SheetPrimitive.Overlay
            className={clsx("__sidebar-sheet-overlay", styles.sheetOverlay)}
          />
          <SheetPrimitive.Content
            data-sidebar="sidebar"
            data-mobile="true"
            data-side={side}
            className={clsx(
              "__sidebar-sheet-content",
              styles.sheetContent,
              side === "left"
                ? styles.sheetContentLeft
                : styles.sheetContentRight,
              className,
            )}
            style={
              { "--sidebar-width": SIDEBAR_WIDTH_MOBILE } as React.CSSProperties
            }
          >
            <div
              className={clsx(
                "__sidebar-sheet-content-inner",
                styles.sidebarContent,
              )}
            >
              {children}
            </div>
          </SheetPrimitive.Content>
        </SheetPrimitive.Portal>
      </SheetPrimitive.Root>
    );
  }

  return (
    <div
      className={clsx(styles.sidebar, className)}
      data-state={state}
      data-collapsible={state === "collapsed" ? collapsible : ""}
      data-variant={variant}
      data-side={side}
      {...props}
    >
      <div className={styles.sidebarGap} />
      <div data-mobile="false" className="__sidebar-content-wrapper">
        <div
          data-sidebar="sidebar"
          className={clsx("__sidebar-content", styles.sidebarContent)}
        >
          {children}
        </div>
      </div>
    </div>
  );
};

/**
 * SidebarTrigger: Button to toggle the sidebar.
 */
const SidebarTrigger = ({
  className,
  onClick,
  ref,
  ...props
}: React.ComponentProps<"button"> & {
  ref?: React.Ref<HTMLButtonElement>;
}) => {
  const { toggleSidebar } = useSidebar();
  return (
    <button
      ref={ref}
      data-sidebar="trigger"
      className={className}
      onClick={(event) => {
        onClick?.(event);
        toggleSidebar();
      }}
      {...props}
    >
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        fill="currentCOLOR"
        viewBox="0 -960 960 960"
      >
        <path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120zm120-80v-560H200v560zm80 0h360v-560H400zm-80 0H200z"></path>
      </svg>
      <span className="brevity-visually-hidden">Toggle Sidebar</span>
    </button>
  );
};
SidebarTrigger[TooltipSymbol] = true;

/**
 * SidebarRail: Sidebar toggle rail.
 */
const SidebarRail = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"button"> & {
  ref?: React.Ref<HTMLButtonElement>;
}) => {
  const { toggleSidebar } = useSidebar();
  return (
    <button
      ref={ref}
      data-sidebar="rail"
      aria-label="Toggle Sidebar"
      tabIndex={-1}
      onClick={toggleSidebar}
      title="Toggle Sidebar"
      className={className}
      {...props}
    />
  );
};

/**
 * SidebarInset: Main content area beside the sidebar.
 */
const SidebarInset = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"main"> & {
  ref?: React.Ref<HTMLElement>;
}) => {
  return <main ref={ref} className={className} {...props} />;
};

/**
 * SidebarInput: Input field within the sidebar.
 */
const SidebarInput = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"input"> & {
  ref?: React.Ref<HTMLInputElement>;
}) => {
  return (
    <input
      ref={ref}
      data-sidebar="input"
      className={clsx(styles.sidebarInput, className)}
      {...props}
    />
  );
};

/**
 * SidebarHeader: Header section of the sidebar.
 */
const SidebarHeader = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"div"> & {
  ref?: React.Ref<HTMLDivElement>;
}) => {
  return (
    <div ref={ref} data-sidebar="header" className={className} {...props} />
  );
};

/**
 * SidebarFooter: Footer section of the sidebar.
 */
const SidebarFooter = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"div"> & {
  ref?: React.Ref<HTMLDivElement>;
}) => {
  return (
    <div ref={ref} data-sidebar="footer" className={className} {...props} />
  );
};

/**
 * SidebarSeparator: Separator within the sidebar.
 */
const SidebarSeparator = ({
  className,
  ref,
  ...props
}: React.ComponentProps<typeof Separator.Root> & {
  ref?: React.Ref<HTMLDivElement>;
}) => {
  return (
    <Separator.Root
      ref={ref}
      data-sidebar="separator"
      className={className}
      {...props}
    />
  );
};

/**
 * SidebarContent: Main content area of the sidebar.
 */
const SidebarContent = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"div"> & {
  ref?: React.Ref<HTMLDivElement>;
}) => {
  return (
    <div ref={ref} data-sidebar="content" className={className} {...props} />
  );
};

/**
 * SidebarGroup: Group container within the sidebar.
 */
const SidebarGroup = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"div"> & {
  ref?: React.Ref<HTMLDivElement>;
}) => {
  return (
    <div ref={ref} data-sidebar="group" className={className} {...props} />
  );
};

/**
 * SidebarGroupLabel: Label for a sidebar group.
 */
const SidebarGroupLabel = ({
  className,
  asChild = false,
  ref,
  children,
  ...props
}: React.ComponentProps<"div"> & {
  asChild?: boolean;
  ref?: React.Ref<HTMLDivElement>;
}) => {
  const Comp = asChild ? SlotPrimitive.Slot : "div";
  return (
    <Comp
      ref={ref}
      data-sidebar="group-label"
      className={className}
      children={children}
      {...props}
    />
  );
};

/**
 * SidebarGroupAction: Action button for a sidebar group.
 */
const SidebarGroupAction = ({
  className,
  asChild = false,
  ref,
  children,
  ...props
}: React.ComponentProps<"button"> & {
  asChild?: boolean;
  ref?: React.Ref<HTMLButtonElement>;
}) => {
  const Comp = asChild ? SlotPrimitive.Slot : MaybeButton;
  return (
    <Comp
      ref={ref}
      data-sidebar="group-action"
      className={className}
      children={children}
      {...props}
    />
  );
};

/**
 * SidebarGroupContent: Content area for a sidebar group.
 */
const SidebarGroupContent = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"div"> & {
  ref?: React.Ref<HTMLDivElement>;
}) => (
  <div
    ref={ref}
    data-sidebar="group-content"
    className={className}
    {...props}
  />
);

/**
 * SidebarMenu: Menu list within the sidebar.
 */
const SidebarMenu = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"ul"> & {
  ref?: React.Ref<HTMLUListElement>;
}) => <ul ref={ref} data-sidebar="menu" className={className} {...props} />;

/**
 * SidebarMenuItem: Menu item within the sidebar.
 */
const SidebarMenuItem = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"li"> & {
  ref?: React.Ref<HTMLLIElement>;
}) => (
  <li ref={ref} data-sidebar="menu-item" className={className} {...props} />
);

/**
 * SidebarMenuButton: Button within a sidebar menu.
 */
const SidebarMenuButton = ({
  asChild = false,
  isActive = false,
  variant = "subtle",
  size = "md",
  tooltip,
  className,
  ref,
  children,
  ...props
}: React.ComponentProps<"button"> & {
  asChild?: boolean;
  isActive?: boolean;
  tooltip?: string | React.ComponentProps<typeof Tooltip.Content>;
  variant?: "solid" | "outline" | "subtle" | "elvated";
  size?: "xs" | "sm" | "md" | "lg";
  ref?: React.Ref<HTMLButtonElement>;
}) => {
  const Comp = asChild ? SlotPrimitive.Slot : MaybeButton;
  const { isMobile, state } = useSidebar();
  const button = (
    <ButtonContext value={true}>
      <Comp
        ref={ref}
        data-sidebar="menu-button"
        data-size={size}
        data-variant={variant}
        data-active={isActive ? "" : undefined}
        className={clsx(className)}
        children={children}
        {...props}
      />
    </ButtonContext>
  );
  if (!tooltip) return button;
  const tooltipProps =
    typeof tooltip === "string" ? { children: tooltip } : tooltip;
  return (
    <Tooltip.Root>
      <Tooltip.Trigger asChild>{button}</Tooltip.Trigger>
      <Tooltip.Content
        className={clsx("__sidebar_tooltip-content", styles.tooltipContent)}
        side="right"
        align="center"
        hidden={state !== "collapsed" || isMobile}
        {...tooltipProps}
      />
    </Tooltip.Root>
  );
};
SidebarMenuButton[TooltipSymbol] = true;

/**
 * SidebarMenuLink: Link within a sidebar menu.
 */
const SidebarMenuLink = ({
  variant = "subtle",
  size = "md",
  tooltip,
  className,
  ref,
  children,
  onClick,
  ...props
}: React.ComponentProps<typeof InternalLink> & {
  "data-id"?: string;
  variant?: "solid" | "outline" | "subtle" | "elvated";
  size?: "xs" | "sm" | "md" | "lg";
  tooltip?: string | React.ComponentProps<typeof Tooltip.Content>;
  ref?: React.Ref<HTMLAnchorElement>;
  prefetch?: PrefetchBehavior;
  newTab?: boolean;
  to: RouteRecord;
  preserveSearchParams?: boolean;
  query: { key: string; value: string }[];
}) => {
  const { isMobile, state, setOpenMobile } = useSidebar();
  const link = (
    <InternalLink
      ref={ref}
      data-sidebar="menu-button"
      data-size={size}
      data-variant={variant}
      className={clsx(className)}
      children={children}
      onClick={(evt) => {
        onClick?.(evt);
        setOpenMobile(false);
      }}
      {...props}
    />
  );
  if (!tooltip) return link;
  const tooltipProps =
    typeof tooltip === "string" ? { children: tooltip } : tooltip;
  return (
    <Tooltip.Root>
      <Tooltip.Trigger>{link}</Tooltip.Trigger>
      <Tooltip.Content
        className={clsx("__sidebar_tooltip-content", styles.tooltipContent)}
        side="right"
        align="center"
        hidden={state !== "collapsed" || isMobile}
        {...tooltipProps}
      />
    </Tooltip.Root>
  );
};
SidebarMenuLink[TooltipSymbol] = true;

/**
 * SidebarMenuAction: Action button within a sidebar menu.
 */
const SidebarMenuAction = ({
  className,
  asChild = false,
  showOnHover = false,
  ref,
  children,
  ...props
}: React.ComponentProps<"button"> & {
  asChild?: boolean;
  showOnHover?: boolean;
  ref?: React.Ref<HTMLButtonElement>;
}) => {
  const Comp = asChild ? SlotPrimitive.Slot : MaybeButton;
  return (
    <ButtonContext value={true}>
      <Comp
        ref={ref}
        data-sidebar="menu-action"
        data-show-on-hover={showOnHover ? "" : undefined}
        className={className}
        children={children}
        {...props}
      />
    </ButtonContext>
  );
};

/**
 * SidebarMenuBadge: Badge within a sidebar menu.
 */
const SidebarMenuBadge = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"div"> & {
  ref?: React.Ref<HTMLDivElement>;
}) => {
  return (
    <div ref={ref} data-sidebar="menu-badge" className={className} {...props} />
  );
};

/**
 * SidebarMenuSkeleton: Skeleton loader for a sidebar menu.
 */
const SidebarMenuSkeleton = ({
  className,
  showIcon = false,
  ref,
  ...props
}: React.ComponentProps<"div"> & {
  showIcon?: boolean;
  ref?: React.Ref<HTMLDivElement>;
}) => {
  const width = React.useMemo(
    () => `${Math.floor(Math.random() * 40) + 50}%`,
    [],
  );
  return (
    <div
      ref={ref}
      data-sidebar="menu-skeleton"
      className={className}
      {...props}
    >
      {showIcon && (
        <Skeleton
          className={styles.menuSkeletonIcon}
          data-component="Skeleton$Brevity"
        />
      )}
      <Skeleton
        data-component="Skeleton$Brevity"
        className={styles.menuSkeletonText}
        style={{ "--skeleton-width": width } as React.CSSProperties}
      />
    </div>
  );
};

/**
 * SidebarMenuSub: Submenu list within the sidebar.
 */
const SidebarMenuSub = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"ul"> & {
  ref?: React.Ref<HTMLUListElement>;
}) => {
  return (
    <ul ref={ref} data-sidebar="menu-sub" className={className} {...props} />
  );
};

/**
 * SidebarMenuSubItem: Submenu item within the sidebar.
 */
const SidebarMenuSubItem = ({
  className,
  ref,
  ...props
}: React.ComponentProps<"li"> & {
  ref?: React.Ref<HTMLLIElement>;
}) => (
  <li ref={ref} data-sidebar="menu-sub-item" className={className} {...props} />
);

/**
 * SidebarMenuSubButton: Button within a submenu.
 */
const SidebarMenuSubButton = ({
  asChild = false,
  size = "md",
  isActive,
  className,
  ref,
  children,
  ...props
}: React.ComponentProps<"button"> & {
  asChild?: boolean;
  size?: "sm" | "md";
  isActive?: boolean;
  ref?: React.Ref<HTMLAnchorElement>;
}) => {
  const Comp = asChild ? SlotPrimitive.Slot : MaybeButton;
  return (
    <ButtonContext value={true}>
      <Comp
        ref={ref}
        data-sidebar="menu-sub-button"
        data-size={size}
        data-active={isActive}
        className={className}
        children={children}
        {...props}
      />
    </ButtonContext>
  );
};
SidebarMenuSubButton[TooltipSymbol] = true;

// Root
Sidebar.Sidebar = SidebarImpl;
Sidebar.Inset = SidebarInset;
Sidebar.Trigger = SidebarTrigger;

// Sidebar
SidebarImpl.Rail = SidebarRail;
SidebarImpl.Input = SidebarInput;
SidebarImpl.Header = SidebarHeader;
SidebarImpl.Footer = SidebarFooter;
SidebarImpl.Separator = SidebarSeparator;
SidebarImpl.Content = SidebarContent;
SidebarImpl.Group = SidebarGroup;
SidebarImpl.Menu = SidebarMenu;

// Sidebar.Group
SidebarGroup.GroupLabel = SidebarGroupLabel;
SidebarGroup.GroupAction = SidebarGroupAction;
SidebarGroup.GroupContent = SidebarGroupContent;

// Menu
SidebarMenu.MenuItem = SidebarMenuItem;
SidebarMenu.MenuSkeleton = SidebarMenuSkeleton;

// MenuItem
SidebarMenuItem.MenuButton = SidebarMenuButton;
SidebarMenuItem.MenuLink = SidebarMenuLink;
SidebarMenuItem.MenuAction = SidebarMenuAction;
SidebarMenuItem.MenuBadge = SidebarMenuBadge;
SidebarMenuItem.MenuSub = SidebarMenuSub;

// MenuSub
SidebarMenuSub.MenuSubItem = SidebarMenuSubItem;
SidebarMenuSub.MenuSubButton = SidebarMenuSubButton;
