import type { ComponentProps } from 'react';
import React, {
  forwardRef,
  useCallback,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
  useState,
} from 'react';
import { InteractionManager } from 'react-native';
import type { StyleProp, ViewStyle, TextInput } from 'react-native';
import Icon from '../Icon';
import PinCell from './PinCell';
import {
  StyledErrorContainer,
  StyledErrorMessage,
  StyledHiddenInput,
  StyledSpacer,
  StyledPinWrapper,
  StyledWrapper,
} from './StyledPinInput';
import type { State } from './StyledPinInput';

interface PinInputProps {
  /**
   * The value to show for the input.
   */
  value?: string;
  /**
   * Callback function that's called when the text changed.
   */
  onChangeText: (value: string) => void;
  /**
   * Callback function that's called when the input is completely filled.
   */
  onFulfill?: (value: string) => void;
  /**
   * Number of character for the input.
   */
  length?: number;
  /*
   * Whether the input is disabled.
   */
  disabled?: boolean;
  /**
   * Whether the pin value is secured using masks.
   * By default, this is true, meaning that masks are shown to hide pin value.
   */
  secure?: boolean;
  /**
   * If true, focuses the input on componentDidMount.
   */
  autoFocus?: boolean;
  /**
   * Error message.
   */
  error?: string;
  /**
   * Additional style.
   */
  style?: StyleProp<ViewStyle>;
  /**
   * Testing id of the component.
   */
  testID?: string;
  /**
   * The text content type of the input.
   */
  textContentType?: ComponentProps<typeof TextInput>['textContentType'];
  /**
   * The autofill type of the input.
   */
  autoComplete?: ComponentProps<typeof TextInput>['autoComplete'];
  /**
   * If true, indicates that the PinInput is accessible to screen readers.
   */
  accessible?: boolean;
}

export function getState({
  disabled,
  error,
}: {
  disabled?: boolean;
  error?: string;
}): State {
  if (disabled) {
    return 'disabled';
  }
  if (error) {
    return 'error';
  }
  return 'default';
}

export interface PinInputHandler {
  /**
   * focus into input
   */
  focus: () => void;
  /**
   * blur text input
   */
  blur: () => void;
}
// Normalize the value to only contain numbers and limit the length
export const normalizeValue = (value?: string, length = 4) => {
  return (value?.match(/\d/g) || []).join('').slice(0, length);
};

const PinInput = forwardRef<PinInputHandler, PinInputProps>(
  (
    {
      value = '',
      onChangeText,
      onFulfill,
      length = 4,
      disabled = false,
      secure = true,
      autoFocus = false,
      error,
      style,
      testID,
      textContentType,
      autoComplete,
      accessible,
    },
    ref
  ) => {
    const inputRef = useRef<TextInput>(null);
    const [focused, setFocused] = useState(autoFocus);
    const state = getState({ disabled, error });
    const trimmedValue = normalizeValue(value, length);

    const focus = useCallback(() => {
      if (inputRef?.current) {
        inputRef.current.focus();
        setFocused(true);
      }
    }, []);

    const blur = useCallback(() => {
      if (inputRef?.current) {
        inputRef.current.blur();
        setFocused(false);
      }
    }, []);
    const changeText = useCallback(
      (text: string) => {
        const trimmedPin = normalizeValue(text, length);

        // If current value is already the length, and user is not deleting,
        // replace it with the new text, starting from the length
        if (
          trimmedValue.length === length &&
          trimmedValue.length <= text.length
        ) {
          // Slice the text, starting from the length position
          const slicedText = text.slice(length);

          onChangeText?.(normalizeValue(slicedText, length));

          if (slicedText.length === length) {
            onFulfill?.(slicedText);
          }
        } else {
          // Prevent user from entering more than the length
          onChangeText?.(trimmedPin);

          if (trimmedPin.length === length) {
            onFulfill?.(trimmedPin);
          }
        }
      },
      [length, onChangeText, onFulfill, trimmedValue.length]
    );

    useEffect(() => {
      // Must run after animations for keyboard to automatically open
      if (autoFocus) {
        InteractionManager.runAfterInteractions(focus);
      }
    }, [inputRef]);

    useImperativeHandle(ref, () => ({
      focus,
      blur,
    }));

    const selection = useMemo(
      () => ({
        start: trimmedValue.length,
      }),
      [trimmedValue]
    );

    return (
      <StyledWrapper style={style} testID={testID} accessible={accessible}>
        <StyledPinWrapper
          onPress={focus}
          disabled={disabled}
          testID="pin-wrapper"
        >
          {[...Array(length).keys()].map((index) => (
            <React.Fragment key={index}>
              {index !== 0 && <StyledSpacer />}
              <PinCell
                value={trimmedValue.charAt(index)}
                secure={secure}
                focused={focused && index === trimmedValue.length}
                state={state}
              />
            </React.Fragment>
          ))}
        </StyledPinWrapper>
        {state === 'error' && (
          <StyledErrorContainer>
            <Icon
              icon="circle-info"
              size="xsmall"
              intent="danger"
              testID="pin-error-icon"
            />
            <StyledErrorMessage>{error}</StyledErrorMessage>
          </StyledErrorContainer>
        )}
        <StyledHiddenInput
          themePinLength={length}
          ref={inputRef}
          value={trimmedValue}
          onChangeText={changeText}
          secureTextEntry={secure}
          editable={!disabled}
          autoFocus={autoFocus}
          onFocus={focus}
          onBlur={blur}
          inputMode="numeric"
          testID="pin-hidden-input"
          textContentType={textContentType}
          autoComplete={autoComplete}
          selectTextOnFocus={false}
          selection={selection}
        />
      </StyledWrapper>
    );
  }
);

PinInput.displayName = 'PinInput';

export default PinInput;
