import { render } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { describe, expect, test } from 'vitest';
import { UiRadioGroup, UiRadioGroupItem } from '../index';
import { h } from 'vue';

// Values for the radio buttons
const firstItemValue = 'first';
const secondItemValue = 'second';
const thirdItemValue = 'third';

// Labels for accessible names (via label element with for attribute)
const firstItemLabel = 'First Option';
const secondItemLabel = 'Second Option';
const thirdItemLabel = 'Third Option';

const twoItemsContent = () => [
  h('div', [
    h(UiRadioGroupItem, {
      id: 'rg-1',
      value: firstItemValue,
    }),
    h('label', { for: 'rg-1' }, firstItemLabel),
  ]),
  h('div', [
    h(UiRadioGroupItem, {
      id: 'rg-2',
      value: secondItemValue,
    }),
    h('label', { for: 'rg-2' }, secondItemLabel),
  ]),
];

const threeItemsContent = () => [
  h('div', [
    h(UiRadioGroupItem, {
      id: 'rg-1',
      value: firstItemValue,
    }),
    h('label', { for: 'rg-1' }, firstItemLabel),
  ]),
  h('div', [
    h(UiRadioGroupItem, {
      id: 'rg-2',
      value: secondItemValue,
    }),
    h('label', { for: 'rg-2' }, secondItemLabel),
  ]),
  h('div', [
    h(UiRadioGroupItem, {
      id: 'rg-3',
      value: thirdItemValue,
    }),
    h('label', { for: 'rg-3' }, thirdItemLabel),
  ]),
];

const contentWithDisabledItem = () => [
  h('div', [
    h(UiRadioGroupItem, {
      id: 'rg-1',
      value: firstItemValue,
    }),
    h('label', { for: 'rg-1' }, firstItemLabel),
  ]),
  h('div', [
    h(UiRadioGroupItem, {
      id: 'rg-2',
      value: secondItemValue,
      disabled: true,
    }),
    h('label', { for: 'rg-2' }, secondItemLabel),
  ]),
  h('div', [
    h(UiRadioGroupItem, {
      id: 'rg-3',
      value: thirdItemValue,
    }),
    h('label', { for: 'rg-3' }, thirdItemLabel),
  ]),
];

describe('UiRadioGroup', () => {
  describe('selection behavior', () => {
    test('renders with initial selected value', () => {
      const { getByLabelText } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: secondItemValue,
        },
        slots: {
          default: twoItemsContent(),
        },
      });

      expect(getByLabelText(secondItemLabel)).toBeChecked();
      expect(getByLabelText(firstItemLabel)).not.toBeChecked();
    });

    test('emits update:modelValue when selection changes', async () => {
      const { getByLabelText, emitted } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: firstItemValue,
        },
        slots: {
          default: twoItemsContent(),
        },
      });

      await userEvent.click(getByLabelText(secondItemLabel));

      expect(emitted('update:modelValue')).toHaveLength(1);
      expect(emitted('update:modelValue')[0][0]).toBe(secondItemValue);
    });

    test('allows only one item to be selected at a time', async () => {
      const { getByLabelText } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: firstItemValue,
        },
        slots: {
          default: threeItemsContent(),
        },
      });

      // Initially first is selected
      expect(getByLabelText(firstItemLabel)).toBeChecked();

      // Click second
      await userEvent.click(getByLabelText(secondItemLabel));
      expect(getByLabelText(secondItemLabel)).toBeChecked();
      expect(getByLabelText(firstItemLabel)).not.toBeChecked();

      // Click third
      await userEvent.click(getByLabelText(thirdItemLabel));
      expect(getByLabelText(thirdItemLabel)).toBeChecked();
      expect(getByLabelText(secondItemLabel)).not.toBeChecked();
    });
  });

  describe('keyboard navigation', () => {
    test('arrow down moves focus to next item', async () => {
      const { getByLabelText } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: firstItemValue,
        },
        slots: {
          default: threeItemsContent(),
        },
      });

      const firstRadio = getByLabelText(firstItemLabel);
      firstRadio.focus();

      await userEvent.keyboard('{ArrowDown}');

      expect(getByLabelText(secondItemLabel)).toHaveFocus();
    });

    test('arrow up moves focus to previous item', async () => {
      const { getByLabelText } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: secondItemValue,
        },
        slots: {
          default: threeItemsContent(),
        },
      });

      const secondRadio = getByLabelText(secondItemLabel);
      secondRadio.focus();

      await userEvent.keyboard('{ArrowUp}');

      expect(getByLabelText(firstItemLabel)).toHaveFocus();
    });

    test('loops from last to first when loop is enabled (default)', async () => {
      const { getByLabelText } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: thirdItemValue,
        },
        slots: {
          default: threeItemsContent(),
        },
      });

      const thirdRadio = getByLabelText(thirdItemLabel);
      thirdRadio.focus();

      await userEvent.keyboard('{ArrowDown}');

      expect(getByLabelText(firstItemLabel)).toHaveFocus();
    });

    test('does not loop when loop is disabled', async () => {
      const { getByLabelText } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: thirdItemValue,
          loop: false,
        },
        slots: {
          default: threeItemsContent(),
        },
      });

      const thirdRadio = getByLabelText(thirdItemLabel);
      thirdRadio.focus();

      await userEvent.keyboard('{ArrowDown}');

      // Should stay on third item
      expect(getByLabelText(thirdItemLabel)).toHaveFocus();
    });

    test('skips disabled items during navigation', async () => {
      const { getByLabelText } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: firstItemValue,
        },
        slots: {
          default: contentWithDisabledItem(),
        },
      });

      const firstRadio = getByLabelText(firstItemLabel);
      firstRadio.focus();

      await userEvent.keyboard('{ArrowDown}');

      // Should skip disabled second item and go to third
      expect(getByLabelText(thirdItemLabel)).toHaveFocus();
    });
  });

  describe('disabled state', () => {
    test('disabled group prevents selection changes', async () => {
      const { getByLabelText, emitted } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: firstItemValue,
          disabled: true,
        },
        slots: {
          default: twoItemsContent(),
        },
      });

      await userEvent.click(getByLabelText(secondItemLabel));

      expect(emitted('update:modelValue')).toBeUndefined();
    });

    test('disabled item cannot be selected', async () => {
      const { getByLabelText, emitted } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: firstItemValue,
        },
        slots: {
          default: contentWithDisabledItem(),
        },
      });

      await userEvent.click(getByLabelText(secondItemLabel));

      expect(emitted('update:modelValue')).toBeUndefined();
    });
  });

  describe('accessibility', () => {
    test('has radiogroup role', () => {
      const { getByRole } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
        },
        slots: {
          default: twoItemsContent(),
        },
      });

      expect(getByRole('radiogroup')).toBeInTheDocument();
    });

    test('items have radio role', () => {
      const { getByLabelText } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
        },
        slots: {
          default: twoItemsContent(),
        },
      });

      // Assert role via the labeled interactive elements
      expect(getByLabelText(firstItemLabel).getAttribute('role')).toBe('radio');
      expect(getByLabelText(secondItemLabel).getAttribute('role')).toBe(
        'radio',
      );
    });

    test('selected item has aria-checked true', () => {
      const { getByLabelText } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          modelValue: firstItemValue,
        },
        slots: {
          default: twoItemsContent(),
        },
      });

      expect(getByLabelText(firstItemLabel)).toHaveAttribute(
        'aria-checked',
        'true',
      );
      expect(getByLabelText(secondItemLabel)).toHaveAttribute(
        'aria-checked',
        'false',
      );
    });

    test('disabled item has disabled attribute', () => {
      const { getByLabelText } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
        },
        slots: {
          default: contentWithDisabledItem(),
        },
      });

      expect(getByLabelText(secondItemLabel)).toBeDisabled();
    });
  });

  describe('orientation', () => {
    test('uses vertical orientation by default', () => {
      const { getByRole } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
        },
        slots: {
          default: twoItemsContent(),
        },
      });

      const radioGroup = getByRole('radiogroup');
      expect(radioGroup).toHaveAttribute('data-orientation', 'vertical');
      expect(radioGroup).toHaveClass('grid');
    });

    test('horizontal orientation sets horizontal layout', () => {
      const { getByRole } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          orientation: 'horizontal',
        },
        slots: {
          default: threeItemsContent(),
        },
      });

      const radioGroup = getByRole('radiogroup');
      expect(radioGroup).toHaveAttribute('data-orientation', 'horizontal');
      expect(radioGroup).toHaveClass('flex');
      expect(radioGroup).not.toHaveClass('grid');
    });

    test('merges custom class with orientation classes', () => {
      const { getByRole } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          orientation: 'horizontal',
          class: 'mt-4 custom-class',
        },
        slots: {
          default: twoItemsContent(),
        },
      });

      const radioGroup = getByRole('radiogroup');
      expect(radioGroup).toHaveClass('flex');
      expect(radioGroup).toHaveClass('mt-4');
      expect(radioGroup).toHaveClass('custom-class');
    });

    test('custom class can override default gap', () => {
      const { getByRole } = render(UiRadioGroup, {
        props: {
          name: 'radio-group',
          class: 'gap-6',
        },
        slots: {
          default: twoItemsContent(),
        },
      });

      const radioGroup = getByRole('radiogroup');
      // tailwind-merge resolves conflicting gap utilities
      expect(radioGroup).toHaveClass('gap-6');
      expect(radioGroup).not.toHaveClass('gap-3');
    });
  });
});
