import '@testing-library/jest-dom'
import { axe, toHaveNoViolations } from 'jest-axe'
import { fireEvent } from '@testing-library/dom'
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
import { CustomElementFor } from '../../tests/component-registry'
import { type IPktListbox } from './listbox'
import './listbox'

export interface ListboxTestConfig extends Partial<IPktListbox>, BaseTestConfig {
  label?: string
  id?: string
}

// Properties that must be set via JS because their attribute names are kebab-case
const jsOnlyProps = [
  'options',
  'isOpen',
  'includeSearch',
  'isMultiSelect',
  'allowUserInput',
  'maxIsReached',
  'customUserInput',
  'searchPlaceholder',
  'searchValue',
  'maxLength',
  'userMessage',
] as const

// Use shared framework
export const createListboxTest = async (config: ListboxTestConfig = {}) => {
  const htmlConfig: Record<string, unknown> = {}
  const jsConfig: Record<string, unknown> = {}

  for (const [key, value] of Object.entries(config)) {
    if ((jsOnlyProps as readonly string[]).includes(key)) {
      jsConfig[key] = value
    } else {
      htmlConfig[key] = value
    }
  }

  const { container, element } = await createElementTest<
    CustomElementFor<'pkt-listbox'>,
    BaseTestConfig & Record<string, unknown>
  >('pkt-listbox', htmlConfig)

  // Set JS-only properties directly
  for (const [key, value] of Object.entries(jsConfig)) {
    ;(element as any)[key] = value
  }
  await element.updateComplete

  return {
    container,
    listbox: element,
  }
}

expect.extend(toHaveNoViolations)

// Cleanup after each test
afterEach(() => {
  document.body.innerHTML = ''
})

describe('PktListbox', () => {
  describe('Rendering and basic functionality', () => {
    test('renders without errors', async () => {
      const { listbox } = await createListboxTest()

      expect(listbox).toBeInTheDocument()
      expect(listbox).toBeTruthy()
    })

    test('renders with options', async () => {
      const options = [
        { value: 'option1', label: 'Option 1' },
        { value: 'option2', label: 'Option 2' },
      ]
      const { listbox } = await createListboxTest({
        label: 'Test Listbox',
        options,
      })

      const listboxElement = listbox.querySelector('.pkt-listbox')
      expect(listboxElement).toBeInTheDocument()

      const optionElements = listbox.querySelectorAll('.pkt-listbox__option')
      expect(optionElements).toHaveLength(2)
    })
  })

  describe('Properties and attributes', () => {
    test('applies default properties correctly', async () => {
      const { listbox } = await createListboxTest()

      expect(listbox.isOpen).toBe(false)
      expect(listbox.disabled).toBe(false)
      expect(listbox.includeSearch).toBe(false)
      expect(listbox.isMultiSelect).toBe(false)
      expect(listbox.allowUserInput).toBe(false)
      expect(listbox.maxLength).toBe(0)
    })

    test('sets properties correctly', async () => {
      const { listbox } = await createListboxTest({
        isOpen: true,
        disabled: true,
        includeSearch: true,
        isMultiSelect: true,
      })

      expect(listbox.isOpen).toBe(true)
      expect(listbox.disabled).toBe(true)
      expect(listbox.includeSearch).toBe(true)
      expect(listbox.isMultiSelect).toBe(true)
    })
  })

  describe('Option handling', () => {
    test('renders single select options correctly', async () => {
      const options = [
        { value: 'option1', label: 'Option 1', selected: true },
        { value: 'option2', label: 'Option 2' },
      ]
      const { listbox } = await createListboxTest({ options })

      const selectedOption = listbox.querySelector('.pkt-listbox__option--selected')
      expect(selectedOption).toBeInTheDocument()

      const checkIcon = listbox.querySelector('pkt-icon[name="check-big"]')
      expect(checkIcon).toBeInTheDocument()
    })

    test('renders multi-select options with checkboxes', async () => {
      const options = [
        { value: 'option1', label: 'Option 1', selected: true },
        { value: 'option2', label: 'Option 2' },
      ]
      const { listbox } = await createListboxTest({
        isMultiSelect: true,
        options,
      })

      const checkboxes = listbox.querySelectorAll('input[type="checkbox"]')
      expect(checkboxes).toHaveLength(2)
      expect(checkboxes[0]).toBeChecked()
      expect(checkboxes[1]).not.toBeChecked()
    })

    test('handles option click', async () => {
      const options = [
        { value: 'option1', label: 'Option 1' },
        { value: 'option2', label: 'Option 2' },
      ]
      const { listbox } = await createListboxTest({ options })

      // Listen for the option-toggle event
      let toggledValue: string | null = null
      listbox.addEventListener('option-toggle', (e: any) => {
        toggledValue = e.detail
      })

      const optionElement = listbox.querySelector('.pkt-listbox__option')
      fireEvent.click(optionElement!)

      await listbox.updateComplete
      expect(toggledValue).toBe('option1')
    })
  })

  describe('Search functionality', () => {
    test('renders search when includeSearch is true', async () => {
      const { listbox } = await createListboxTest({ includeSearch: true })

      const searchInput = listbox.querySelector('[role="searchbox"]')
      expect(searchInput).toBeInTheDocument()
    })

    test('filters options based on search', async () => {
      const options = [
        { value: 'apple', label: 'Apple' },
        { value: 'banana', label: 'Banana' },
      ]
      const { listbox } = await createListboxTest({
        includeSearch: true,
        options,
      })

      // Set search value and trigger filtering
      listbox.searchValue = 'app'
      listbox.filterOptions()
      await listbox.updateComplete

      // Should filter to only show Apple - check filtered options
      const visibleOptions = listbox.querySelectorAll('.pkt-listbox__option')
      expect(visibleOptions).toHaveLength(1)
      expect(visibleOptions[0].textContent?.trim()).toContain('Apple')
    })
  })

  describe('User input functionality', () => {
    test('renders new option banner when allowUserInput is true', async () => {
      const { listbox } = await createListboxTest({
        allowUserInput: true,
        customUserInput: 'New',
      })

      const newOptionBanner = listbox.querySelector('.pkt-listbox__banner--new-option')
      expect(newOptionBanner).toBeInTheDocument()
      expect(newOptionBanner?.getAttribute('data-value')).toBe('New')
      // Check that the text contains the basic structure
      expect(newOptionBanner?.textContent).toMatch(/Legg til.*New/)
    })
  })

  describe('Maximum selection', () => {
    test('shows maximum reached banner', async () => {
      const options = [
        { value: 'option1', label: 'Option 1', selected: true },
        { value: 'option2', label: 'Option 2', selected: true },
      ]
      const { listbox } = await createListboxTest({
        isMultiSelect: true,
        maxLength: 3,
        options,
      })

      const banner = listbox.querySelector('.pkt-listbox__banner--maximum-reached')
      expect(banner).toBeInTheDocument()
      expect(banner?.textContent).toContain('2 av maks 3')
    })
  })

  describe('Multi-select functionality', () => {
    test('sets aria-multiselectable to true when isMultiSelect is true', async () => {
      const options = [
        { value: 'option1', label: 'Option 1' },
        { value: 'option2', label: 'Option 2' },
      ]
      const { listbox } = await createListboxTest({
        isMultiSelect: true,
        options,
      })

      const listboxElement = listbox.querySelector('[role="listbox"]')
      expect(listboxElement?.getAttribute('aria-multiselectable')).toBe('true')
    })

    test('allows multiple options to be selected', async () => {
      const options = [
        { value: 'option1', label: 'Option 1', selected: true },
        { value: 'option2', label: 'Option 2', selected: true },
        { value: 'option3', label: 'Option 3', selected: false },
      ]
      const { listbox } = await createListboxTest({
        isMultiSelect: true,
        options,
      })

      const checkboxes = listbox.querySelectorAll('input[type="checkbox"]')
      expect(checkboxes[0]).toBeChecked()
      expect(checkboxes[1]).toBeChecked()
      expect(checkboxes[2]).not.toBeChecked()
    })

    test('dispatches select-all event when Cmd+A is pressed', async () => {
      const options = [
        { value: 'option1', label: 'Option 1' },
        { value: 'option2', label: 'Option 2' },
        { value: 'option3', label: 'Option 3' },
      ]
      const { listbox } = await createListboxTest({
        isMultiSelect: true,
        options,
      })

      let selectAllCalled = false
      listbox.addEventListener('select-all', () => {
        selectAllCalled = true
      })

      const firstOption = listbox.querySelector('.pkt-listbox__option')
      firstOption?.dispatchEvent(
        new KeyboardEvent('keydown', {
          key: 'a',
          metaKey: true,
          bubbles: true,
        }),
      )

      await listbox.updateComplete
      expect(selectAllCalled).toBe(true)
    })

    test('dispatches select-all event when Ctrl+A is pressed', async () => {
      const options = [
        { value: 'option1', label: 'Option 1' },
        { value: 'option2', label: 'Option 2' },
      ]
      const { listbox } = await createListboxTest({
        isMultiSelect: true,
        options,
      })

      let selectAllCalled = false
      listbox.addEventListener('select-all', () => {
        selectAllCalled = true
      })

      const firstOption = listbox.querySelector('.pkt-listbox__option')
      firstOption?.dispatchEvent(
        new KeyboardEvent('keydown', {
          key: 'a',
          ctrlKey: true,
          bubbles: true,
        }),
      )

      await listbox.updateComplete
      expect(selectAllCalled).toBe(true)
    })

    test('toggles individual options in multi-select mode', async () => {
      const options = [
        { value: 'option1', label: 'Option 1' },
        { value: 'option2', label: 'Option 2' },
      ]
      const { listbox } = await createListboxTest({
        isMultiSelect: true,
        options,
      })

      const toggledValues: string[] = []
      listbox.addEventListener('option-toggle', (e: any) => {
        toggledValues.push(e.detail)
      })

      const optionElements = listbox.querySelectorAll('.pkt-listbox__option')
      fireEvent.click(optionElements[0])
      fireEvent.click(optionElements[1])

      await listbox.updateComplete
      expect(toggledValues).toEqual(['option1', 'option2'])
    })
  })

  describe('Accessibility', () => {
    test('basic listbox is accessible', async () => {
      const options = [
        { value: 'option1', label: 'Option 1' },
        { value: 'option2', label: 'Option 2' },
      ]
      const { container } = await createListboxTest({
        label: 'Accessible Listbox',
        options,
      })
      await new Promise((resolve) => setTimeout(resolve, 100))

      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })
  })
})
