import React from 'react';

import { render, screen, userEvent, waitFor } from '../test-utils';

import SegmentedControl, { SegmentedControlProps } from './SegmentedControl';

const defaultSegments = [
  {
    id: '1',
    value: 'accounting',
    label: 'Accounting',
  },
  {
    id: '2',
    value: 'payroll',
    label: 'Payroll',
  },
  {
    id: '3',
    value: 'reporting',
    label: 'Reporting',
  },
];

const defaultSegmentsWithControls = defaultSegments.map((segment, index) => ({
  ...segment,
  controls: `aControlId${index}`,
}));

const onChange = jest.fn();

const defaultProps: SegmentedControlProps = {
  name: 'segmentedControl',
  value: defaultSegments[0].value,
  mode: 'input',
  segments: defaultSegments,
  onChange,
};

const renderSegmentedControl = (overrides: Partial<SegmentedControlProps> = {}) => {
  return render(<SegmentedControl {...defaultProps} {...(overrides as SegmentedControlProps)} />);
};

describe('SegmentedControl', () => {
  it('dynamically adds inline style props for animated segment overlay', () => {
    renderSegmentedControl();

    const segmentedControls = screen.getByTestId('segmented-control');

    expect(segmentedControls).toHaveStyle(
      '--segment-highlight-width: 0px; --segment-highlight-x: 0px;',
    );
  });

  it('default value is selected', () => {
    renderSegmentedControl({ mode: 'view', segments: defaultSegmentsWithControls });

    const accountingTab = screen.getByRole('tab', { name: 'Accounting' });
    expect(accountingTab).toHaveAttribute('aria-selected', 'true');
  });

  it('lets the user pick through the segments when it is set to input', async () => {
    renderSegmentedControl();

    const payroll = screen.getByRole('radio', { name: 'Payroll' });
    await userEvent.click(payroll);

    expect(onChange).toHaveBeenCalledWith('payroll');

    const reporting = screen.getByRole('radio', { name: 'Reporting' });
    await userEvent.click(reporting);

    expect(onChange).toHaveBeenCalledWith('reporting');
  });

  it('lets the user pick through the segments when it is set to view', async () => {
    renderSegmentedControl({ mode: 'view', segments: defaultSegmentsWithControls });

    const payroll = screen.getByRole('tab', { name: 'Payroll' });
    await userEvent.click(payroll);

    expect(onChange).toHaveBeenCalledWith('payroll');

    const reporting = screen.getByRole('tab', { name: 'Reporting' });
    await userEvent.click(reporting);

    expect(onChange).toHaveBeenCalledWith('reporting');
  });

  it('does not call onChange on mount', async () => {
    let onChangeCallCount = 0;

    const ParentComponent = () => {
      const [_, simulateRerender] = React.useState({});

      // new function is created on every render
      const onChange = () => {
        onChangeCallCount += 1;
        simulateRerender({});
      };

      return <SegmentedControl {...defaultProps} onChange={onChange} />;
    };

    render(<ParentComponent />);

    await waitFor(() => {
      expect(onChangeCallCount).toBe(0);
    });
  });

  it('does not repeatedly call the onChange when the provided onChange prop changes on every render of its parent component', async () => {
    let onChangeCallCount = 0;

    const ParentComponent = () => {
      const [_, simulateRerender] = React.useState({});

      // a new onChange function is created on every render
      const onChange = () => {
        onChangeCallCount += 1;
        simulateRerender({});
      };

      return <SegmentedControl {...defaultProps} onChange={onChange} />;
    };

    const { rerender } = render(<ParentComponent />);

    rerender(<ParentComponent />);

    await waitFor(() => {
      expect(onChangeCallCount).toBe(0);
    });
  });

  it('updates the selected segment when the selectedValue prop changes', async () => {
    const { rerender } = render(<SegmentedControl {...defaultProps} />);

    const payroll = screen.getByRole('radio', { name: 'Payroll' });
    await userEvent.click(payroll);

    expect(onChange).toHaveBeenCalledWith('payroll');

    rerender(<SegmentedControl {...defaultProps} value="reporting" />);

    const reporting = screen.getByRole('radio', { name: 'Reporting' });
    expect(reporting).toBeChecked();
  });

  it('updates the options when the segments prop changes', async () => {
    const { rerender } = render(<SegmentedControl {...defaultProps} />);

    const newSegments = [
      {
        id: '1',
        value: 'payroll',
        label: 'Payroll',
      },
      {
        id: '3',
        value: 'anotherOne',
        label: 'Another One',
      },
    ];

    rerender(<SegmentedControl {...defaultProps} segments={newSegments} />);

    const anotherOne = screen.getByRole('radio', { name: 'Another One' });
    await userEvent.click(anotherOne);

    expect(onChange).toHaveBeenCalledWith('anotherOne');
  });

  it('throws error if user tries to add too many segments', () => {
    expect(() => {
      renderSegmentedControl({
        mode: 'input',
        segments: [
          ...defaultSegments,
          {
            id: '4',
            value: 'anotherOne',
            label: 'Another One',
          },
        ],
      });
    }).toThrow(
      'SegmentedControl only supports up to 3 segments. Please refer to: https://docs.wise.design/components/segmented-control',
    );
  });

  describe('animation behavior', () => {
    it('skips animation on initial render', () => {
      renderSegmentedControl();

      // On initial render, the component should render without triggering animation
      // The isMountedRef ensures animation is skipped on first render
      const segmentedControl = screen.getByTestId('segmented-control');

      // Verify the component renders successfully
      expect(segmentedControl).toBeInTheDocument();
    });

    it('enables animation after value change', async () => {
      renderSegmentedControl();

      // Change value by clicking on a different segment
      const payroll = screen.getByRole('radio', { name: 'Payroll' });
      await userEvent.click(payroll);

      // After a value change, the component should have completed at least one update cycle
      // verifying that the isMountedRef tracking works correctly
      expect(onChange).toHaveBeenCalledWith('payroll');
    });
  });
});
