import { act, renderHook } from '@testing-library/react-native';
import { useInput } from './use-input';
import {
  TextInput,
  type NativeSyntheticEvent,
  type TextInputSelectionChangeEventData,
} from 'react-native';

describe('useInput', () => {
  const defaultProps = {
    maxLength: 4,
    onChange: jest.fn(),
  };

  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('Initialization', () => {
    test('initializes with default values', () => {
      const { result } = renderHook(() => useInput({ maxLength: 4 }));

      expect(result.current.value).toBe('');
      expect(result.current.isFocused).toBe(false);
      expect(result.current.contextValue.slots).toHaveLength(4);
    });

    test('initializes with default value', () => {
      const { result } = renderHook(() =>
        useInput({ maxLength: 4, defaultValue: '123' })
      );

      expect(result.current.value).toBe('123');
      expect(result.current.contextValue.slots).toHaveLength(4);
    });
  });

  describe('Value Changes', () => {
    test('updates value on valid input', () => {
      const onChange = jest.fn();
      const { result } = renderHook(() => useInput({ maxLength: 4, onChange }));

      act(() => {
        result.current.handlers.onChangeText('123');
      });

      expect(result.current.value).toBe('123');
      expect(onChange).toHaveBeenCalledWith('123');
    });

    test('limits input to maxLength', () => {
      const onChange = jest.fn();
      const { result } = renderHook(() => useInput({ maxLength: 4, onChange }));

      act(() => {
        result.current.handlers.onChangeText('12345');
      });

      expect(result.current.value).toBe('1234');
      expect(onChange).toHaveBeenCalledWith('1234');
    });

    test('validates input against pattern (string)', () => {
      const onChange = jest.fn();
      const { result } = renderHook(() =>
        useInput({ maxLength: 4, pattern: '[0-9]', onChange })
      );

      act(() => {
        result.current.handlers.onChangeText('abc');
      });

      expect(result.current.value).toBe('');
      expect(onChange).not.toHaveBeenCalled();
    });

    test('validates input against pattern (RegExp string)', () => {
      const onChange = jest.fn();
      const { result } = renderHook(() =>
        useInput({ maxLength: 4, pattern: '^[0-9]+$', onChange })
      );

      act(() => {
        result.current.handlers.onChangeText('123');
      });

      expect(result.current.value).toBe('123');
      expect(onChange).toHaveBeenCalledWith('123');
    });

    test('calls onComplete when maxLength is reached', () => {
      const onComplete = jest.fn();
      const { result } = renderHook(() =>
        useInput({ maxLength: 4, onComplete })
      );

      act(() => {
        result.current.handlers.onChangeText('1234');
      });

      expect(onComplete).toHaveBeenCalledWith('1234');
    });

    test('applies pasteTransformer on paste operations', () => {
      const onChange = jest.fn();
      const pasteTransformer = jest.fn((text) => text.replace(/\D/g, ''));
      const { result } = renderHook(() =>
        useInput({ maxLength: 4, onChange, pasteTransformer })
      );

      // Simulate pasting "Code: 1234"
      act(() => {
        result.current.handlers.onChangeText('Code: 1234');
      });

      expect(pasteTransformer).toHaveBeenCalledWith('Code: 1234');
      expect(result.current.value).toBe('1234');
      expect(onChange).toHaveBeenCalledWith('1234');
    });

    test('onSelectionChange resets cursor when it moves from end', () => {
      const { result } = renderHook(() => useInput({ maxLength: 4 }));

      act(() => {
        result.current.handlers.onChangeText('12');
      });

      // Simulate cursor moving to position 0 (arrow key navigation)
      act(() => {
        result.current.handlers.onSelectionChange({
          nativeEvent: { selection: { start: 0, end: 0 } },
        } as NativeSyntheticEvent<TextInputSelectionChangeEventData>);
      });

      // After re-render, value is unchanged (no data was lost)
      expect(result.current.value).toBe('12');
    });

    test('does not apply pasteTransformer on normal typing', () => {
      const onChange = jest.fn();
      const pasteTransformer = jest.fn((text) => text.replace(/\D/g, ''));
      const { result } = renderHook(() =>
        useInput({ maxLength: 4, onChange, pasteTransformer })
      );

      // Simulate typing "1"
      act(() => {
        result.current.handlers.onChangeText('1');
      });

      expect(pasteTransformer).not.toHaveBeenCalled();
      expect(result.current.value).toBe('1');
      expect(onChange).toHaveBeenCalledWith('1');
    });
  });

  describe('Focus Management', () => {
    test('handles focus state', () => {
      const { result } = renderHook(() => useInput({ maxLength: 4 }));

      act(() => {
        result.current.handlers.onFocus();
      });

      expect(result.current.isFocused).toBe(true);

      act(() => {
        result.current.handlers.onBlur();
      });

      expect(result.current.isFocused).toBe(false);
    });
  });

  describe('Actions', () => {
    test('clear action resets value', () => {
      const { result } = renderHook(() =>
        useInput({ maxLength: 4, defaultValue: '123' })
      );

      act(() => {
        result.current.actions.clear();
      });

      expect(result.current.value).toBe('');
    });

    describe('focusSlot', () => {
      test('truncates value to the given index', () => {
        const onChange = jest.fn();
        const { result } = renderHook(() =>
          useInput({ maxLength: 6, defaultValue: '123456', onChange })
        );

        act(() => {
          result.current.actions.focusSlot(3);
        });

        expect(result.current.value).toBe('123');
        expect(onChange).toHaveBeenCalledWith('123');
      });

      test('focusSlot(0) clears the value entirely', () => {
        const onChange = jest.fn();
        const { result } = renderHook(() =>
          useInput({ maxLength: 6, defaultValue: '123456', onChange })
        );

        act(() => {
          result.current.actions.focusSlot(0);
        });

        expect(result.current.value).toBe('');
        expect(onChange).toHaveBeenCalledWith('');
      });

      test('clamps index above maxLength to maxLength', () => {
        const onChange = jest.fn();
        const { result } = renderHook(() =>
          useInput({ maxLength: 4, defaultValue: '1234', onChange })
        );

        act(() => {
          result.current.actions.focusSlot(10);
        });

        expect(result.current.value).toBe('1234');
        expect(onChange).toHaveBeenCalledWith('1234');
      });

      test('clamps negative index to 0', () => {
        const onChange = jest.fn();
        const { result } = renderHook(() =>
          useInput({ maxLength: 4, defaultValue: '1234', onChange })
        );

        act(() => {
          result.current.actions.focusSlot(-1);
        });

        expect(result.current.value).toBe('');
        expect(onChange).toHaveBeenCalledWith('');
      });

      test('focuses the input', () => {
        const mockFocus = jest.fn();
        const { result } = renderHook(() =>
          useInput({ maxLength: 4, defaultValue: '1234' })
        );

        (result.current.inputRef as React.MutableRefObject<TextInput>).current =
          {
            focus: mockFocus,
            clear: jest.fn(),
          } as unknown as TextInput;

        act(() => {
          result.current.actions.focusSlot(2);
        });

        expect(mockFocus).toHaveBeenCalled();
      });

      test('marks correct slot as active after focusSlot', () => {
        const { result } = renderHook(() =>
          useInput({ maxLength: 6, defaultValue: '123456' })
        );

        act(() => {
          result.current.actions.focusSlot(3);
          result.current.handlers.onFocus();
        });

        const slots = result.current.contextValue.slots;
        expect(slots[3]?.isActive).toBe(true);
        expect(slots[2]?.isActive).toBe(false);
        expect(slots[4]?.isActive).toBe(false);
      });
    });
  });

  describe('Slots', () => {
    test('generates correct slots with placeholder', () => {
      const { result } = renderHook(() =>
        useInput({ maxLength: 4, placeholder: '0000' })
      );

      const firstSlot = result.current.contextValue.slots[0];
      expect(firstSlot?.placeholderChar).toBe('0');
      expect(firstSlot?.char).toBe(null);
    });

    test('generates correct slots with value', () => {
      const { result } = renderHook(() =>
        useInput({ maxLength: 4, defaultValue: '12' })
      );

      const slots = result.current.contextValue.slots;
      expect(slots[0]?.char).toBe('1');
      expect(slots[1]?.char).toBe('2');
      expect(slots[2]?.char).toBe(null);
    });

    test('marks active slot correctly', () => {
      const { result } = renderHook(() =>
        useInput({ maxLength: 4, defaultValue: '12' })
      );

      act(() => {
        result.current.handlers.onFocus();
      });

      const activeSlot = result.current.contextValue.slots[2];
      expect(activeSlot?.isActive).toBe(true);
      expect(activeSlot?.hasFakeCaret).toBe(true);
    });
  });

  describe('pattern validation', () => {
    test('handles undefined pattern', () => {
      const { result } = renderHook(() =>
        useInput({
          ...defaultProps,
          pattern: undefined,
        })
      );

      act(() => {
        result.current.handlers.onChangeText('123');
      });

      expect(result.current.value).toBe('123');
    });

    test('handles string pattern', () => {
      const { result } = renderHook(() =>
        useInput({
          ...defaultProps,
          pattern: '^[0-9]+$',
        })
      );

      // Test valid input
      act(() => {
        result.current.handlers.onChangeText('123');
      });
      expect(result.current.value).toBe('123');

      // Test invalid input
      act(() => {
        result.current.handlers.onChangeText('abc');
      });
      expect(result.current.value).toBe('123'); // Value should not change
    });

    test('handles RegExp pattern', () => {
      const { result } = renderHook(() =>
        useInput({
          ...defaultProps,
          pattern: /^[0-9]+$/,
        })
      );

      // Test valid input
      act(() => {
        result.current.handlers.onChangeText('123');
      });
      expect(result.current.value).toBe('123');

      // Test invalid input
      act(() => {
        result.current.handlers.onChangeText('abc');
      });
      expect(result.current.value).toBe('123'); // Value should not change
    });
  });

  test('focus action focuses the input', () => {
    const mockFocus = jest.fn();
    const { result } = renderHook(() =>
      useInput({
        ...defaultProps,
      })
    );

    // Mock the inputRef.current
    (result.current.inputRef as React.MutableRefObject<TextInput>).current = {
      focus: mockFocus,
      clear: jest.fn(),
    } as unknown as TextInput;

    act(() => {
      result.current.actions.focus();
    });

    expect(mockFocus).toHaveBeenCalled();
  });
});
