import { AriaAttributes, ButtonHTMLAttributes, ReactElement, ReactNode, forwardRef } from 'react';

import { CommonProps } from '../../types';
import { assertEmptyObject } from '../../utils/assertEmptyObject';
import { IconGlyph } from '../Icon/constants';

import { ButtonAppearance } from './constants';
import { TunedButton, TunedButtonIcon } from './styled';

export interface ButtonProps
  extends CommonProps,
    Pick<
      ButtonHTMLAttributes<HTMLButtonElement>,
      'onMouseDown' | 'onKeyDown' | 'onClick' | 'name' | 'value' | 'id'
    > {
  ariaLabel?: AriaAttributes['aria-label'];
  ariaDescribedBy?: AriaAttributes['aria-describedby'];
  ariaErrorMessage?: AriaAttributes['aria-errormessage'];
  ariaInvalid?: AriaAttributes['aria-invalid'];
  /** Icon that will be shown left of button */
  icon?: IconGlyph | ReactElement;
  /** Visual button appearance */
  appearance?: ButtonAppearance;
  /** Flag that disable or enable button */
  disabled?: boolean;
  /** type of button default value "button" */
  type?: 'button' | 'submit' | 'reset' | undefined;
  /** Content that should be shown in button */
  children?: ReactNode;
}

/**
 * A Button triggers an event or action, and let us know what happens next. Can be used in any part of the application.
 *
 * ## Appearance
 *
 * Button supports set of default appearances:
 *
 * | Appearance                             | Example                                                   | Purpose                                                                    |
 * | -------------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------- |
 * | {@link ButtonAppearance.Default}       | <Story id="components-button--default" noCanvas />        | Default button style                                                       |
 * | {@link ButtonAppearance.Primary}       | <Story id="components-button--primary" noCanvas />        | Calls for a primary action. Tend having only one Primary button on a page. |
 * | {@link ButtonAppearance.Subtle}        | <Story id="components-button--subtle" noCanvas />         | Tertiary action                                                            |
 * | {@link ButtonAppearance.SubtlePrimary} | <Story id="components-button--subtle-primary" noCanvas /> | Tertiary action                                                            |
 * | {@link ButtonAppearance.Danger}        | <Story id="components-button--danger" noCanvas />         | Calls for deleting or other dangerous/irreversible action                  |
 *
 * Appearance can be set with {@link ButtonProps.appearance} prop.
 *
 * ## Icon
 *
 * A button can contain an Icon, if {@link ButtonProps.icon} is provided.
 *
 * <Story id="components-button--with-icon" noCanvas />
 *
 * ## Type
 *
 * A button has type="button". Possible values "button" | "submit" | "reset"
 *
 * ```tsx
 * import { Button } from "ui-kit";
 *
 * <Button appearance={ButtonAppearance.Primary} icon={IconGlyph.Sms}>Send SMS</Button>
 * ```
 */
export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
  const {
    appearance,
    type,
    onClick,
    icon,
    children,
    id,
    value,
    name,
    disabled,
    className,
    onMouseDown,
    onKeyDown,
    ariaLabel,
    ariaDescribedBy,
    ariaErrorMessage,
    ariaInvalid,
    testId,
    ...rest
  } = props;
  assertEmptyObject(rest);

  if (!children && !ariaLabel) {
    throw new Error('Button required children or aria-label property.');
  }

  return (
    <TunedButton
      ref={ref}
      $appearance={appearance ?? ButtonAppearance.Default}
      ariaDescribedBy={ariaDescribedBy}
      ariaErrorMessage={ariaErrorMessage}
      ariaInvalid={ariaInvalid}
      ariaLabel={ariaLabel}
      className={className}
      disabled={disabled}
      id={id}
      name={name}
      onClick={onClick}
      onKeyDown={onKeyDown}
      onMouseDown={onMouseDown}
      testId={testId}
      type={type ?? 'button'}
      value={value}
    >
      {typeof icon === 'string' ? <TunedButtonIcon glyph={icon} /> : icon}
      {children}
    </TunedButton>
  );
});
