import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

type ButtonGroupSize = "md" | "sm" | "xs"
type ButtonGroupMode = "default" | "iconOnly"

type ButtonGroupContextValue = {
  size: ButtonGroupSize
  mode: ButtonGroupMode
}

const buttonGroupContext = React.createContext<ButtonGroupContextValue>({
  size: "md",
  mode: "default",
})

const buttonGroupVariants = cva(
  "inline-flex items-center overflow-hidden rounded-[var(--bh-button-group-radius)] bg-[var(--bh-button-group-bg)] text-[var(--bh-button-group-content)] shadow-[var(--shadow-button-group)] [--shadow-button-group:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-button-group-border),var(--shadow-component-default)]",
  {
    variants: {
      size: {
        md: "h-[var(--bh-button-group-md-height)]",
        sm: "h-[var(--bh-button-group-sm-height)]",
        xs: "h-[var(--bh-button-group-xs-height)]",
      },
    },
    defaultVariants: {
      size: "md",
    },
  }
)

const buttonGroupItemVariants = cva(
  "relative inline-flex shrink-0 items-center justify-center whitespace-nowrap bg-[var(--bh-button-group-item-bg)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] text-[var(--bh-button-group-content)] outline-none transition-[background-color,box-shadow,color] before:absolute before:inset-y-0 before:start-0 before:w-[var(--bh-border-width-default)] before:bg-[var(--bh-button-group-divider)] before:content-[''] first:before:hidden hover:bg-[var(--bh-button-group-item-hover-bg)] active:bg-[var(--bh-button-group-item-pressed-bg)] focus-visible:z-[var(--bh-z-raised)] focus-visible:shadow-[var(--shadow-button-group-focus)] aria-[pressed=true]:bg-[var(--bh-button-group-item-active-bg)] aria-[pressed=true]:text-[var(--bh-button-group-content-active)] data-[active=true]:bg-[var(--bh-button-group-item-active-bg)] data-[active=true]:text-[var(--bh-button-group-content-active)] disabled:pointer-events-none disabled:bg-[var(--bh-button-group-item-disabled-bg)] disabled:text-[var(--bh-button-group-content-disabled)] [&_svg]:pointer-events-none [&_svg]:size-[var(--bh-button-group-icon-size)] [&_svg]:shrink-0 rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
  {
    variants: {
      size: {
        md: "h-[var(--bh-button-group-md-height)] gap-[var(--bh-button-group-md-gap)]",
        sm: "h-[var(--bh-button-group-sm-height)] gap-[var(--bh-button-group-sm-gap)]",
        xs: "h-[var(--bh-button-group-xs-height)] gap-[var(--bh-button-group-xs-gap)]",
      },
      mode: {
        default: "",
        iconOnly: "",
      },
    },
    compoundVariants: [
      {
        size: "md",
        mode: "default",
        class:
          "w-[var(--bh-button-group-md-item-width)] px-[var(--bh-button-group-md-padding-x)] rtl:w-[var(--bh-button-group-md-item-rtl-width)]",
      },
      {
        size: "sm",
        mode: "default",
        class:
          "w-[var(--bh-button-group-sm-item-width)] px-[var(--bh-button-group-sm-padding-x)] rtl:w-[var(--bh-button-group-sm-item-rtl-width)]",
      },
      {
        size: "xs",
        mode: "default",
        class:
          "w-[var(--bh-button-group-xs-item-width)] px-[var(--bh-button-group-xs-padding-x)] rtl:w-[var(--bh-button-group-xs-item-rtl-width)]",
      },
      {
        size: "md",
        mode: "iconOnly",
        class:
          "w-[var(--bh-button-group-md-height)] px-[var(--bh-button-group-md-icon-padding-x)]",
      },
      {
        size: "sm",
        mode: "iconOnly",
        class:
          "w-[var(--bh-button-group-sm-height)] px-[var(--bh-button-group-sm-icon-padding-x)]",
      },
      {
        size: "xs",
        mode: "iconOnly",
        class:
          "w-[var(--bh-button-group-xs-height)] px-[var(--bh-button-group-xs-icon-padding-x)]",
      },
    ],
    defaultVariants: {
      size: "md",
      mode: "default",
    },
  }
)

type ButtonGroupProps = React.ComponentProps<"div"> &
  VariantProps<typeof buttonGroupVariants> & {
    mode?: ButtonGroupMode
  }

const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(function ButtonGroup({
  className,
  size = "md",
  mode = "default",
  children,
  role = "group",
  ...props
}, ref) {
  const contextValue = React.useMemo(
    () => ({
      size: (size || "md") as ButtonGroupSize,
      mode: mode || "default",
    }),
    [size, mode]
  )

  return (
    <buttonGroupContext.Provider value={contextValue}>
      <div
        data-mode={contextValue.mode}
        data-size={contextValue.size}
        data-slot="button-group"
        ref={ref}
        role={role}
        className={cn(buttonGroupVariants({ size: contextValue.size }), className)}
        {...props}
      >
        {children}
      </div>
    </buttonGroupContext.Provider>
  )
})

type ButtonGroupItemProps = React.ComponentProps<"button"> &
  VariantProps<typeof buttonGroupItemVariants> & {
    asChild?: boolean
  }

const ButtonGroupItem = React.forwardRef<HTMLButtonElement, ButtonGroupItemProps>(function ButtonGroupItem({
  className,
  size,
  mode,
  asChild = false,
  children,
  type,
  "aria-label": ariaLabel,
  disabled,
  "aria-pressed": ariaPressed,
  onMouseDown,
  ...props
}, ref) {
  const context = React.useContext(buttonGroupContext)
  const selectedSize = (size || context.size) as ButtonGroupSize
  const selectedMode = mode || context.mode
  const Comp = asChild ? Slot : "button"
  const accessibleLabel =
    selectedMode === "iconOnly" ? ariaLabel || getTextAlternative(children) : ariaLabel
  const handleMouseDown = React.useCallback(
    (event: React.MouseEvent<HTMLButtonElement>) => {
      onMouseDown?.(event)

      if (!event.defaultPrevented) {
        event.preventDefault()
      }
    },
    [onMouseDown]
  )

  return (
    <Comp
      aria-label={accessibleLabel}
      aria-pressed={ariaPressed}
      data-mode={selectedMode}
      data-size={selectedSize}
      data-slot="button-group-item"
      ref={ref}
      className={cn(
        buttonGroupItemVariants({
          size: selectedSize,
          mode: selectedMode,
          className,
        })
      )}
      {...(!asChild ? { type: type || "button" } : {})}
      {...(!asChild ? { disabled } : {})}
      onMouseDown={handleMouseDown}
      {...props}
    >
      {asChild ? children : (
        <ButtonGroupItemChildren mode={selectedMode}>{children}</ButtonGroupItemChildren>
      )}
    </Comp>
  )
})

function ButtonGroupItemChildren({
  children,
  mode,
}: {
  children: React.ReactNode
  mode: ButtonGroupMode
}) {
  if (mode === "iconOnly") {
    return children
  }

  return React.Children.map(children, (child) => {
    if (typeof child === "string" || typeof child === "number") {
      return (
        <span
          data-slot="button-group-item-label"
          dir="auto"
          className="min-w-0 px-[var(--bh-button-group-label-padding-x)]"
        >
          {child}
        </span>
      )
    }

    return child
  })
}

function getTextAlternative(children: React.ReactNode) {
  let text = ""

  React.Children.forEach(children, (child) => {
    if (typeof child === "string" || typeof child === "number") {
      text += child
      return
    }

    if (React.isValidElement<{ children?: React.ReactNode }>(child)) {
      text += getTextAlternative(child.props.children) || ""
    }
  })

  return text.trim() || undefined
}

export { ButtonGroup, ButtonGroupItem, buttonGroupVariants, buttonGroupItemVariants }
export type {
  ButtonGroupItemProps,
  ButtonGroupMode,
  ButtonGroupProps,
  ButtonGroupSize,
}
