import { forwardRef, useState } from 'react';
import { type TextInput, type TextInputProps, type ViewProps } from 'react-native';
import {
  InputField as InputFieldPrimitive,
  InputRoot as InputRootPrimitive,
  dataAttributes,
  type IInputFieldProps,
} from '@cdx-ui/primitives';
import { cn } from '@cdx-ui/utils';
import {
  type CurrencyInputVariantProps,
  currencyInputFieldVariants,
  currencyInputRootVariants,
} from './styles';

// Hoisted at module scope — Intl instantiation is expensive (parses locale data).
const currencyFormatter = new Intl.NumberFormat('en-US', {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
});

// Cap the accumulated cents to 15 digits so the integer stays below
// Number.MAX_SAFE_INTEGER (16 digits). Past that, parsing loses precision and the
// amount garbles or resets — 15 digits already allows up to $9,999,999,999,999.99.
const MAX_DIGITS = 15;

function centsToAmountDisplay(cents: number): string {
  return currencyFormatter.format(cents / 100);
}

function valueToCents(value: number | undefined | null): number {
  return Math.round((value ?? 0) * 100);
}

export interface CurrencyInputProps
  extends
    IInputFieldProps,
    CurrencyInputVariantProps,
    Pick<
      TextInputProps,
      'accessibilityLabel' | 'accessibilityHint' | 'testID' | 'onFocus' | 'onBlur'
    > {
  /** Current numeric value. Renders formatted zero when undefined or null. */
  value?: number;
  /** Called with the parsed number when the user changes the amount. */
  onChangeValue?: (value: number) => void;
  /** Currency symbol rendered as an inline prefix. Defaults to `"$"`. */
  currencySymbol?: string;
  className?: string;
  style?: ViewProps['style'];
}

export const CurrencyInput = forwardRef<TextInput, CurrencyInputProps>(
  (
    {
      value,
      onChangeValue,
      currencySymbol = '$',
      size = 'default',
      isDisabled,
      isInvalid,
      isReadOnly,
      isRequired,
      isFocused,
      isHovered,
      isFullWidth,
      className,
      style,
      accessibilityLabel,
      accessibilityHint,
      testID,
      onFocus,
      onBlur,
    },
    ref,
  ) => {
    // null = display mode; number = active editing cents.
    // Calculator-style cents accumulation: digits are always extracted from whatever
    // the user types, interpreted as integer cents, then reformatted. The caret is
    // kept at the end at all times so entry behaves like a banking amount pad — new
    // digits append, backspace removes the rightmost digit.
    const [editingCents, setEditingCents] = useState<number | null>(null);

    // Controlled caret, pinned to the end of the formatted string. `undefined` in
    // display mode so we never fight the platform when the field is not being edited.
    const [selection, setSelection] = useState<{ start: number; end: number }>();

    type FocusHandler = NonNullable<TextInputProps['onFocus']>;
    type BlurHandler = NonNullable<TextInputProps['onBlur']>;
    type SelectionChangeHandler = NonNullable<TextInputProps['onSelectionChange']>;

    const displayCents = editingCents !== null ? editingCents : valueToCents(value);
    const amountDisplay = centsToAmountDisplay(displayCents);
    const fullDisplay = `${currencySymbol}${amountDisplay}`;
    const isEmpty = displayCents === 0;

    // Index just past the last character of the formatted string for a given cents.
    // Computed from `cents` (not the render-closure `fullDisplay`) so the caret is
    // correct in the same commit as a value change, even when a reformat adds or
    // removes a grouping comma (e.g. 444.44 → 3,444.44) and changes the length.
    const caretIndexFor = (cents: number) =>
      `${currencySymbol}${centsToAmountDisplay(cents)}`.length;

    const pinCaret = (end: number) => setSelection({ start: end, end });

    const handleFocus: FocusHandler = (e) => {
      // Initialise from the current value so existing amounts are preserved on focus.
      const cents = valueToCents(value);
      setEditingCents(cents);
      pinCaret(caretIndexFor(cents));
      onFocus?.(e);
    };

    const handleBlur: BlurHandler = (e) => {
      setEditingCents(null);
      setSelection(undefined);
      onBlur?.(e);
    };

    const handleChangeText = (text: string) => {
      // Extract only digit characters, interpret the resulting integer as cents.
      // e.g. user typing '1' after '0.00' → digits '0001' → 1 cent → 0.01
      // Backspace on '1.23' → '1.2' → digits '12' → 12 cents → 0.12
      // Digits past the cap are dropped, so extra keystrokes are ignored rather
      // than overflowing into an unsafe integer.
      const digits = text.replace(/\D/g, '').slice(0, MAX_DIGITS);
      const cents = parseInt(digits || '0', 10);
      setEditingCents(cents);
      pinCaret(caretIndexFor(cents));
      onChangeValue?.(cents / 100);
    };

    const handleSelectionChange: SelectionChangeHandler = (e) => {
      // Re-pin the caret to the end ONLY when it drifts to the LEFT of the end while
      // editing — e.g. web hardware-keyboard arrow keys or a click/tap into the middle
      // (native decimal-pad keyboards have no arrow keys, so those platforms rarely
      // trigger this). Value-driven changes already pin the caret via
      // `handleChangeText`/`handleFocus`.
      //
      // Crucially we do NOT touch the caret when it sits at or past the last-rendered
      // end. While typing, the native caret momentarily lands one past our last render
      // (the new value hasn't committed yet); re-pinning there would tug it back to the
      // stale, one-shorter end and yank it one position left — the classic controlled-
      // selection race. Ignoring forward positions sidesteps that entirely, and also
      // prevents a feedback loop (the pin re-emits a selectionChange already at the end).
      if (editingCents === null) {
        return;
      }
      const end = fullDisplay.length;
      const { selection: current } = e.nativeEvent;
      if (current.end < end) {
        pinCaret(end);
      }
    };

    return (
      <InputRootPrimitive
        isDisabled={isDisabled}
        isInvalid={isInvalid}
        isReadOnly={isReadOnly}
        isRequired={isRequired}
        isFocused={isFocused}
        isHovered={isHovered}
        isFullWidth={isFullWidth}
        className={cn(currencyInputRootVariants({ size }), className)}
        style={style}
        {...dataAttributes({ slot: 'currency-input' })}
      >
        <InputFieldPrimitive
          ref={ref}
          value={fullDisplay}
          selection={selection}
          onChangeText={handleChangeText}
          onSelectionChange={handleSelectionChange}
          keyboardType="decimal-pad"
          accessibilityLabel={accessibilityLabel}
          accessibilityHint={accessibilityHint}
          accessibilityValue={{ text: fullDisplay }}
          testID={testID}
          onFocus={handleFocus}
          onBlur={handleBlur}
          className={cn(
            currencyInputFieldVariants({ size }),
            isEmpty ? 'text-content-tertiary' : 'text-content-primary',
          )}
        />
      </InputRootPrimitive>
    );
  },
);

CurrencyInput.displayName = 'CurrencyInput';
