import { ComponentPropsWithRef, forwardRef } from 'react';
import { cva, VariantProps } from 'class-variance-authority';

import './button.css';
import { Spinner } from '../spinner';

export const buttonTokens = {
  variant: {
    default: 'c-btn',
    elevate: 'c-btn-elevate',
  },
  intent: {
    default: 'c-btn',
    action: 'c-btn-action',
    danger: 'c-btn-danger',
  },
  size: {
    xs: 'c-btn-xs',
    sm: 'c-btn-sm',
    md: 'c-btn-md',
    lg: 'c-btn-lg',
  },
} as const;

type ButtonStylesProps = VariantProps<typeof buttonStyles>;
const buttonStyles = cva('c-btn', {
  variants: {
    intent: {
      default: '',
      action: '',
      danger: '',
    },
    variant: {
      default: '',
      elevate: '',
    },
    status: {
      loading: 'c-btn-loading',
    },
    size: {
      xs: 'c-btn-xs',
      sm: 'c-btn-sm',
      md: 'c-btn-md',
      lg: 'c-btn-lg',
    },
  },
  compoundVariants: [
    {
      intent: 'default',
      variant: 'elevate',
      class: 'c-btn-elevate',
    },
    {
      intent: 'action',
      variant: 'default',
      class: 'c-btn-action',
    },
    {
      intent: 'danger',
      variant: 'default',
      class: 'c-btn-danger',
    },
    {
      intent: 'danger',
      variant: 'elevate',
      class: 'c-btn-danger c-btn-elevate',
    },
    {
      intent: 'action',
      variant: 'elevate',
      class: 'c-btn-action c-btn-elevate',
    },
  ],
  defaultVariants: {
    intent: 'default',
    variant: 'default',
    size: 'sm',
  },
});

export type ButtonProps = ComponentPropsWithRef<'button'> &
  ButtonStylesProps & {
    prepend?: React.ReactNode;
    append?: React.ReactNode;
    as?: React.ElementType;
    // To support this button being used as a link
    to?: string;
    href?: string;
  };

const spinnerSizeMap: Record<NonNullable<ButtonProps['size']>, number> = {
  xs: 10,
  sm: 12,
  md: 14,
  lg: 16,
};

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  (
    {
      children,
      className,
      type = 'button',
      as: El = 'button',
      size,
      variant,
      intent,
      prepend,
      append,
      status,
      ...delegated
    },
    ref,
  ) => {
    let isAnchor = false;

    if ('to' in delegated || 'href' in delegated) {
      isAnchor = true;
    }

    return (
      <El
        ref={ref}
        type={isAnchor ? undefined : type}
        className={buttonStyles({ size, variant, intent, status, className })}
        {...delegated}
      >
        {!prepend ? null : <span className="c-btn__prepend">{prepend}</span>}
        {children}
        {status !== 'loading' ? null : (
          <span className="c-btn__loading-spinner">
            <Spinner size={size ? spinnerSizeMap[size] : spinnerSizeMap.md} />
          </span>
        )}
        {!append ? null : <span className="c-btn__append">{append}</span>}
      </El>
    );
  },
);

Button.displayName = 'Button';
