import {
  AriaAttributes,
  ButtonHTMLAttributes,
  FocusEventHandler,
  KeyboardEventHandler,
  MouseEventHandler,
  forwardRef,
  useCallback,
} from 'react';

import { useTestIdAttribute } from '../../hooks/useTestIdAttribute';
import { TestIdProps } from '../../types';
import { assertEmptyObject } from '../../utils/assertEmptyObject';
import { setForwarderRef } from '../../utils/setForwarderRef';

import { useTriggerButtonContext } from './contexts/TriggerButtonContext/TriggerButtonContext';

export interface TriggerButtonProps
  extends TestIdProps,
    Pick<
      ButtonHTMLAttributes<HTMLButtonElement>,
      | 'onBlur'
      | 'onClick'
      | 'onKeyDown'
      | 'children'
      | 'onMouseDown'
      | 'tabIndex'
      | 'className'
      | 'disabled'
      | 'id'
      | 'name'
      | 'type'
      | 'value'
    > {
  ariaLabel?: AriaAttributes['aria-label'];
  ariaDescribedBy?: AriaAttributes['aria-describedby'];
  ariaInvalid?: AriaAttributes['aria-invalid'];

  ariaControls?: AriaAttributes['aria-controls'];
  ariaErrorMessage?: AriaAttributes['aria-errormessage'];
  ariaExpanded?: AriaAttributes['aria-expanded'];
  ariaHasPopup?: AriaAttributes['aria-haspopup'];
  ariaHidden?: AriaAttributes['aria-hidden'];
}

/**
 * Button that should be used as base for creating other buttons that should have
 * possibility of using as trigger in {@link Menu} or {@link SelectField}.
 *
 * It add some a11y props to button to make it valid when they used as trigger.
 *
 * Just use as usual button without any specific actions.
 */
export const TriggerButton = forwardRef<HTMLButtonElement, TriggerButtonProps>((props, buttonRef) => {
  const {
    children,
    ariaHidden,
    ariaDescribedBy,
    ariaErrorMessage,
    ariaLabel,
    ariaInvalid,
    ariaControls,
    ariaHasPopup,
    ariaExpanded,
    onClick,
    onBlur,
    id,
    value,
    name,
    className,
    onMouseDown,
    onKeyDown,
    type,
    tabIndex,
    disabled,
    testId,
    ...rest
  } = props;
  assertEmptyObject(rest);

  const {
    triggerRef,
    ariaExpanded: contextAriaExpanded,
    ariaControls: contextAriaControls,
    ariaDescribedBy: contextAriaDescribedBy,
    ariaInvalid: contextAriaInvalid,
    ariaErrorMessage: contextAriaErrorMessage,
    ariaLabel: contextAriaLabel,
    ariaHasPopup: contextAriaHasPopup,
    onBlur: contextOnBlur,
    onClick: contextOnClick,
    id: contextId,
    disabled: contextDisabled,
    role: contextRole,
    ...restContextProps
  } = useTriggerButtonContext();
  assertEmptyObject(restContextProps);

  const testIdAttribute = useTestIdAttribute();

  const setRef = useCallback(
    (ref: HTMLButtonElement) => {
      setForwarderRef(triggerRef, ref);
      setForwarderRef(buttonRef, ref);
    },
    [buttonRef, triggerRef],
  );

  // We need to combine event handlers execution from all props
  const handleBlur: FocusEventHandler<HTMLButtonElement> = (event) => {
    contextOnBlur?.(event);
    onBlur?.(event);
  };

  // We use our own implementation of onClick (see two methods below)
  // because we want able to use stopPropagation() on trigger button children,
  // and it will not work with default onClick() because the stopPropagation()
  // in onKeyDown handler will not stop onClick() in trigger button=(
  const handleKeyDown: KeyboardEventHandler<HTMLButtonElement> = (event) => {
    if (event.key === ' ' || event.key === 'Enter' || event.key === 'ArrowDown') {
      contextOnClick?.();
    }
    onKeyDown?.(event);
  };
  const handleMouseUp: MouseEventHandler<HTMLButtonElement> = (event) => {
    if (event.button === 0) {
      contextOnClick?.();
    }
  };

  // Trigger props should have more weight instead usual button props
  return (
    <button
      ref={setRef}
      aria-controls={contextAriaControls ?? ariaControls}
      aria-describedby={contextAriaDescribedBy ?? ariaDescribedBy}
      aria-errormessage={contextAriaErrorMessage ?? ariaErrorMessage}
      aria-expanded={contextAriaExpanded ?? ariaExpanded}
      aria-haspopup={contextAriaHasPopup ?? ariaHasPopup}
      aria-hidden={ariaHidden}
      aria-invalid={contextAriaInvalid ?? ariaInvalid}
      aria-label={contextAriaLabel ?? ariaLabel}
      className={className}
      disabled={contextDisabled ?? disabled}
      id={contextId ?? id}
      name={name}
      onBlur={handleBlur}
      onClick={onClick}
      onKeyDown={handleKeyDown}
      onMouseDown={onMouseDown}
      onMouseUp={handleMouseUp}
      role={contextRole}
      tabIndex={tabIndex}
      type={type ?? 'button'}
      value={value}
      {...{ [testIdAttribute]: testId }}
    >
      {children}
    </button>
  );
});
