import { fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { describe, expect, it, vi } from 'vitest';
import { noop } from '../../../../../helpers/utils';
import { CreateSelectRendererConfig } from '../renderers.model';
import { createSelectRenderer } from './createSelectRenderer';

interface RendererWrapperProps extends CreateSelectRendererConfig {
  currentValue?: unknown;
  error?: string | undefined;
  onValueChange?: (value: unknown) => void;
  disabled?: boolean;
}

const RendererWrapper: React.FC<RendererWrapperProps> = ({
  id,
  name,
  placeholder,
  options,
  transform,
  currentValue,
  error,
  onValueChange = noop,
  disabled = false,
}) => {
  return (
    <>
      {createSelectRenderer({
        id,
        name,
        placeholder,
        options,
        transform,
      })(currentValue, error, onValueChange, disabled)}
    </>
  );
};

describe('createSelectRenderer', () => {
  it('renders the component without crashing', () => {
    const { container } = render(<RendererWrapper />);
    expect(container).toBeTruthy();
  });

  it(`sets 'select' value to current value`, () => {
    const mockOptions = [{ value: 'test-value', label: 'Test Value' }];
    const { container } = render(
      <RendererWrapper
        currentValue={mockOptions[0].value}
        options={mockOptions}
      />,
    );

    const input = container.querySelector(
      '[role="combobox"]',
    ) as HTMLInputElement;

    expect(input.value).toBe(mockOptions[0].label);
  });

  it(`emits 'onValueChange' with the new value when a new option has been selected`, async () => {
    const onValueChangeSpy = vi.fn();
    const mockValueUpdated = 'updated-test-value';
    const mockOptions = [{ value: mockValueUpdated, label: 'Updated Value' }];
    const { container } = render(
      <RendererWrapper
        onValueChange={onValueChangeSpy}
        options={mockOptions}
      />,
    );

    const input = container.querySelector('[role="combobox"]') as HTMLElement;

    act(() => {
      fireEvent.mouseDown(input);
    });

    const option = await waitFor(() => {
      const options = document.querySelectorAll('[role="option"]');
      const el = Array.from(options).find(
        (opt) => opt.textContent === 'Updated Value',
      ) as HTMLElement;
      expect(el).toBeInTheDocument();
      return el;
    });

    act(() => {
      fireEvent.click(option);
    });

    expect(onValueChangeSpy).toHaveBeenCalledWith(mockValueUpdated);
  });

  it(`formats the value when a 'transformer' is set`, async () => {
    const onValueChangeSpy = vi.fn();
    const mockStringifiedNumber = '5';
    const stringToNumberTransformer = (value: string): number => Number(value);
    const mockOptions = [{ value: mockStringifiedNumber, label: 'Five' }];

    const { container } = render(
      <RendererWrapper
        onValueChange={onValueChangeSpy}
        transform={stringToNumberTransformer}
        options={mockOptions}
      />,
    );

    const input = container.querySelector('[role="combobox"]') as HTMLElement;

    act(() => {
      fireEvent.mouseDown(input);
    });

    const option = await waitFor(() => {
      const options = document.querySelectorAll('[role="option"]');
      const el = Array.from(options).find(
        (opt) => opt.textContent === 'Five',
      ) as HTMLElement;
      expect(el).toBeInTheDocument();
      return el;
    });

    act(() => {
      fireEvent.click(option);
    });

    expect(onValueChangeSpy).toHaveBeenCalledWith(
      Number(mockStringifiedNumber),
    );
  });
});
