import React, { ChangeEventHandler, useCallback, useEffect, useRef } from 'react';

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

import { StyledCheckbox } from './styled';

/** Props for {@link Checkbox} */
export interface CheckboxProps
  extends CommonProps,
    Omit<GeneralFormControlProps<boolean | null>, 'onChange'> {
  /**
   * Checkbox value.
   * Can be at three states:
   * - `true`, the {@link Checkbox} checkbox will be `checked`
   * - `false`, the {@link Checkbox} checkbox will be `unchecked`
   * - `null`, the {@link Checkbox} checkbox will be `indeterminate`
   */
  value?: boolean | null;
  /** Function that will be invoked when user click by checkbox */
  onChange?: (value: boolean) => void;
  tabIndex?: number;
}

/**
 * Checkbox input that provide custom `<input type="checkbox" />` design.
 *
 * Checkbox value can be at three states:
 *
 * | Value   | Example                                                  | State             |
 * | ------- | -------------------------------------------------------- | ----------------- |
 * | `false` | <Story id="form-checkbox--default" noCanvas />           | Unchecked         |
 * | `true`  | <Story id="form-checkbox--checked" noCanvas />           | Checked           |
 * | `null`  | <Story id="form-checkbox--partially-checked" noCanvas /> | Partially checked |
 *
 * ```tsx
 * import { Checkbox } from "ui-kit";
 *
 * <Checkbox value={true} onChange={value => console.log(value)} />
 * ```
 *
 * Checkbox can be disabled by `disabled` prop.
 *
 * ```tsx
 * import { Checkbox } from "ui-kit";
 *
 * <Checkbox disabled />
 * ```
 */
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
  (
    {
      id,
      ariaDescribedBy,
      ariaErrorMessage,
      ariaRequired,
      value,
      onChange,
      onBlur,
      required,
      disabled,
      tabIndex,
      ariaInvalid,
      ariaLabel,
      className,
      testId,
      ...rest
    },
    forwardedRef,
  ) => {
    assertEmptyObject(rest);

    const testIdAttribute = useTestIdAttribute();

    const checkboxRef = useRef<HTMLInputElement | null>(null);

    const setRef = useCallback(
      (ref: HTMLInputElement) => {
        checkboxRef.current = ref;
        setForwarderRef(forwardedRef, ref);
      },
      [forwardedRef],
    );

    useEffect(() => {
      /* istanbul ignore else */
      if (checkboxRef.current) {
        checkboxRef.current.indeterminate = value === null;
      }
    }, [value]);

    const handleInputChange: ChangeEventHandler<HTMLInputElement> = useCallback(
      (event) => {
        if (onChange && event.currentTarget.checked !== value) {
          onChange(event.currentTarget.checked);
        }
      },
      [onChange, value],
    );

    return (
      <StyledCheckbox
        ref={setRef}
        aria-describedby={ariaDescribedBy}
        aria-errormessage={ariaErrorMessage}
        aria-invalid={ariaInvalid}
        aria-label={ariaLabel}
        aria-required={ariaRequired}
        checked={value === true}
        className={className}
        disabled={disabled}
        id={id}
        onBlur={onBlur}
        onChange={handleInputChange}
        required={required}
        tabIndex={tabIndex}
        type="checkbox"
        {...{ [testIdAttribute]: testId }}
      />
    );
  },
);
