import { clsx } from 'clsx';
import { ElementType, forwardRef, MouseEvent } from 'react';
import { useIntl } from 'react-intl';

import {
  Size,
  ControlType,
  Priority,
  ControlTypeAccent,
  ControlTypeNegative,
  ControlTypePositive,
  PriorityPrimary,
  PrioritySecondary,
  PriorityTertiary,
  SizeExtraSmall,
  SizeSmall,
  SizeMedium,
  SizeLarge,
} from '../common';
import ProcessIndicator from '../processIndicator';

import messages from '../i18n/commonMessages/Button.messages';
import { typeClassMap, priorityClassMap } from './classMap';
import { establishNewPriority, establishNewType, logDeprecationNotices } from './legacyUtils';
import { ButtonReferenceType } from './Button.types';

/** @deprecated */
type DeprecatedTypes = 'primary' | 'pay' | 'secondary' | 'danger' | 'link';

/** @deprecated */
type DeprecatedSizes = SizeExtraSmall;

/**
 * @deprecated Use new button instead of these APIs.
 * @example
 * ```
 * import { Button, ButtonProps } from '@transferwise/components';
 *
 * <Button v2>...</Button>
 * ```
 */
export type CommonProps = {
  v2?: false;

  /** Makes the button take up the full width of its container */
  block?: boolean;

  /**
   * The `target` attribute for HTML anchor.
   * If set to `_blank`, `rel="noopener noreferrer"` is automatically added to the rendered node.
   */
  target?: string;
  /**
   * Toggles the disabled state
   * @default false
   */

  disabled?: boolean;
  /**
   * Toggles the loading state
   * @default false
   */
  loading?: boolean;
  type?: ControlTypeAccent | ControlTypeNegative | ControlTypePositive | DeprecatedTypes | null;
  priority?: PriorityPrimary | PrioritySecondary | PriorityTertiary | null;
  size?: SizeSmall | SizeMedium | SizeLarge | DeprecatedSizes;
  htmlType?: 'submit' | 'reset' | 'button';
};

/**
 * @deprecated Use new button instead of APIs.
 * @example
 * ```
 * import { Button, ButtonProps } from '@transferwise/components';
 *
 * <Button v2>...</Button>
 * ```
 */
export type ButtonProps = CommonProps &
  Omit<React.ComponentPropsWithRef<'button'>, 'type'> & {
    as?: 'button';
  };

type AnchorProps = CommonProps &
  Omit<React.ComponentPropsWithRef<'a'>, 'type'> & {
    as?: 'a';
  };

/**
 * @deprecated Use new button instead of APIs.
 * @example
 * ```
 * import { Button, ButtonProps } from '@transferwise/components';
 *
 * <Button v2>...</Button>
 * ```
 */
export type LegacyButtonProps = ButtonProps | AnchorProps;

/**
 * @deprecated Use new Button component via `<Button v2 .. />` and new props.
 */
const LegacyButton = forwardRef<ButtonReferenceType, LegacyButtonProps>(
  (
    {
      as: component = 'button',
      block = false,
      children,
      className,
      disabled,
      loading = false,
      priority = Priority.PRIMARY,
      size = Size.MEDIUM,
      type = ControlType.ACCENT,
      onClick,
      ...rest
    }: LegacyButtonProps,
    ref,
  ) => {
    const intl = useIntl();

    logDeprecationNotices({ size, type });

    const newType = establishNewType(type);
    const newPriority = establishNewPriority(priority, type);

    const classes = clsx(
      `btn btn-${size}`,
      `np-btn np-btn-${size}`,
      {
        'btn-loading': loading,
        'btn-block np-btn-block': block,
        disabled,
      },
      // @ts-expect-error fix when refactor `typeClassMap` to TypeScript
      typeClassMap[newType],
      // @ts-expect-error fix when refactor `typeClassMap` to TypeScript
      priorityClassMap[newPriority],
      className,
    );

    function processIndicatorSize() {
      return ['sm', 'xs'].includes(size) ? 'xxs' : 'xs';
    }

    const Element = (component as ElementType) ?? 'button';
    let props;

    if (Element === 'button') {
      const { htmlType = 'button', ...restProps } = rest as ButtonProps;
      props = {
        ...restProps,
        disabled,
        'aria-disabled': loading,
        type: htmlType,
      };
    } else {
      props = {
        ...rest,
        'aria-disabled': loading,
      } as AnchorProps;
    }

    /**
     * Ensures that the button cannot be activated in loading or disabled mode,
     * when `aria-disabled` might be used over the `disabled` HTML attribute
     */
    const handleClick =
      (handler: LegacyButtonProps['onClick']) =>
      (event: MouseEvent<HTMLButtonElement> & MouseEvent<HTMLAnchorElement>) => {
        if (disabled || loading) {
          event.preventDefault();
        } else if (typeof handler === 'function') {
          handler(event);
        }
      };

    return (
      <Element
        ref={ref}
        className={classes}
        onClick={handleClick(onClick)}
        {...props}
        aria-live={loading ? 'polite' : 'off'}
        aria-busy={loading}
        aria-label={loading ? intl.formatMessage(messages.loadingAriaLabel) : rest['aria-label']}
      >
        {children}
        {loading && (
          <ProcessIndicator
            size={processIndicatorSize()}
            className="btn-loader"
            data-testid="ButtonProgressIndicator"
          />
        )}
      </Element>
    );
  },
);

export default LegacyButton;
