import { type ReactNode, cloneElement, isValidElement } from "react";

import cn from "../../utils/classnames.ts";
import { Loader } from "../index.ts";

type ButtonColor = "primary" | "secondary" | "mono" | "danger";
type ButtonVariant = "solid" | "outline" | "ghost";
type ButtonSize = "xs" | "sm" | "md" | "lg";

interface BaseButtonProps {
  children?: ReactNode;
  className?: string;
  color?: ButtonColor;
  variant?: ButtonVariant;
  size?: ButtonSize;
  iconLeft?: ReactNode;
  iconRight?: ReactNode;
  disabled?: boolean;
  loading?: boolean;
  notDisabledWhileLoading?: boolean;
  shiny?: boolean;
}

interface ButtonAsButton extends BaseButtonProps {
  onClick?: () => void;
  type?: "button" | "submit" | "reset";
}

interface ButtonAsLink extends BaseButtonProps {
  href: string;
  onClick?: never;
  to?: never;
  target?: string;
  rel?: string;
}

export type ButtonProps = ButtonAsButton | ButtonAsLink;

const sizeClasses: Record<ButtonSize, string> = {
  xs: "px-2 py-1.5 text-xs",
  sm: "px-3 py-1.5 text-sm",
  md: "px-4 py-2 text-base",
  lg: "px-6 py-3 text-lg",
};

const iconOnlySizeClasses: Record<ButtonSize, string> = {
  xs: "p-1.5",
  sm: "p-1.5",
  md: "p-2",
  lg: "p-3",
};

const iconSizeClasses: Record<ButtonSize, string> = {
  xs: "h-3 w-3",
  sm: "h-4 w-4",
  md: "h-5 w-5",
  lg: "h-6 w-6",
};

const colorVariantClasses: Record<
  ButtonColor,
  Record<ButtonVariant, string>
> = {
  primary: {
    solid:
      "bg-yellow-500 text-gray-900 hover:bg-yellow-600 dark:bg-yellow-400 dark:text-gray-900 dark:hover:bg-yellow-500",
    outline:
      "border-1 border-yellow-500 text-yellow-600 hover:bg-yellow-50 dark:border-yellow-400 dark:text-yellow-400 dark:hover:bg-yellow-950",
    ghost:
      "text-yellow-600 hover:bg-yellow-50 dark:text-yellow-400 dark:hover:bg-yellow-950",
  },
  secondary: {
    solid:
      "bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600",
    outline:
      "border-1 border-blue-600 text-blue-600 hover:bg-blue-50 dark:border-blue-400 dark:text-blue-400 dark:hover:bg-blue-950",
    ghost:
      "text-blue-600 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-950",
  },
  mono: {
    solid:
      "bg-gray-900 text-white hover:bg-gray-800 dark:bg-white dark:text-gray-900 dark:hover:bg-gray-100",
    outline:
      "border-1 border-gray-900 text-gray-900 hover:bg-gray-50 dark:border-white dark:text-white dark:hover:bg-white/10",
    ghost:
      "text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-white/10",
  },
  danger: {
    solid:
      "bg-red-600 text-white hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600",
    outline:
      "border-1 border-red-600 text-red-600 hover:bg-red-50 dark:border-red-400 dark:text-red-400 dark:hover:bg-red-950",
    ghost:
      "text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950",
  },
};

export default function Button({
  children,
  className = "",
  color = "primary",
  variant = "solid",
  size = "md",
  iconLeft,
  iconRight,
  disabled = false,
  loading = false,
  notDisabledWhileLoading = false,
  shiny = false,
  ...props
}: ButtonProps) {
  const isIconOnly = !children && (iconLeft || iconRight || loading);

  const baseClasses = cn(
    "inline-flex cursor-pointer items-center justify-center gap-2 rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900",
    isIconOnly ? iconOnlySizeClasses[size] : sizeClasses[size],
    colorVariantClasses[color][variant],
    {
      "cursor-not-allowed opacity-50": disabled || loading,
    },
    className
  );

  const isDisabled = disabled || (loading && !notDisabledWhileLoading);

  const renderIcon = (icon: ReactNode) => {
    if (isValidElement(icon)) {
      return cloneElement(icon as any, {
        className: cn((icon.props as any)?.className, iconSizeClasses[size]),
      });
    }
    return icon;
  };

  const content = (
    <>
      {loading && <Loader size={size} />}
      {!loading && iconLeft && renderIcon(iconLeft)}
      {children && <span>{children}</span>}
      {!loading && iconRight && renderIcon(iconRight)}
    </>
  );

  if ("href" in props && props.href) {
    return (
      <a
        href={props.href}
        className={baseClasses}
        target={props.target}
        rel={props.rel}
        aria-disabled={isDisabled}
      >
        {content}
      </a>
    );
  }

  return (
    <button
      type={(props as ButtonAsButton).type || "button"}
      onClick={(props as ButtonAsButton).onClick}
      disabled={isDisabled}
      className={baseClasses}
    >
      {content}
    </button>
  );
}
