import { fireEvent, render } from '@testing-library/react';
import React, { FormEvent, useEffect, useState } from 'react';
import { act } from 'react-dom/test-utils';
import { describe, expect, it, vi } from 'vitest';
import { useDebounce } from './useDebounce';

const mockIntialValue = 'test-value';

const TestWrapper: React.FC<{ value: string }> = ({ value }) => {
  const [debouncedValue, setVal, val] = useDebounce<string>(value, 2000);
  const [state, setState] = useState<string>('');
  useEffect(() => {
    setState(debouncedValue);
  }, [debouncedValue]);

  const onChangeHandler = (event: FormEvent<HTMLInputElement>): void => {
    setVal(event.currentTarget.value);
  };

  return (
    <div>
      <p id="value">{String(val)}</p>
      <p id="debounced-value">{String(state)}</p>
      <input value={val} onChange={onChangeHandler} />
    </div>
  );
};

describe('useDebounce', () => {
  it(`both 'val' and 'debouncedValue' are set to the initial value`, () => {
    const { container } = render(<TestWrapper value={mockIntialValue} />);

    const currentValue = container.querySelector('#value');
    const debounced = container.querySelector('#debounced-value');

    expect(currentValue?.textContent).toBe(mockIntialValue);
    expect(debounced?.textContent).toBe(mockIntialValue);
  });

  it(`'setVal()' updates both 'val' and 'debouncedValue'`, () => {
    const mockNewValue = 'test-new-value';

    vi.useFakeTimers();

    const { container } = render(<TestWrapper value={mockIntialValue} />);

    let currentValue = container.querySelector('#value');
    let debounced = container.querySelector('#debounced-value');

    expect(currentValue?.textContent).toBe(mockIntialValue);
    expect(debounced?.textContent).toBe(mockIntialValue);

    const input = container.querySelector('input') as HTMLInputElement;

    act(() => {
      fireEvent.change(input, {
        target: { value: mockNewValue },
      });
    });

    // Check that val is updated immediately
    currentValue = container.querySelector('#value');
    expect(currentValue?.textContent).toBe(mockNewValue);

    // Check that debounced value is still the old value
    debounced = container.querySelector('#debounced-value');
    expect(debounced?.textContent).toBe(mockIntialValue);

    // Fast-forward time to trigger debounce
    act(() => {
      vi.runAllTimers();
    });

    // Now check that debounced value has updated
    debounced = container.querySelector('#debounced-value');
    expect(debounced?.textContent).toBe(mockNewValue);

    vi.useRealTimers();
  });
});
