"use client"
import { DialogDescription, DialogTitle } from "@radix-ui/react-dialog"
import React, {
  ComponentProps,
  ComponentPropsWithoutRef,
  createContext,
  ElementRef,
  forwardRef,
  useContext,
  useMemo,
} from "react"
import { tv, type VariantProps } from "tailwind-variants"
import { Drawer as SheetPrimitive } from "vaul"
import { ChevronLeft, ChevronRight, Close } from "../../icons"
import { overlayVariants } from "../../styles/utils/overlay"
import { classNames } from "../../utils"
import { Block, BlockProps } from "../Block"
import { FlexCenter, FlexCenterProps } from "../FlexCenter"
import { Media } from "../Media"
import { Text } from "../Text"

const sheetVariants = tv({
  base: classNames(
    "fixed inset-y-0 z-sheet flex max-h-full w-full flex-col focus-visible:outline-hidden lg:max-w-[460px]",
    "bg-bg-app shadow-sm",
  ),
  variants: {
    direction: {
      left: "left-0",
      right: "right-0",
    },
  },
})

export type SideSheetProps = Pick<
  ComponentProps<typeof SheetPrimitive.Root>,
  | "open"
  | "onOpenChange"
  | "modal"
  | "shouldScaleBackground"
  | "disablePreventScroll"
  | "repositionInputs"
  | "handleOnly"
> &
  Omit<ComponentProps<typeof SheetPrimitive.Content>, "content"> &
  VariantProps<typeof sheetVariants> & {
    content: React.ReactNode
    withCloseIcon?: boolean
    overrides?: {
      Overlay: ComponentProps<typeof SheetPrimitive.Overlay>
    }
  }

const SideSheetContext = createContext<{
  direction: "left" | "right"
  withCloseIcon: boolean
}>({
  direction: "left",
  withCloseIcon: true,
})

const SideSheetBase = forwardRef<
  ElementRef<typeof SheetPrimitive.Root>,
  SideSheetProps
>(function SideSheet(
  {
    open,
    onOpenChange,
    content,
    children,
    className,
    overrides,
    modal,
    direction = "left",
    withCloseIcon = true,
    repositionInputs = false,
    handleOnly = true,
    ...props
  },
  ref,
) {
  const value = useMemo(
    () => ({ direction, withCloseIcon }),
    [direction, withCloseIcon],
  )

  return (
    <SideSheetContext.Provider value={value}>
      <SheetPrimitive.Root
        direction={direction}
        // Disable prevent scroll and noBodyStyles is needed to prevent layout shift.
        // Seems that scrolling is prevented anyways even if this is disabled.
        // https://github.com/emilkowalski/vaul/issues/285
        disablePreventScroll
        handleOnly={handleOnly}
        modal={modal}
        noBodyStyles
        open={open}
        repositionInputs={repositionInputs}
        onOpenChange={onOpenChange}
      >
        <SheetPrimitive.Trigger asChild>{children}</SheetPrimitive.Trigger>
        <SheetPrimitive.Portal>
          <SheetPrimitive.Overlay
            data-testid="Overlay"
            {...overrides?.Overlay}
            className={classNames(
              overlayVariants(),
              overrides?.Overlay.className,
            )}
          />

          <SheetPrimitive.Content
            className={classNames(sheetVariants({ direction }), className)}
            ref={ref}
            {...props}
          >
            {content}
          </SheetPrimitive.Content>
        </SheetPrimitive.Portal>
      </SheetPrimitive.Root>
    </SideSheetContext.Provider>
  )
})

export const SideSheetHeader = ({
  className,
  children,
  ...props
}: FlexCenterProps) => {
  const { direction, withCloseIcon } = useContext(SideSheetContext)
  const MobileCloseIcon = direction === "left" ? ChevronLeft : ChevronRight

  return (
    <FlexCenter
      className={classNames(
        "relative justify-center p-4 lg:justify-normal lg:p-6",
        className,
      )}
      {...props}
    >
      {children}
      {withCloseIcon && (
        <>
          <Media lessThan="lg">
            <SheetPrimitive.Close
              className={classNames(
                "absolute inset-y-0 z-sheet flex items-center focus-visible:outline-hidden",
                direction === "left" ? "right-4" : "left-4",
              )}
            >
              <MobileCloseIcon
                aria-label="Close"
                fill="interactive-primary-styles"
                size={35}
              />
            </SheetPrimitive.Close>
          </Media>
          <Media greaterThanOrEqual="lg">
            <SheetPrimitive.Close
              className={classNames(
                "absolute inset-y-0 top-[22px] z-sheet flex aspect-square size-7 items-center justify-center rounded bg-bg-app focus-visible:outline-hidden",
                direction === "left"
                  ? "right-[calc((theme(spacing.7)+theme(spacing.6))*-1)]"
                  : "left-[calc((theme(spacing.7)+theme(spacing.6))*-1)]",
              )}
            >
              <Close aria-label="Close" fill="text-primary" />
            </SheetPrimitive.Close>
          </Media>
        </>
      )}
    </FlexCenter>
  )
}

export const SideSheetHeaderTitle = forwardRef<
  ElementRef<typeof SheetPrimitive.Title>,
  ComponentPropsWithoutRef<typeof DialogTitle>
>(function SideSheetTitle({ className, children, ...props }, ref) {
  return (
    <SheetPrimitive.Title asChild ref={ref} {...props}>
      <Text.Heading className={className} size="sm">
        {children}
      </Text.Heading>
    </SheetPrimitive.Title>
  )
})

export const SideSheetBody = forwardRef<ElementRef<typeof Block>, BlockProps>(
  function SideSheetBody({ children, className, ...props }, ref) {
    return (
      <Block
        className={classNames("overflow-y-auto px-2 lg:px-6", className)}
        ref={ref}
        {...props}
      >
        {children}
      </Block>
    )
  },
)

export const SideSheetDescription = forwardRef<
  ElementRef<typeof SheetPrimitive.Description>,
  ComponentPropsWithoutRef<typeof DialogDescription>
>(function BottomSheetDescription({ className, content, ...props }, ref) {
  return (
    <SheetPrimitive.Description className={className} ref={ref} {...props}>
      {content}
    </SheetPrimitive.Description>
  )
})

/**
 * Displays content that complements the main content of the screen.
 * The bottom variant of this is a common pattern for mobile devices.
 */
export const SideSheet = Object.assign(SideSheetBase, {
  Header: Object.assign(SideSheetHeader, { Title: SideSheetHeaderTitle }),
  Body: SideSheetBody,
  Description: SideSheetDescription,
})
