import React from 'react';
import { userEvent, render, screen, waitFor, fireEvent } from '../test-utils';
import { Input } from '../inputs/Input';
import WithDisplayFormat from '.';

const user = userEvent;

const setup = (props) =>
  render(
    <WithDisplayFormat
      {...props}
      render={(renderProps) => <Input {...renderProps} name="input" />}
    />,
  );

describe('InputWithTextFormat (React Testing Library)', () => {
  const initialProps = {
    placeholder: 'a placeholder',
    displayPattern: '**-**',
    onChange: jest.fn(),
    onFocus: jest.fn(),
    onBlur: jest.fn(),
  };

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

  [
    { value: '123', expectedValue: '12-3', displayPattern: '**-**-**' },
    { value: '1234', expectedValue: '12-34', displayPattern: '**-**-**' },
    { value: '1234', expectedValue: '+(12) 34', displayPattern: '+(**) *' },
  ].forEach(({ value, expectedValue, displayPattern }) => {
    it(`returns ${expectedValue} for ${value} if displayPattern is ${displayPattern}`, async () => {
      setup({ ...initialProps, displayPattern });

      await user.tab();
      await user.keyboard(value);

      expect(screen.getByRole('textbox')).toHaveValue(expectedValue);
      await waitFor(() => expect(initialProps.onChange).toHaveBeenCalledWith(value));

      await user.tab();
      expect(initialProps.onBlur).toHaveBeenCalledWith(value);
    });
  });

  ['Meta', 'Control'].forEach((modifier) => {
    it(`should support undo and redo with "${modifier}" key`, async () => {
      setup({ ...initialProps, displayPattern: '***' });

      const modifierOpening = `{${modifier}>}`;
      const modifierClosing = `{/${modifier}}`;
      const input = screen.getByRole('textbox');

      await user.type(input, '123');
      expect(input).toHaveValue('123');
      await user.type(input, `${modifierOpening}z${modifierClosing}`);
      expect(input).toHaveValue('12');
      await user.type(input, `${modifierOpening}z${modifierClosing}`);
      expect(input).toHaveValue('1');
      await user.type(input, `${modifierOpening}z${modifierClosing}`);
      expect(input).toHaveValue('');
      await user.type(input, `{Shift>}${modifierOpening}z${modifierClosing}{/Shift}`);
      expect(input).toHaveValue('1');
      await user.type(input, `{Shift>}${modifierOpening}z${modifierClosing}{/Shift}`);
      expect(input).toHaveValue('12');
      await user.type(input, `{Shift>}${modifierOpening}z${modifierClosing}{/Shift}`);
      expect(input).toHaveValue('123');
    });
  });

  it(`can delete user-entered characters`, async () => {
    setup(initialProps);
    const input = screen.getByRole('textbox');
    await user.type(input, '12345');
    await user.type(input, '{backspace}{backspace}');
    expect(input).toHaveValue('12-3');
  });

  it(`can select and delete multiple user-entered characters`, async () => {
    setup(initialProps);
    const input = screen.getByRole('textbox');
    await user.type(input, '12345');
    expect(input).toHaveValue('12-345');

    await user.type(input, '{backspace}', {
      initialSelectionStart: 4,
      initialSelectionEnd: 6,
    });
    expect(input).toHaveValue('12-3');
  });

  it(`can select and delete multiple user-entered characters, including separators`, async () => {
    setup(initialProps);
    const input = screen.getByRole('textbox');
    await user.type(input, '12345');
    expect(input).toHaveValue('12-345');

    await user.type(input, '{backspace}', {
      initialSelectionStart: 1,
      initialSelectionEnd: 4,
    });
    expect(input).toHaveValue('14-5');
  });

  it(`has default behaviour for multiple deletion`, async () => {
    setup(initialProps);
    const input = screen.getByRole('textbox');
    await user.type(input, '12345');
    expect(input).toHaveValue('12-345');

    await user.type(input, '{backspace}', {
      initialSelectionStart: 4,
      initialSelectionEnd: 6,
    });
    expect(input).toHaveValue('12-3');
  });

  it(`performs an extra delete if previous char is a separator`, async () => {
    setup(initialProps);
    const input = screen.getByRole('textbox');
    await user.type(input, '123');
    expect(input).toHaveValue('12-3');

    await user.type(input, '{backspace}');
    expect(input).toHaveValue('12');
  });

  it(`cusor preserve position when entered a char in the middle`, async () => {
    setup(initialProps);
    const input = screen.getByRole('textbox');
    await user.type(input, '123');
    expect(input).toHaveValue('12-3');

    await user.keyboard('{arrowleft}{arrowleft}{arrowleft}');
    await user.keyboard('@');

    expect(input).toHaveValue('1@-23');

    await user.keyboard('x');
    expect(input).toHaveValue('1@-x23');

    await user.keyboard('{arrowright}{backspace}');

    expect(input).toHaveValue('1@-x3');
  });

  it(`supports pasting`, async () => {
    setup(initialProps);
    const input = screen.getByRole('textbox');
    await user.type(input, '123');
    expect(input).toHaveValue('12-3');

    await user.type(input, '{arrowLeft}{arrowLeft}{arrowLeft}');
    await user.paste('@@');

    expect(input).toHaveValue('1@-@23');
  });

  it(`supports cutting`, async () => {
    setup(initialProps);
    const input = screen.getByRole('textbox');
    await user.type(input, '12345');
    expect(input).toHaveValue('12-345');

    // this is a workaround to set selection without entering
    // any character (pressing Shift doesn't reset it)
    await user.type(input, '{Shift}', {
      initialSelectionStart: 1,
      initialSelectionEnd: 4,
    });
    await user.cut();

    expect(input).toHaveValue('14-5');
  });

  it('calls onFocus when input is focused', () => {
    const onFocus = jest.fn();
    setup({ ...initialProps, onFocus });

    const input = screen.getByRole('textbox');
    fireEvent.focus(input);

    expect(initialProps.onBlur).not.toHaveBeenCalled();
    expect(initialProps.onChange).not.toHaveBeenCalled();
    expect(onFocus).toHaveBeenCalled();
  });

  it('calls onBlur when input is blurred', () => {
    setup(initialProps);

    const input = screen.getByRole('textbox');
    fireEvent.blur(input);

    expect(initialProps.onFocus).not.toHaveBeenCalled();
    expect(initialProps.onChange).not.toHaveBeenCalled();
    expect(initialProps.onBlur).toHaveBeenCalled();
  });
});
