import {
  useCallback,
  useEffect,
  useImperativeHandle,
  useLayoutEffect,
  useRef,
  useState,
} from 'react';
import classnames from 'classnames';
import css from 'styled-jsx/css';

import type { WithSpacingProps } from '~/src/lib/hocs/withSpacing';
import type { ReactNode, RefObject } from 'react';

import Box from '../Box';
import Clickable from '../Clickable';
import TickIcon from '../Icon/TickIcon';
import Text from '../Text';

export interface CheckboxApi {
  check(): void;
  uncheck(): void;
  reportValidity(): void;
}

export interface CheckboxProps extends WithSpacingProps {
  testId?: string;
  apiRef?: RefObject<CheckboxApi | null>;

  name?: HTMLInputElement['name'];
  title?: ReactNode;
  description?: ReactNode;
  validationMessage?: string;

  isInitiallyChecked?: boolean;
  isDisabled?: boolean;
  isRequired?: boolean;

  size?: 'medium' | 'large';

  onChange?(isChecked: boolean): void;
}

const styles = css.resolve`
  .checkbox {
    border-radius: 0.26rem;
    border: 1px solid #999;
    background-color: #151515;
  }

  .checkbox:hover {
    border-color: #fff;
  }

  .checkbox:focus-visible {
    outline: 3px solid rgba(255, 255, 255, 0.15);
  }

  .checkbox.isDisabled {
    color: #666;
    border-color: #444;
    background-color: #313131;
  }
`;

const Checkbox = ({
  testId,
  apiRef,

  name,
  title,
  description,
  validationMessage = '',

  isInitiallyChecked = false,
  isDisabled = false,
  isRequired = false,

  size = 'medium',

  onChange,

  ...spacingProps
}: CheckboxProps) => {
  const [isChecked, setIsChecked] = useState(isInitiallyChecked);
  const inputRef = useRef<HTMLInputElement>(null);
  const firstRender = useRef(true);

  // Mirror the latest checked state so the form `reset` listener (registered
  // once) can read it without a stale closure.
  const isCheckedRef = useRef(isChecked);
  isCheckedRef.current = isChecked;

  const onChangeInternal = useCallback(
    (value?: boolean) => {
      const isCheckedNext = value ?? !isChecked;

      setIsChecked(isCheckedNext);
      onChange?.(isCheckedNext);
    },
    [onChange, isChecked]
  );

  const reportValidity = useCallback(() => {
    if (isRequired && !isChecked) {
      inputRef.current?.setCustomValidity(validationMessage);
      inputRef.current?.reportValidity();
    } else {
      inputRef.current?.setCustomValidity('');
    }
  }, [isRequired, isChecked, validationMessage]);

  useImperativeHandle(
    apiRef,
    () => ({
      reportValidity,
      check() {
        onChangeInternal(true);
      },
      uncheck() {
        onChangeInternal(false);
      },
    }),
    [reportValidity, onChangeInternal]
  );

  // Native `form.reset()` (e.g. via Form's `clear()`) only resets uncontrolled
  // DOM controls. Because this checkbox is controlled by React state, reset it
  // ourselves so it visibly returns to its initial state on form reset.
  useEffect(() => {
    const form = inputRef.current?.form;
    if (!form) return;

    const handleReset = () => {
      if (isCheckedRef.current === isInitiallyChecked) return;

      // Re-arm firstRender so the validity effect treats this like a fresh
      // mount and doesn't surface a validation bubble after reset.
      firstRender.current = true;
      setIsChecked(isInitiallyChecked);
    };

    form.addEventListener('reset', handleReset);
    return () => form.removeEventListener('reset', handleReset);
  }, [isInitiallyChecked]);

  useLayoutEffect(() => {
    // avoid validation on first render
    if (firstRender.current) {
      // set validation message on first render for cases
      // when validation triggered by form submission
      inputRef.current?.setCustomValidity(validationMessage);

      firstRender.current = false;
      return;
    }

    reportValidity();
  }, [reportValidity]);

  return (
    <Box {...spacingProps} flexRow style={{ alignItems: 'flex-start' }}>
      <Box positionRelative noFlexShrink width="1.75rem" height="1.75rem">
        <input
          ref={inputRef}
          name={name}
          type="checkbox"
          checked={isChecked}
          tabIndex={-1}
          required={isRequired}
          style={{
            position: 'absolute',
            zIndex: -1,
            top: 0,
            left: 0,
            margin: 0,
            opacity: '0',
            pointerEvents: 'none',
            width: '100%',
            height: '100%',
          }}
        />
        <Clickable
          testId={testId}
          width="100%"
          height="100%"
          withFocusStyle={false}
          withDisabledStyle={false}
          isDisabled={isDisabled}
          onClick={() => onChangeInternal()}
          className={classnames(styles.className, 'checkbox', {
            isDisabled,
          })}
        >
          {isChecked && <TickIcon width="100%" height="100%" />}
        </Clickable>
      </Box>
      {(title || description) && (
        <Box padding="0 0 0 1rem">
          {title && (
            <Text
              size={size == 'large' ? '1.70rem' : '1.3rem'}
              lineHeight="1.75rem"
            >
              {title}
            </Text>
          )}
          {description && (
            <Text
              testId={testId && `${testId}-description`}
              color="#aaa"
              size="1.15rem"
              lineHeight="1.3rem"
              padding="0.225rem 0"
            >
              {description}
            </Text>
          )}
        </Box>
      )}
      {styles.styles}
    </Box>
  );
};

export default Checkbox;
