import React, {
  type CSSProperties,
  type ForwardedRef,
  type Ref,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react'

import Button from '../Button/Button'
import {
  hasValue,
  notEmpty,
  parseSymbology,
} from '../../services/HelperServiceTyped'
import Icon from '../Icons/Icon'
import type Tooltip from '../Tooltip/Tooltip'
import FormLabel from '../FormLabel/FormLabel'
import styles from './_text-input.module.scss'
import {
  adjustDatePart,
  dateOverlayPlaceholder,
  getMaxLength,
  getPlaceholder,
  handleDateArrowKey,
  handleDateInput,
  showDateOverlay,
} from './dateInputHelper'

const ARROW_KEY_VALUES = ['arrowup', 'arrowdown']

// Utility functions for number formatting
const formatNumberWithCommas = (
  value: string | number,
  locale?: string,
): string => {
  if (!notEmpty(value)) return ''

  const stringValue = String(value)
  const num = typeof value === 'string' ? parseFloat(value) : value

  // Check if number is too large for safe conversion (beyond MAX_SAFE_INTEGER)
  // or if converting to number would lose precision
  if (stringValue.length > 15 || isNaN(num)) {
    // For very large numbers, manually add commas without converting to number
    // This preserves precision for numbers beyond JavaScript's safe integer range
    const parts = stringValue.split('.')
    const integerPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
    return parts.length > 1 ? `${integerPart}.${parts[1]}` : integerPart
  }

  // For normal-sized numbers, use toLocaleString for proper locale formatting
  return num.toLocaleString(locale || navigator.language || 'en-US')
}

type CalloutType = (stateName: StateNameType, value: string | number) => void
type StateNameType = string | number | undefined
type TooltipProps = React.ComponentProps<typeof Tooltip>
type TextInputBase = {
  /** The value to display in the input */
  value: string | number
  /** Optional callback when focus on input is lost */
  onBlurCallout?: (
    stateName: StateNameType,
    value: React.SyntheticEvent<HTMLTextAreaElement> | string,
  ) => void
  /** Optional number prop used to delay the execution used in a setTimeout's second param as milliseconds */
  debounce?: number
  /** Name of the state for the input */
  stateName?: string | number
  /** Optional callback function to specify when user releases a key on the keyboard */
  keyUp?: (stateName: StateNameType, value: unknown) => void
  /** Optional label above the input */
  labelText?: React.ReactNode
  /** Optional label to the top right of the input */
  rightLabel?: React.ReactNode
  /** Optional placeholder to display inside input */
  placeholder?: string
  /** Optional identifier for the input */
  id?: string
  /** Optional className for the input */
  classType?: string
  /** This gives the input the required attribute and will also display a red asterisk next to the label */
  required?: boolean
  /** Optional boolean to display the "Clear" text in the input */
  clear?: boolean
  /** Optional callback when the Clear button is clicked */
  clearCallout?: () => void
  /** Optional boolean to inidicate if the input should be initialized to the focused state */
  autoFocus?: boolean
  /** Optional show the browser's autocomplete popover */
  autoComplete?: 'on' | 'off'
  /** Optional label that appears inside the input and aligned to the right */
  endText?: string
  /** Boolean to know if an error has occured */
  hasError?: boolean
  /** Optional boolean to specify that an input field is read-only */
  readOnly?: boolean
  /** Optional boolean used to display full width of the input */
  fullWidth?: boolean
  /** Optional boolean to specify the maximum number of characters allowed in the input */
  maxLength?: number
  /** Optional function to specify when the input receives focus */
  setIsOnFocus?: (isOnFocus: boolean) => void
  /** Optional function to run when the input receives focus */
  onFocus?: () => void
  /** Optional function to run when pressing the return / enter key */
  onReturnCallout?: () => void
  /** Optional boolean to not allow the input to remain an empty field */
  noEmpty?: boolean
  /** The right aligned label in the input e.g. '%', 'in', 'lbs' etc */
  inputLabel?: string
  /** Optional class to add to the parent div of the input */
  containerClassName?: string
  /** Optional prop used to display a tooltip for the label  */
  labelTooltip?: Omit<TooltipProps, 'children'>
  /** Optional prop used to display an error message below the input  */
  errorText?: string
  /** Optional boolean to enable dynamic height for textarea */
  autoResize?: boolean
  /** Optional prop to add a test id to the TextInput for QA testing */
  qaTestId?: string
  /** When true, strips scanner symbology prefixes (e.g. `~A`, `~P12`, `]C1`) from the displayed value and callout */
  shouldStripSymbologyPrefix?: boolean
}

type TextInputNotNumber = TextInputBase & {
  /** Optional prop to indicate the type of the input */
  type?:
    | 'email'
    | 'password'
    | 'tel'
    | 'text'
    | 'textarea'
    | 'date-single'
    | 'date-range'
  min?: never
  max?: never
  step?: never
  onlyWholeNumbers?: never
  onlyPositiveNumbers?: never
  noScrollForNumbers?: never
  noArrowKeyNumberChange?: never
  maxDecimalPlaces?: never
  numberFormat?: never
  locale?: never
}

export type TextInputNumber = TextInputBase & {
  /** Required prop to indicate the input is of type `number` */
  type: 'number'
  /** Optional boolean to specify the minimum value for the input */
  min?: number
  /** Optional boolean to specify the maximum value for the input */
  max?: number
  /** Optional value to specify the interval between legal numbers in the input */
  step?: number
  /** Optional prop used to round a number to the nearest integer */
  onlyWholeNumbers?: boolean
  /** Optional prop used to only accept positive numbers as a value */
  onlyPositiveNumbers?: boolean
  /** Disable incrementing / decrementing number value with scroll wheel */
  noScrollForNumbers?: boolean
  /** Disable incrementing / decrementing number value with keyboard arrows (default: false) */
  noArrowKeyNumberChange?: boolean
  /** Used if we want to restrict a certain number of decimal places. */
  maxDecimalPlaces?: number
  /** Optional prop used to display currency or percentage input */
  numberFormat?: {
    /** To indicate the type of numberFormat */
    type: 'currency' | 'percentage'
    /** Currency symbol to display in a gray background (e.g: $) */
    currencySymbol?: string
  }
  /** Optional locale for number formatting (defaults to browser locale) */
  locale?: string
}

type TextInputTypes = TextInputNotNumber | TextInputNumber

// if disabled===true, then callout is optional
// if disabled===false or undefined, then callout is required
type DisabledOrNot =
  | { disabled: true; callout?: CalloutType }
  | { disabled?: false; callout: CalloutType }

export type TextInputProps = TextInputTypes & DisabledOrNot

const TextInput = ({
  value,
  type,
  onBlurCallout,
  debounce,
  callout,
  stateName,
  keyUp,
  labelText,
  rightLabel,
  placeholder,
  id,
  classType,
  required,
  clear,
  clearCallout,
  min,
  max,
  step,
  disabled,
  autoFocus,
  inputLabel,
  autoComplete,
  onlyWholeNumbers,
  onlyPositiveNumbers,
  endText,
  readOnly = false,
  fullWidth = false,
  noEmpty = false,
  maxLength,
  setIsOnFocus,
  onFocus,
  onReturnCallout,
  hasError = false,
  containerClassName = '',
  maxDecimalPlaces,
  labelTooltip,
  noScrollForNumbers,
  noArrowKeyNumberChange,
  numberFormat,
  autoResize,
  errorText,
  ref,
  qaTestId = 'text-input',
  locale,
  shouldStripSymbologyPrefix = false,
}: TextInputProps & {
  ref?: Ref<HTMLTextAreaElement | HTMLInputElement | null>
}): React.JSX.Element => {
  const shouldShowDateOverlay = showDateOverlay({ type })
  const isNumberInput = type === 'number'
  const [customClass, setCustomClass] = useState('')
  const classes = [customClass, classType].filter(Boolean).join(' ')
  const [inputValue, setInputValue] = useState(value ? value : '')
  const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined)
  const [inputType, setInputType] = useState(
    type === 'date-single' || type === 'date-range' ? 'text' : type,
  )
  const inputLabelRef = useRef<HTMLSpanElement>(null)
  const textAreaRef = useRef<HTMLTextAreaElement>(null)
  const previousDateValueRef = useRef(value ? String(value) : '')
  const [isNumberInputFocused, setIsNumberInputFocused] = useState(false)

  const onBlur = (
    value: React.SyntheticEvent<HTMLTextAreaElement> | string,
  ): void => {
    // TODO: This state name only uses "focus". It should be refactored to use a more descriptive name.
    // Clearing the focus class.
    setCustomClass('')

    // Handle number formatting on blur - just set focus state
    if (type === 'number') {
      setIsNumberInputFocused(false)
    }

    if (onBlurCallout) {
      onBlurCallout(stateName ?? '', value)
    }
    if (setIsOnFocus) {
      setIsOnFocus(false)
    }
  }

  useEffect(() => {
    if (autoResize && type === 'textarea' && textAreaRef) {
      const textarea = textAreaRef?.current
      // if element is not present, return
      if (!textarea) return
      // function to adjust the height of the textarea
      const adjustHeight = () => {
        // Reset height to auto
        textarea.style.height = 'auto'
        // Set the height to the scrollHeight
        textarea.style.height = `${textarea.scrollHeight}px`
      }
      // Adjust height initially if there is pre-populated content
      adjustHeight()
      // Add event listener for input changes
      textarea?.addEventListener('input', adjustHeight)
      // Clean up event listener on component unmount
      return () => {
        // removing the event listener
        textarea?.removeEventListener('input', adjustHeight)
      }
    }
  }, [autoResize, type])

  const checkDebounce = useCallback(
    (value: string | number) => {
      clearTimeout(timeoutRef.current)
      if (debounce) {
        timeoutRef.current = setTimeout(() => {
          callout?.(stateName, value)
        }, debounce)
      } else {
        callout?.(stateName, value)
      }
    },
    [debounce, callout, stateName],
  )

  const setValue = useCallback(
    (rawValue: string): void => {
      const value = shouldStripSymbologyPrefix
        ? (parseSymbology(rawValue)?.value ?? rawValue)
        : rawValue

      if (type === 'date-single' || type === 'date-range') {
        const result = handleDateInput(
          value,
          type as 'date-single' | 'date-range',
          String(inputValue),
          ref as React.RefObject<HTMLInputElement>,
          setInputValue,
          checkDebounce,
        )

        if (result.shouldReturn) {
          return
        }

        return
      }

      let val: string | number = value
      if (inputType === 'number') {
        val = onlyWholeNumbers
          ? val
            ? Math.round(val as unknown as number)
            : ''
          : val
        val = onlyPositiveNumbers && (val as unknown as number) < 0 ? 0 : val
        if (
          hasValue(min) &&
          (val as unknown as number) < (min as number) &&
          value !== ''
        )
          val = min as number
        if (
          hasValue(max) &&
          (val as unknown as number) > (max as number) &&
          value !== ''
        )
          val = max as number

        if (value === '' && noEmpty) {
          val = min ?? 0
        }
        if (maxDecimalPlaces) {
          const arr = value.split('.')
          if (arr.length > 1 && arr[1].length > maxDecimalPlaces) return
        }
      }
      setInputValue(val)
      checkDebounce(val)
    },
    [
      type,
      inputType,
      checkDebounce,
      ref,
      inputValue,
      onlyWholeNumbers,
      onlyPositiveNumbers,
      min,
      max,
      noEmpty,
      maxDecimalPlaces,
      shouldStripSymbologyPrefix,
    ],
  )

  const clearValue = () => {
    setInputValue('')
    if (type === 'date-single' || type === 'date-range') {
      previousDateValueRef.current = ''
    }
    if (clearCallout) {
      clearCallout()
    }
    // Focus the input after the clear button is clicked
    setTimeout(() => {
      if (ref && 'current' in ref) {
        ref.current?.focus()
      }
    }, 100)
  }

  const onInputFocus = () => {
    setCustomClass('focus')

    // Handle number formatting on focus - just set focus state
    if (type === 'number') {
      setIsNumberInputFocused(true)
    }

    setIsOnFocus?.(true)
    onFocus?.()
  }

  const togglePassword = () => {
    setInputType(inputType === 'password' ? 'text' : 'password')
  }

  const handleKeyUp = (event: React.KeyboardEvent) => {
    switch (event.keyCode) {
      case 13:
        keyUp && keyUp(stateName, inputValue)
        break
      case 27:
        if (inputValue && (inputValue as string).length > 0) {
          clearValue()
        } else if (!inputValue || (inputValue as string).length === 0) {
          const target = event.target as HTMLInputElement | HTMLTextAreaElement
          target.blur()
        }
        break
      default:
        break
    }
  }

  useEffect(
    () => () => {
      timeoutRef.current && clearTimeout(timeoutRef.current)
    },
    [],
  )

  useEffect(() => {
    setInputType(
      type === 'date-single' || type === 'date-range' ? 'text' : type,
    )
  }, [type])

  useEffect(() => {
    setInputValue(value)
    if (type === 'date-single' || type === 'date-range') {
      previousDateValueRef.current = String(value)
    }
  }, [value, type])

  const inputRefOffsetWidth = inputLabelRef?.current?.offsetWidth
  const inlineStyles = {
    '--input-label-width': (inputRefOffsetWidth ?? 0) + 12 + 'px',
  } as CSSProperties

  const handleDateArrowKeys = useMemo(() => {
    if (!showDateOverlay({ type })) return null

    return (e: React.KeyboardEvent<HTMLInputElement>) => {
      if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
        e.preventDefault()
        const input = e.target as HTMLInputElement
        const cursorPosition = input.selectionStart || 0
        const newValue = adjustDatePart(
          String(inputValue),
          cursorPosition,
          e.key === 'ArrowUp',
          type,
        )

        // Use specialized arrow key handler that preserves cursor position
        handleDateArrowKey(
          newValue,
          type as 'date-single' | 'date-range',
          String(inputValue),
          ref as React.RefObject<HTMLInputElement>,
          setInputValue,
          checkDebounce,
        )
      }
    }
  }, [type, inputValue, ref, checkDebounce])

  // Overlay logic extracted for reuse
  const shouldShowOverlay = isNumberInput && inputValue
  // Always apply wrapper class when wrapper div is present
  const overlayWrapperClass = isNumberInput ? styles.numberInputWrapper : ''
  const overlayInputClass =
    shouldShowOverlay && !isNumberInputFocused
      ? styles.numberInputWithOverlay
      : ''

  const renderNumberOverlay = () => {
    if (!shouldShowOverlay || isNumberInputFocused) return null

    return (
      <div className={styles.numberInputOverlay}>
        <span className={styles.numberInputOverlayText}>
          {formatNumberWithCommas(String(inputValue), locale)}
        </span>
      </div>
    )
  }

  const renderInputWithOverlay = (inputProps: Record<string, unknown> = {}) => {
    const inputElement = (
      <input
        qa-test-id={`${qaTestId}-input-box`}
        {...commonInputProps}
        placeholder={getPlaceholder({
          type,
          placeholder,
        })}
        className={`${classes} ${endText ? styles.endInput : ''} ${
          numberFormat?.type === 'currency' ? styles.currencyInput : ''
        } ${overlayInputClass}`}
        maxLength={maxLength}
        {...inputProps}
      />
    )

    // Wrap in div when number input
    if (isNumberInput) {
      return (
        <div className={overlayWrapperClass}>
          {inputElement}
          {renderNumberOverlay()}
        </div>
      )
    }

    return inputElement
  }

  // Common input props to avoid duplication
  const commonInputProps = {
    'data-testid': 'text-input-field',
    id,
    ref: ref as ForwardedRef<HTMLInputElement>,
    type: inputType ? inputType : 'text',
    value: inputValue,
    onBlur: (e: React.FocusEvent<HTMLInputElement>) => onBlur(e.target.value),
    onFocus: onInputFocus,
    onMouseDown: (e: React.MouseEvent<HTMLInputElement>) => {
      e.stopPropagation()
      ;(e.target as HTMLInputElement).focus()
    },
    onTouchEnd: (e: React.TouchEvent<HTMLInputElement>) => {
      e.stopPropagation()
      ;(e.target as HTMLInputElement).focus()
    },
    required,
    onKeyUp: (e: React.KeyboardEvent<HTMLInputElement>) => handleKeyUp(e),
    onChange: (e: React.ChangeEvent<HTMLInputElement>) =>
      setValue(e.target.value),
    min,
    max,
    onWheel: (e: React.WheelEvent<HTMLInputElement>) =>
      noScrollForNumbers && e.currentTarget.blur(),
    disabled,
    autoFocus,
    autoComplete: autoComplete || 'on',
    readOnly,
    step,
    onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => {
      if (
        noArrowKeyNumberChange &&
        inputType === 'number' &&
        ARROW_KEY_VALUES.includes(e.key.toLowerCase())
      ) {
        e.preventDefault()
      }
      if (e.key === 'Enter') {
        onReturnCallout?.()
      }

      // Handle arrow keys for date inputs
      if (handleDateArrowKeys) {
        handleDateArrowKeys(e)
      }
    },
  }

  return (
    <div
      data-testid='text-input-container'
      className={`${styles.textInput} ${
        fullWidth ? styles.fullWidth : ''
      } ${containerClassName}`}
      qa-test-id={qaTestId}
    >
      {labelText && (
        <div className={labelTooltip && `${styles.labelText}`}>
          <FormLabel
            label={labelText}
            rightLabel={rightLabel}
            tooltip={labelTooltip}
            required={required}
            error={hasError}
            id={id}
            disabled={disabled}
          />
        </div>
      )}

      {type === 'textarea' ? (
        <textarea
          data-testid='text-input-textarea'
          qa-test-id={`${qaTestId}-textarea`}
          id={id}
          ref={
            autoResize
              ? textAreaRef
              : (ref as ForwardedRef<HTMLTextAreaElement>)
          }
          value={inputValue}
          onBlur={onBlur}
          onFocus={onInputFocus}
          placeholder={placeholder}
          required={required}
          className={
            classes + ` ${autoResize ? styles.textareaAutoResize : ''}`
          }
          onKeyUp={(e) => handleKeyUp(e)}
          onChange={(e) => setValue(e.target.value)}
          disabled={disabled}
          autoFocus={autoFocus}
          readOnly={readOnly}
          maxLength={maxLength}
        />
      ) : (
        <div
          data-testid='text-input-wrapper'
          className={`${styles.inputContainer} ${
            inputLabel &&
            inputLabelRef?.current &&
            numberFormat?.type !== 'percentage'
              ? styles.hasLabel
              : ''
          }${fullWidth ? styles.fullWidth : ''} ${
            numberFormat?.type === 'currency' ? styles.flex : ''
          }`}
          style={inlineStyles}
        >
          {numberFormat?.type === 'currency' && (
            <span
              data-testid='text-input-currency'
              className={`${styles.inputPrefix} ${
                customClass === 'error' || classType === 'error'
                  ? styles.prefixError
                  : ''
              } ${customClass === 'focus' ? styles.prefixActive : ''}`}
            >
              {numberFormat?.currencySymbol}
            </span>
          )}

          {type === 'password' && inputValue !== '' && (
            <Button
              as='unstyled'
              onClick={togglePassword}
              className={styles.iconStyle}
            >
              <Icon icon='eyeglasses' iconSize='20px' />
              {inputType === 'password' ? (
                <div className={styles.iconOverlayLine} />
              ) : null}
            </Button>
          )}

          {endText ? (
            <div className={styles.conditionalWrapperFlex}>
              {shouldShowDateOverlay ? (
                <div className={styles.dateInputWrapper}>
                  <input
                    {...commonInputProps}
                    placeholder=''
                    className={`${classes} ${endText ? styles.endInput : ''} ${
                      numberFormat?.type === 'currency'
                        ? styles.currencyInput
                        : ''
                    } ${styles.dateInputWithOverlay}`}
                    maxLength={getMaxLength({
                      type,
                      maxLength,
                    })}
                  />
                  <div className={styles.dateInputOverlay}>
                    <span className={styles.dateInputTypedText}>
                      {String(inputValue)}
                    </span>
                    <span className={styles.dateInputPlaceholderText}>
                      {dateOverlayPlaceholder({
                        type,
                        inputValue,
                      })}
                    </span>
                  </div>
                </div>
              ) : (
                renderInputWithOverlay()
              )}
              {endText && (
                <input
                  data-testid='text-input-end-text'
                  type='text'
                  className={`${styles.endText} ${classes}`}
                  value={endText}
                  disabled
                />
              )}
              {inputLabel ? (
                <span
                  data-testid='text-input-suffix'
                  className={`${styles.inputSuffix} ${
                    customClass === 'error' ? styles.prefixError : ''
                  } ${customClass === 'focus' ? styles.prefixActive : ''}`}
                  ref={inputLabelRef}
                >
                  {inputLabel}
                </span>
              ) : null}
              {clear && !disabled && notEmpty(value) && (
                <Button
                  as='unstyled'
                  className={styles.buttonForXIcon}
                  onClick={clearValue}
                >
                  <Icon
                    icon='x'
                    iconSize='12px'
                    className={`${styles.xIcon} ${
                      customClass === 'focus' ? styles.focused : ''
                    }`}
                    qaTestId={`${qaTestId}-x-icon`}
                  />
                </Button>
              )}
            </div>
          ) : (
            <>
              {shouldShowDateOverlay ? (
                <div className={styles.dateInputWrapper}>
                  <input
                    {...commonInputProps}
                    placeholder=''
                    className={`${classes} ${endText ? styles.endInput : ''} ${
                      numberFormat?.type === 'currency'
                        ? styles.currencyInput
                        : ''
                    } ${styles.dateInputWithOverlay}`}
                    maxLength={getMaxLength({
                      type,
                      maxLength,
                    })}
                  />
                  <div className={styles.dateInputOverlay}>
                    <span className={styles.dateInputTypedText}>
                      {String(inputValue)}
                    </span>
                    <span className={styles.dateInputPlaceholderText}>
                      {dateOverlayPlaceholder({
                        type,
                        inputValue,
                      })}
                    </span>
                  </div>
                </div>
              ) : (
                renderInputWithOverlay()
              )}
              {inputLabel ? (
                <span
                  data-testid='text-input-suffix'
                  className={`${styles.inputSuffix} ${
                    customClass === 'error' ? styles.prefixError : ''
                  } ${customClass === 'focus' ? styles.prefixActive : ''}`}
                  ref={inputLabelRef}
                >
                  {inputLabel}
                </span>
              ) : null}
              {clear && !disabled && notEmpty(value) && (
                <Button
                  as='unstyled'
                  className={styles.buttonForXIcon}
                  onClick={clearValue}
                >
                  <Icon
                    icon='x'
                    iconSize='12px'
                    className={`${styles.xIcon} ${
                      customClass === 'focus' ? styles.focused : ''
                    }`}
                    qaTestId={`${qaTestId}-x-icon`}
                  />
                </Button>
              )}
            </>
          )}
        </div>
      )}

      {(customClass === 'error' || classType?.includes('error')) &&
        errorText && (
          <div data-testid='text-input-error' className={styles.errorTextStyle}>
            {errorText}
          </div>
        )}
    </div>
  )
}

TextInput.displayName = 'TextInput'
export default TextInput
