import { CalendarDate } from '@internationalized/date';
import { render } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import type { DateRange } from 'reka-ui';
import { describe, expect, test } from 'vitest';
import UiDatePicker from '../UiDatePicker.vue';

function getEnabledDateButton(
  cells: HTMLElement[],
  index = 0,
): HTMLButtonElement {
  const enabledCells = cells.filter(
    (cell) => !cell.querySelector('button')?.hasAttribute('data-disabled'),
  );
  const button = enabledCells[index]?.querySelector('button');

  if (!button) {
    throw new Error(`Expected enabled date button at index ${index}`);
  }

  return button;
}

describe('UiDatePicker', () => {
  const testDate = new CalendarDate(2025, 1, 15);

  test('renders with custom class on trigger button', () => {
    const { getByRole } = render(UiDatePicker, {
      props: { class: 'w-full custom-picker' },
    });
    const button = getByRole('button');
    expect(button).toHaveClass('w-full', 'custom-picker');
  });

  test('displays placeholder when no date is selected', () => {
    const { getByRole } = render(UiDatePicker, {
      props: { placeholder: 'Select a date' },
    });
    expect(getByRole('button')).toHaveTextContent('Select a date');
  });

  test('displays formatted date when modelValue is set', () => {
    const { getByRole } = render(UiDatePicker, {
      props: { modelValue: testDate, locale: 'en' },
    });
    expect(getByRole('button')).toHaveTextContent('January 15, 2025');
  });

  test('applies disabled state to trigger button', () => {
    const { getByRole } = render(UiDatePicker, {
      props: { disabled: true },
    });
    expect(getByRole('button')).toBeDisabled();
  });

  test('emits update:modelValue with a DateValue when date is selected', async () => {
    const { getByRole, findAllByRole, emitted } = render(UiDatePicker, {
      props: { modelValue: testDate },
    });

    // Open the popover
    await userEvent.click(getByRole('button'));

    // Find and click a date cell (use findAllByRole to wait for async component)
    const cells = await findAllByRole('gridcell');
    const dateButton = getEnabledDateButton(cells);

    await userEvent.click(dateButton);

    const emittedEvents = emitted('update:modelValue');
    expect(emittedEvents).toHaveLength(1);
    const emittedValue = emittedEvents[0][0];
    expect(emittedValue).toHaveProperty('year');
    expect(emittedValue).toHaveProperty('month');
    expect(emittedValue).toHaveProperty('day');
  });

  test('displays defaultValue immediately in uncontrolled mode', () => {
    const { getByRole } = render(UiDatePicker, {
      props: { defaultValue: testDate, locale: 'en' },
    });
    // Should display the default value, not the placeholder
    expect(getByRole('button')).toHaveTextContent('January 15, 2025');
  });

  test('updates display after selection in uncontrolled mode', async () => {
    const { getByRole, findAllByRole, emitted } = render(UiDatePicker, {
      props: { defaultValue: testDate, locale: 'en' },
    });

    // Initially shows the default value
    expect(getByRole('button')).toHaveTextContent('January 15, 2025');

    // Open and select a different date (use findAllByRole to wait for async component)
    await userEvent.click(getByRole('button'));
    const cells = await findAllByRole('gridcell');
    await userEvent.click(getEnabledDateButton(cells));

    // Display should update to show the newly selected date (not the placeholder)
    const buttonText = getByRole('button').textContent;
    expect(buttonText).not.toBe('Pick a date');
    expect(buttonText).not.toBe('January 15, 2025'); // Should be different from initial

    // In uncontrolled mode, update:modelValue should NOT be emitted
    expect(emitted('update:modelValue')).toBeUndefined();
  });

  test('closes popover after date selection when closeOnSelect is true', async () => {
    const { getByRole, findAllByRole, queryByRole } = render(UiDatePicker, {
      props: { closeOnSelect: true },
    });

    // Open the popover
    await userEvent.click(getByRole('button'));
    expect(queryByRole('dialog')).toBeInTheDocument();

    // Select a date (use findAllByRole to wait for async component)
    const cells = await findAllByRole('gridcell');
    await userEvent.click(getEnabledDateButton(cells));

    // Popover should be closed
    expect(queryByRole('dialog')).not.toBeInTheDocument();
  });

  test('keeps popover open after date selection when closeOnSelect is false', async () => {
    const { getByRole, findAllByRole, queryByRole } = render(UiDatePicker, {
      props: { closeOnSelect: false },
    });

    // Open the popover
    await userEvent.click(getByRole('button'));
    expect(queryByRole('dialog')).toBeInTheDocument();

    // Select a date (use findAllByRole to wait for async component)
    const cells = await findAllByRole('gridcell');
    await userEvent.click(getEnabledDateButton(cells));

    // Popover should remain open
    expect(queryByRole('dialog')).toBeInTheDocument();
  });

  test('formats date according to locale', () => {
    const { getByRole } = render(UiDatePicker, {
      props: { modelValue: testDate, locale: 'de' },
    });
    // German format: "15. Januar 2025"
    expect(getByRole('button')).toHaveTextContent('15. Januar 2025');
  });

  test('defaults to "Pick a date" placeholder in single mode', () => {
    const { getByRole } = render(UiDatePicker, {
      props: { mode: 'single' },
    });
    expect(getByRole('button')).toHaveTextContent('Pick a date');
  });

  test('defaults to "Pick a date range" placeholder in range mode', () => {
    const { getByRole } = render(UiDatePicker, {
      props: { mode: 'range' },
    });
    expect(getByRole('button')).toHaveTextContent('Pick a date range');
  });

  test('uses custom placeholder in range mode', () => {
    const { getByRole } = render(UiDatePicker, {
      props: { mode: 'range', placeholder: 'Select travel dates' },
    });
    expect(getByRole('button')).toHaveTextContent('Select travel dates');
  });

  test('displays formatted date range when modelValue is set in range mode', () => {
    const dateRange: DateRange = {
      start: new CalendarDate(2025, 1, 15),
      end: new CalendarDate(2025, 1, 20),
    };
    const { getByRole } = render(UiDatePicker, {
      props: { mode: 'range', modelValue: dateRange, locale: 'en' },
    });
    expect(getByRole('button')).toHaveTextContent(
      'January 15, 2025 - January 20, 2025',
    );
  });

  test('displays partial date range when only start date is selected', () => {
    const dateRange: DateRange = {
      start: new CalendarDate(2025, 1, 15),
      end: undefined,
    };
    const { getByRole } = render(UiDatePicker, {
      props: { mode: 'range', modelValue: dateRange, locale: 'en' },
    });
    expect(getByRole('button')).toHaveTextContent('January 15, 2025');
  });

  test('emits update:modelValue with a DateRange when range is selected', async () => {
    const initialRange: DateRange = { start: undefined, end: undefined };
    const { getByRole, findAllByRole, emitted } = render(UiDatePicker, {
      props: { mode: 'range', modelValue: initialRange },
    });

    // Open the popover
    await userEvent.click(getByRole('button'));

    // Find and click two date cells (use findAllByRole to wait for async component)
    const cells = await findAllByRole('gridcell');
    // Click first date (start)
    await userEvent.click(getEnabledDateButton(cells, 5));

    // Click second date (end)
    await userEvent.click(getEnabledDateButton(cells, 10));

    const emittedEvents = emitted('update:modelValue');
    expect(emittedEvents.length).toBeGreaterThanOrEqual(1);
    const lastEmittedValue = emittedEvents[emittedEvents.length - 1][0];
    expect(lastEmittedValue).toHaveProperty('start');
    expect(lastEmittedValue).toHaveProperty('end');
    expect(lastEmittedValue.start).toHaveProperty('year');
    expect(lastEmittedValue.end).toHaveProperty('year');
  });

  test('keeps popover open in range mode until both dates are selected when closeOnSelect is true', async () => {
    const { getByRole, findAllByRole, queryByRole } = render(UiDatePicker, {
      props: { mode: 'range', closeOnSelect: true },
    });

    // Open the popover
    await userEvent.click(getByRole('button'));
    expect(queryByRole('dialog')).toBeInTheDocument();

    // Select first date (use findAllByRole to wait for async component)
    const cells = await findAllByRole('gridcell');
    await userEvent.click(getEnabledDateButton(cells, 5));

    // Popover should still be open (only start date selected)
    expect(queryByRole('dialog')).toBeInTheDocument();

    // Select second date
    await userEvent.click(getEnabledDateButton(cells, 10));

    // Now popover should close (both dates selected)
    expect(queryByRole('dialog')).not.toBeInTheDocument();
  });

  describe('lazy loading optimization', () => {
    test('lazily loads UiCalendar in single mode when popover opens', async () => {
      const { getByRole, queryByRole, findByRole, findAllByRole } = render(
        UiDatePicker,
        {
          props: { mode: 'single', modelValue: new CalendarDate(2025, 1, 15) },
        },
      );

      // Before opening, popover content is not rendered
      expect(queryByRole('dialog')).not.toBeInTheDocument();

      // Open the popover
      await userEvent.click(getByRole('button'));

      // Calendar should be rendered after async load (find* waits for element)
      await findByRole('dialog');
      const cells = await findAllByRole('gridcell');
      expect(cells.length).toBeGreaterThan(0);
    });

    test('lazily loads UiRangeCalendar in range mode when popover opens', async () => {
      const { getByRole, queryByRole, findByRole, findAllByRole } = render(
        UiDatePicker,
        {
          props: { mode: 'range' },
        },
      );

      // Before opening, popover content is not rendered
      expect(queryByRole('dialog')).not.toBeInTheDocument();

      // Open the popover
      await userEvent.click(getByRole('button'));

      // RangeCalendar should be rendered after async load (find* waits for element)
      await findByRole('dialog');
      const cells = await findAllByRole('gridcell');
      expect(cells.length).toBeGreaterThan(0);
    });

    test('single mode calendar is fully functional after lazy load', async () => {
      const { getByRole, findAllByRole, emitted } = render(UiDatePicker, {
        props: { mode: 'single', modelValue: new CalendarDate(2025, 1, 15) },
      });

      // Open popover and wait for async component
      await userEvent.click(getByRole('button'));

      // Wait for calendar grid cells to load (findAllByRole waits for elements)
      const cells = await findAllByRole('gridcell');
      await userEvent.click(getEnabledDateButton(cells));

      // Should emit the selected date
      const emittedEvents = emitted('update:modelValue');
      expect(emittedEvents).toHaveLength(1);
      expect(emittedEvents[0][0]).toHaveProperty('year');
    });

    test('range mode calendar is fully functional after lazy load', async () => {
      const { getByRole, findAllByRole, emitted } = render(UiDatePicker, {
        props: { mode: 'range' },
      });

      // Open popover and wait for async component
      await userEvent.click(getByRole('button'));

      // Wait for calendar grid cells to load (findAllByRole waits for elements)
      const cells = await findAllByRole('gridcell');
      await userEvent.click(getEnabledDateButton(cells, 5));
      await userEvent.click(getEnabledDateButton(cells, 10));

      // Should emit the date range
      const emittedEvents = emitted('update:modelValue');
      expect(emittedEvents.length).toBeGreaterThanOrEqual(1);
      const lastEmitted = emittedEvents[emittedEvents.length - 1][0];
      expect(lastEmitted).toHaveProperty('start');
      expect(lastEmitted).toHaveProperty('end');
    });
  });
});
