import { Slot, Slottable } from "@radix-ui/react-slot";
import { forwardRef, useState } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";

import { buttonVariants } from "./button.variants";
import { cn } from "../lib/cn";
import type { VariantProps } from "../lib/cva";
import type { ButtonLikeElementProps } from "../lib/contracts";
import { createDataAttributes, createSlot } from "../lib/contracts";

export type ButtonProps = Omit<
  ButtonLikeElementProps,
  "onDrag" | "onDragEnd" | "onDragStart"
> &
  VariantProps<typeof buttonVariants>;

function Spinner() {
  return (
    <motion.span
      {...createSlot("icon")}
      aria-hidden="true"
      animate={{ opacity: 1, rotate: 0, scale: 1 }}
      className="size-4 rounded-full border-2 border-current border-r-transparent animate-spin"
      exit={{ opacity: 0, rotate: 90, scale: 0.7 }}
      initial={{ opacity: 0, rotate: -90, scale: 0.7 }}
      transition={{
        duration: 0.18,
        ease: [0.22, 1, 0.36, 1]
      }}
    />
  );
}

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
  {
    asChild = false,
    children,
    className,
    disabled,
    loading = false,
    onMouseEnter,
    onMouseLeave,
    size,
    type,
    variant,
    ...props
  },
  ref
) {
  const prefersReducedMotion = useReducedMotion();
  const [isHovered, setIsHovered] = useState(false);
  const isDisabled = disabled || loading;
  const Component = asChild ? Slot : "button";
  const baseClassName = cn(buttonVariants({ loading, size, variant }), className);
  const label = asChild ? (
    <Slottable>{children}</Slottable>
  ) : (
    <motion.span
      {...createSlot("label")}
      animate={{
        opacity: loading ? 0.94 : 1,
        x: loading ? 1.5 : 0
      }}
      transition={{
        duration: prefersReducedMotion ? 0.01 : 0.18,
        ease: [0.22, 1, 0.36, 1]
      }}
    >
      {children}
    </motion.span>
  );

  const sheen = !asChild ? (
    <motion.span
      animate={
        prefersReducedMotion || isDisabled
          ? { opacity: 0, x: "-120%" }
          : isHovered
            ? { opacity: 0.75, x: "115%" }
            : { opacity: 0, x: "-120%" }
      }
      aria-hidden="true"
      className="pointer-events-none absolute inset-y-0 left-0 w-1/2 rounded-[inherit] bg-[linear-gradient(120deg,transparent_0%,rgba(255,255,255,0.32)_45%,transparent_100%)] mix-blend-screen"
      initial={false}
      transition={{
        duration: prefersReducedMotion ? 0.01 : 0.55,
        ease: [0.16, 1, 0.3, 1]
      }}
    />
  ) : null;

  return (
    <Component
      {...props}
      {...createSlot("root")}
      {...createDataAttributes({
        disabled: isDisabled,
        loading,
        size,
        variant
      })}
      className={baseClassName}
      disabled={asChild ? undefined : isDisabled}
      onMouseEnter={(event) => {
        setIsHovered(true);
        onMouseEnter?.(event as never);
      }}
      onMouseLeave={(event) => {
        setIsHovered(false);
        onMouseLeave?.(event as never);
      }}
      ref={ref}
      type={asChild ? undefined : type ?? "button"}
    >
      {sheen}
      <AnimatePresence initial={false}>{loading ? <Spinner /> : null}</AnimatePresence>
      {label}
    </Component>
  );
});
