'use client';
import { ChangeEvent, ClipboardEvent, FunctionComponent, KeyboardEvent, useEffect, useMemo, useRef } from 'react';
import { Stack, TextField } from '@mui/material';
import { digitsOnly } from '@/utils';

export interface OtpInputProps {
  /** Number of digit boxes. */
  length?: number;
  /** Controlled value — the digits entered so far. */
  value: string;
  /** Emits the full concatenated value on every change. */
  onChange: (value: string) => void;
  /** Fired once the value reaches `length` digits. */
  onComplete?: (value: string) => void;
  disabled?: boolean;
  autoFocus?: boolean;
  error?: boolean;
  /** Accessible label for the group. */
  'aria-label'?: string;
}

const DEFAULT_LENGTH = 5;
/**
 * The box is sized in `ch`-free absolute terms but must survive 6 boxes + gaps inside the
 * ~480px app frame minus the card padding, so it shrinks rather than overflowing.
 */
const BOX_MIN_SIZE = 40;
const BOX_MAX_SIZE = 52;
const BOX_GAP = 1;

/**
 * One-time-code input: `length` single-digit boxes with auto-advance, backspace-to-previous,
 * and paste distribution. Digits are normalized (Persian/Arabic → ASCII) and the group is
 * forced LTR so codes read left-to-right even inside the RTL (`fa`) layout.
 * @component OtpInput
 */
const OtpInput: FunctionComponent<OtpInputProps> = ({
  length = DEFAULT_LENGTH,
  value,
  onChange,
  onComplete,
  disabled,
  autoFocus,
  error,
  'aria-label': ariaLabel,
}) => {
  const inputRefs = useRef<Array<HTMLInputElement | null>>([]);

  const chars = useMemo(
    () => Array.from({ length }, (_, index) => value[index] ?? ''),
    [value, length]
  );

  useEffect(() => {
    if (autoFocus) inputRefs.current[0]?.focus();
  }, [autoFocus]);

  const focusBox = (index: number) => {
    const target = inputRefs.current[index];
    if (target) {
      target.focus();
      target.select();
    }
  };

  const emit = (next: string[]) => {
    const joined = next.join('');
    onChange(joined);
    if (next.every((char) => char !== '')) onComplete?.(joined);
  };

  const handleChange = (index: number, event: ChangeEvent<HTMLInputElement>) => {
    const incoming = digitsOnly(event.target.value);
    const next = [...chars];

    if (!incoming) {
      next[index] = '';
      emit(next);
      return;
    }

    if (incoming.length === 1) {
      next[index] = incoming;
      focusBox(index + 1);
    } else {
      // Paste / multi-char: fill sequentially from the current box.
      for (let offset = 0; offset < incoming.length && index + offset < length; offset += 1) {
        next[index + offset] = incoming[offset];
      }
      focusBox(Math.min(index + incoming.length, length - 1));
    }
    emit(next);
  };

  const handleKeyDown = (index: number, event: KeyboardEvent<HTMLInputElement>) => {
    if (event.key === 'Backspace' && !chars[index] && index > 0) {
      // Empty box + backspace clears the previous digit too, so one keypress erases one digit
      // instead of the first press only moving focus and the second doing the clearing.
      const next = [...chars];
      next[index - 1] = '';
      focusBox(index - 1);
      emit(next);
    }
  };

  const handlePaste = (index: number, event: ClipboardEvent<HTMLInputElement>) => {
    event.preventDefault();
    const pasted = digitsOnly(event.clipboardData.getData('text'));
    if (!pasted) return;
    const next = [...chars];
    for (let offset = 0; offset < pasted.length && index + offset < length; offset += 1) {
      next[index + offset] = pasted[offset];
    }
    focusBox(Math.min(index + pasted.length, length - 1));
    emit(next);
  };

  return (
    // `gap`, never Stack's `spacing`: spacing compiles to a directional margin that the RTL Emotion
    // cache mirrors, while this group is force-`dir="ltr"` so the code reads left-to-right. The two
    // disagreed on /fa and collapsed the gap between the first two boxes while doubling it at the end.
    <Stack
      direction="row"
      dir="ltr"
      role="group"
      aria-label={ariaLabel}
      sx={{ justifyContent: 'center', gap: BOX_GAP, width: '100%' }}
    >
      {chars.map((char, index) => (
        <TextField
          key={index}
          sx={{ flex: `1 1 ${BOX_MIN_SIZE}px`, minWidth: BOX_MIN_SIZE, maxWidth: BOX_MAX_SIZE }}
          value={char}
          disabled={disabled}
          error={error}
          onChange={(event) => handleChange(index, event as ChangeEvent<HTMLInputElement>)}
          onKeyDown={(event) => handleKeyDown(index, event as KeyboardEvent<HTMLInputElement>)}
          onPaste={(event) => handlePaste(index, event as ClipboardEvent<HTMLInputElement>)}
          inputRef={(el: HTMLInputElement | null) => {
            inputRefs.current[index] = el;
          }}
          slotProps={{
            htmlInput: {
              inputMode: 'numeric',
              maxLength: 1,
              // Lets iOS/Android offer the SMS code as a keyboard suggestion even without WebOTP.
              autoComplete: 'one-time-code',
              'aria-label': `${ariaLabel ?? 'digit'} ${index + 1}`,
              style: { textAlign: 'center', fontSize: '1.25rem', padding: 12, width: '100%' },
            },
          }}
        />
      ))}
    </Stack>
  );
};

export default OtpInput;
