/* 
  This is the otp input control component.
  It contains the following components:
  - OTPInput
*/
import React, { memo, useState, useCallback, useEffect } from 'react'
import './OtpInputControl.scss'
import SingleInput from './SingleInput'

// Define the props type for the OTPInput component
interface OTPInputProps {
    length: number;
    isNumberInput?: boolean;
    autoFocus?: boolean;
    disabled?: boolean;
    onChangeOTP: (otp: string) => void;
    inputClassName?: string;
    inputStyle?: React.CSSProperties;
}

export function OTPInputComponent(props: OTPInputProps) {
    const {
        length,
        isNumberInput,
        autoFocus,
        disabled,
        onChangeOTP,
        inputClassName,
        inputStyle,
        ...rest
    } = props

    const [activeInput, setActiveInput] = useState<number>(0)
    const [otpValues, setOTPValues] = useState<string[]>(Array(length).fill(''))

    // Handle the otp change
    const handleOtpChange = useCallback(
        (otp: string[]) => {
            const otpValue = otp.join('')
            onChangeOTP(otpValue)
        },
        [onChangeOTP]
    )

    // Get the right value
    const getRightValue = useCallback(
        (str: string) => {
            let changedValue = str
            if (!isNumberInput || !changedValue) {
                return changedValue
            }
            return Number(changedValue) >= 0 ? changedValue : ''
        },
        [isNumberInput]
    )

    // Change the code at focus
    const changeCodeAtFocus = useCallback(
        (str: string) => {
            const updatedOTPValues = [...otpValues]
            updatedOTPValues[activeInput] = str[0] || ''
            setOTPValues(updatedOTPValues)
            handleOtpChange(updatedOTPValues)
        },
        [activeInput, handleOtpChange, otpValues]
    )

    // Focus the input
    const focusInput = useCallback(
        (inputIndex: number) => {
            const selectedIndex = Math.max(Math.min(length - 1, inputIndex), 0)
            setActiveInput(selectedIndex)
        },
        [length]
    )

    // Focus the previous input
    const focusPrevInput = useCallback(() => {
        focusInput(activeInput - 1)
    }, [activeInput, focusInput])

    // Focus the next input
    const focusNextInput = useCallback(() => {
        focusInput(activeInput + 1)
    }, [activeInput, focusInput])

    // Handle the focus event
    const handleOnFocus = useCallback(
        (index: number) => () => {
            focusInput(index)
        },
        [focusInput]
    )

    // Handle the change event
    const handleOnChange = useCallback(
        (e: React.ChangeEvent<HTMLInputElement>) => {
            const val = getRightValue(e.currentTarget.value)
            if (!val) {
                e.preventDefault()
                return
            }
            changeCodeAtFocus(val)
            focusNextInput()
        },
        [changeCodeAtFocus, focusNextInput, getRightValue]
    )

    // Handle the blur event
    const onBlur = useCallback(() => {
        setActiveInput(-1)
    }, [])

    // Handle the key down event
    const handleOnKeyDown = useCallback(
        (e: React.KeyboardEvent<HTMLInputElement>) => {
            const pressedKey = e.key
            switch (pressedKey) {
                case 'Backspace':
                case 'Delete': {
                    e.preventDefault()
                    if (otpValues[activeInput]) {
                        changeCodeAtFocus('')
                    } else {
                        focusPrevInput()
                    }
                    break
                }
                case 'ArrowLeft': {
                    e.preventDefault()
                    focusPrevInput()
                    break
                }
                case 'ArrowRight': {
                    e.preventDefault()
                    focusNextInput()
                    break
                }
                case 'e':
                case 'E': {
                    e.preventDefault()
                    break
                }
                default: {
                    if (pressedKey.match(/^[^a-zA-Z0-9]$/)) {
                        e.preventDefault()
                    }
                    break
                }
            }
        },
        [activeInput, changeCodeAtFocus, focusNextInput, focusPrevInput, otpValues]
    )

    // Handle the paste event
    const handleOnPaste = useCallback(
        (e: React.ClipboardEvent<HTMLInputElement>) => {
            e.preventDefault()
            const pastedData = e.clipboardData
                .getData('text/plain')
                .trim()
                .slice(0, length - activeInput)
                .split('')
            if (pastedData) {
                let nextFocusIndex = 0
                const updatedOTPValues = [...otpValues]
                updatedOTPValues.forEach((val, index) => {
                    if (index >= activeInput) {
                        const changedValue = getRightValue(pastedData.shift() || val)
                        if (changedValue) {
                            updatedOTPValues[index] = changedValue
                            nextFocusIndex = index
                        }
                    }
                })
                onChangeOTP(updatedOTPValues.join(''))
                setOTPValues(updatedOTPValues)
                setActiveInput(Math.min(nextFocusIndex + 1, length - 1))
            }
        },
        [activeInput, getRightValue, length, otpValues, onChangeOTP]
    )

    return (
        <div className="verificationWrap justify-center w-full !gap-2 sm:!gap-3.5 md:!gap-4" {...rest}>
            {Array(length)
                .fill('')
                .map((_, index) => (
                    <SingleInput
                        key={`SingleInput-${index}`}
                        type={isNumberInput ? 'number' : 'text'}
                        focus={activeInput === index}
                        value={otpValues && otpValues[index]}
                        autoFocus={autoFocus}
                        onFocus={handleOnFocus(index)}
                        onChange={handleOnChange}
                        onKeyDown={handleOnKeyDown}
                        onBlur={onBlur}
                        onPaste={handleOnPaste}
                        style={inputStyle}
                        className={inputClassName}
                        disabled={disabled}
                    />
                ))}
        </div>
    )
}

const OTPInput = memo(OTPInputComponent)
export default OTPInput 