import { render, screen, userEvent } from '../test-utils';
import * as mockedDeviceDetection from './deviceDetection';

import Stepper from './Stepper';

jest.mock('./deviceDetection', () => ({
  isTouchDevice: jest.fn(() => false),
}));

describe('Stepper', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  const generateSteps = (stepsCount: number) =>
    Array.from({ length: stepsCount }, () => ({
      label: Math.random().toString(),
      onClick: jest.fn(),
    }));

  const getSteps = () => screen.getAllByRole('listitem');

  const initialProps = {
    activeStep: 0,
    steps: generateSteps(3),
  };

  const customRender = (overrides = {}) => {
    return render(<Stepper {...initialProps} {...overrides} />);
  };

  describe('progress bar', () => {
    it('renders nothing when no steps are passed in', () => {
      customRender({ steps: [] });
      expect(screen.queryByTestId('progress-bar')).not.toBeInTheDocument();
    });
  });

  describe('steps', () => {
    it('have rendered labels', () => {
      const steps = generateSteps(5);
      customRender({ steps });

      getSteps().forEach((step, index) => {
        expect(step).toHaveTextContent(steps[index].label);
      });
    });

    describe('step interactive style', () => {
      const expectStepIsVisuallyInteractive = (stepIndex: number) => {
        // eslint-disable-next-line jest/valid-expect
        return expect(getSteps()[stepIndex].classList.contains('tw-stepper__step--clickable'));
      };

      it('is not styled as interactive if it is the active step', async () => {
        const steps = [
          { label: '0' },
          { label: '1', onClick: jest.fn() },
          { label: '2', onClick: jest.fn() },
        ];
        customRender({ steps, activeStep: 0 });
        expectStepIsVisuallyInteractive(0).toBe(false);
      });

      it('is not styled as interactive if not the active step and has no click handler', () => {
        const steps = [
          { label: '0' },
          { label: '1', onClick: jest.fn() },
          { label: '2', onClick: jest.fn() },
        ];
        customRender({ steps, activeStep: 1 });
        expectStepIsVisuallyInteractive(0).toBe(false);
        expectStepIsVisuallyInteractive(1).toBe(false);
      });

      it('is styled as interactive if not the active step and has click handler', () => {
        const steps = [
          { label: '0', onClick: jest.fn() },
          { label: '1', onClick: jest.fn() },
          { label: '2', onClick: jest.fn() },
        ];
        customRender({ steps, activeStep: 2 });
        expectStepIsVisuallyInteractive(0).toBe(true);
        expectStepIsVisuallyInteractive(1).toBe(true);
        expectStepIsVisuallyInteractive(2).toBe(false);
      });
    });

    describe('step interactivity', () => {
      const getStepChild = (stepIndex: number) => getSteps()[stepIndex].children[0];

      it('is not interactive if it is the active step', async () => {
        const steps = [
          { label: '0', onClick: jest.fn() },
          { label: '1', onClick: jest.fn() },
          { label: '2', onClick: jest.fn() },
        ];
        customRender({ steps, activeStep: 0 });
        await userEvent.click(getStepChild(0));
        expect(steps[0].onClick).not.toHaveBeenCalled();
      });

      it('is not interactive if not the active step but has no click handler', async () => {
        const steps = [
          { label: '0' },
          { label: '1', onClick: jest.fn() },
          { label: '2', onClick: jest.fn() },
        ];
        customRender({ steps, activeStep: 1 });
        await userEvent.click(getStepChild(0));
        expect(steps[1].onClick).not.toHaveBeenCalled();
        await userEvent.click(getStepChild(1));
        expect(steps[1].onClick).not.toHaveBeenCalled();
      });

      it('is interactive if not the active step and has click handler', async () => {
        const steps = [
          { label: '0', onClick: jest.fn() },
          { label: '1', onClick: jest.fn() },
          { label: '2', onClick: jest.fn() },
        ];
        customRender({ steps, activeStep: 2 });
        await userEvent.click(getStepChild(1));
        expect(steps[1].onClick).toHaveBeenCalledTimes(1);
      });
    });

    it('are not clickable when active', async () => {
      const clickedOnFirstStep = jest.fn();
      const clickedOnSecondStep = jest.fn();
      const initialProps = {
        steps: [
          { label: 'one', onClick: clickedOnFirstStep },
          { label: 'two', onClick: clickedOnSecondStep },
        ],
        activeStep: 0,
      };
      const { rerender } = customRender(initialProps);

      const clickOnStep = async (stepIndex: number) => {
        const step = screen.getByText(initialProps.steps[stepIndex].label).parentElement;
        if (step) {
          return userEvent.click(step);
        }
      };

      await clickOnStep(0);
      expect(clickedOnFirstStep).not.toHaveBeenCalled();

      rerender(<Stepper {...initialProps} activeStep={1} />);
      await clickOnStep(0);
      expect(clickedOnFirstStep).toHaveBeenCalledTimes(1);

      await clickOnStep(1);
      expect(clickedOnSecondStep).not.toHaveBeenCalled();
    });

    it('are active when they are the currently active step', () => {
      const { rerender } = customRender({
        steps: Array(4)
          .fill('')
          .map((_, i) => ({ label: i.toString() })),
        activeStep: 1,
      });

      const stepActive = (index: number) =>
        getSteps()[index].classList.contains('tw-stepper__step--active');

      expect(stepActive(0)).toBe(false);
      expect(stepActive(1)).toBe(true);
      expect(stepActive(2)).toBe(false);
      expect(stepActive(3)).toBe(false);

      rerender(<Stepper {...initialProps} activeStep={2} />);

      expect(stepActive(1)).toBe(false);
      expect(stepActive(2)).toBe(true);
    });

    it('are aria-current=step when active', () => {
      const { rerender } = customRender({ steps: Array(4).fill({ label: '' }), activeStep: 1 });

      const stepCurrent = (index: number) =>
        getSteps()[index].getAttribute('aria-current') === 'step';

      expect(stepCurrent(0)).toBe(false);
      expect(stepCurrent(1)).toBe(true);
      expect(stepCurrent(2)).toBe(false);
      expect(stepCurrent(3)).toBe(false);

      rerender(<Stepper {...initialProps} activeStep={2} />);

      expect(stepCurrent(1)).toBe(false);
      expect(stepCurrent(2)).toBe(true);
    });
  });

  describe('hover labels', () => {
    it('will be rendered when provided', async () => {
      const hoverLabel = 'hover label';
      customRender({
        steps: [{ hoverLabel, label: 'label' }, { label: 'label 2' }],
      });
      expect(await screen.findByText(hoverLabel)).toBeInTheDocument();
    });

    it('renders jsx', async () => {
      customRender({
        steps: [
          {
            hoverLabel: (
              <>
                <span>hover label 1</span>
                <p>hover label 2</p>
              </>
            ),
            label: 'one',
          },
        ],
      });

      expect(await screen.findByText('hover label 1')).toBeInTheDocument();
      expect(await screen.findByText('hover label 2')).toBeInTheDocument();
    });

    it('will not be rendered if the user is on a touch device', () => {
      jest.spyOn(mockedDeviceDetection, 'isTouchDevice').mockImplementation(() => true);

      customRender({
        steps: [{ hoverLabel: 'hover label', label: 'label' }, { label: 'label 2' }],
      });

      expect(screen.queryByText('hover label')).not.toBeInTheDocument();
    });
  });
});
