import '@testing-library/jest-dom'
import { fireEvent } from '@testing-library/dom'

import './combobox'
import { PktCombobox } from './combobox'
import type { IPktComboboxOption } from './combobox'

const waitForCustomElements = async () => {
  await customElements.whenDefined('pkt-combobox')
}

const createCombobox = async (comboboxProps = '') => {
  const container = document.createElement('div')
  container.innerHTML = `
    <pkt-combobox ${comboboxProps}></pkt-combobox>
  `
  document.body.appendChild(container)
  await waitForCustomElements()
  return container
}

const defaultOptions: IPktComboboxOption[] = [
  { value: 'apple', label: 'Apple' },
  { value: 'banana', label: 'Banana' },
  { value: 'cherry', label: 'Cherry' },
  { value: 'date', label: 'Date' },
]

afterEach(() => {
  document.body.innerHTML = ''
})

describe('PktCombobox', () => {
  describe('Keyboard navigation', () => {
    test('opens dropdown with Enter on arrow button', async () => {
      const container = await createCombobox('id="test" name="test" label="Test"')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const arrowButton = combobox.querySelector('.pkt-combobox__input')
      fireEvent.keyDown(arrowButton!, { key: 'Enter' })
      await combobox.updateComplete

      expect(combobox['_isOptionsOpen']).toBe(true)
    })

    test('opens dropdown with Space on arrow button', async () => {
      const container = await createCombobox('id="test" name="test" label="Test"')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const arrowButton = combobox.querySelector('.pkt-combobox__input')
      fireEvent.keyDown(arrowButton!, { key: ' ' })
      await combobox.updateComplete

      expect(combobox['_isOptionsOpen']).toBe(true)
    })

    test('opens dropdown with ArrowDown on arrow button', async () => {
      const container = await createCombobox('id="test" name="test" label="Test"')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const arrowButton = combobox.querySelector('.pkt-combobox__input')
      fireEvent.keyDown(arrowButton!, { key: 'ArrowDown' })
      await combobox.updateComplete

      expect(combobox['_isOptionsOpen']).toBe(true)
    })

    test('toggles dropdown closed with Enter on arrow button', async () => {
      const container = await createCombobox('id="test" name="test" label="Test"')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const arrowButton = combobox.querySelector('.pkt-combobox__input')

      // Open
      fireEvent.keyDown(arrowButton!, { key: 'Enter' })
      await combobox.updateComplete
      expect(combobox['_isOptionsOpen']).toBe(true)

      // Close
      fireEvent.keyDown(arrowButton!, { key: 'Enter' })
      await combobox.updateComplete
      expect(combobox['_isOptionsOpen']).toBe(false)
    })

    test('does not toggle on non-toggle keys', async () => {
      const container = await createCombobox('id="test" name="test" label="Test"')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const arrowButton = combobox.querySelector('.pkt-combobox__input')
      fireEvent.keyDown(arrowButton!, { key: 'Escape' })
      await combobox.updateComplete

      expect(combobox['_isOptionsOpen']).toBe(false)
    })

    test('submits value with Enter in text input', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(input)
      await combobox.updateComplete

      input.value = 'apple'
      fireEvent.keyDown(input, { key: 'Enter' })
      await combobox.updateComplete

      expect(combobox['_value']).toContain('apple')
    })

    test('closes dropdown with Escape in text input', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(input)
      await combobox.updateComplete
      expect(combobox['_isOptionsOpen']).toBe(true)

      fireEvent.keyDown(input, { key: 'Escape' })
      await combobox.updateComplete

      expect(combobox['_isOptionsOpen']).toBe(false)
    })

    test('does not open dropdown when disabled', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" disabled')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const arrowButton = combobox.querySelector('.pkt-combobox__input')
      fireEvent.keyDown(arrowButton!, { key: 'Enter' })
      await combobox.updateComplete

      expect(combobox['_isOptionsOpen']).toBe(false)
    })
  })

  describe('Focus handling', () => {
    test('opens dropdown on input focus', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(input)
      await combobox.updateComplete

      expect(combobox['_isOptionsOpen']).toBe(true)
      expect(combobox['_inputFocus']).toBe(true)
    })

    test('populates input with current value on focus in single-select', async () => {
      const container = await createCombobox(
        'id="test" name="test" label="Test" allow-user-input value="apple"',
      )
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(input)
      await combobox.updateComplete

      expect(input.value).toBe('Apple')
    })

    test('handles blur correctly', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(input)
      await combobox.updateComplete

      fireEvent.blur(input)
      await combobox.updateComplete

      expect(combobox['_inputFocus']).toBe(false)
    })

    test('opens dropdown on input container click', async () => {
      const container = await createCombobox('id="test" name="test" label="Test"')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const inputDiv = combobox.querySelector('.pkt-combobox__input')
      fireEvent.click(inputDiv!)
      await combobox.updateComplete

      expect(combobox['_isOptionsOpen']).toBe(true)
    })

    test('does not open when disabled and input container is clicked', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" disabled')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const inputDiv = combobox.querySelector('.pkt-combobox__input')
      fireEvent.click(inputDiv!)
      await combobox.updateComplete

      expect(combobox['_isOptionsOpen']).toBe(false)
    })
  })

  describe('Focus-out behavior', () => {
    test('closes dropdown when clicking outside combobox', async () => {
      const container = await createCombobox('id="test" name="test" label="Test"')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const arrowButton = combobox.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)
      await combobox.updateComplete
      expect(combobox['_isOptionsOpen']).toBe(true)

      fireEvent.click(document.body)
      await combobox.updateComplete

      expect(combobox['_isOptionsOpen']).toBe(false)
    })

    test('selects matching option on focus-out when allowUserInput is off', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" typeahead')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(input)
      await combobox.updateComplete

      input.value = 'Apple'
      combobox['_isOptionsOpen'] = true
      await combobox.updateComplete
      ;(combobox as any).closeAndProcessInput()
      await combobox.updateComplete

      expect(combobox['_value']).toContain('apple')
    })

    test('adds custom value on focus-out when allowUserInput is on', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(input)
      await combobox.updateComplete

      input.value = 'NewFruit'
      combobox['_isOptionsOpen'] = true
      await combobox.updateComplete
      ;(combobox as any).closeAndProcessInput()
      await combobox.updateComplete

      expect(combobox['_value']).toContain('NewFruit')
    })
  })

  describe('Search and filtering', () => {
    test('filters options when typing in typeahead mode', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" typeahead')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(input)
      await combobox.updateComplete

      // Type to filter
      input.value = 'app'
      fireEvent.input(input, { target: { value: 'app' } })
      await combobox.updateComplete

      const listbox = combobox.querySelector('pkt-listbox') as any
      await listbox?.updateComplete

      // Internal _options should be filtered
      const filteredCount = combobox['_options'].length
      expect(filteredCount).toBeLessThan(defaultOptions.length)
    })

    test('shows no-match message when search has no results and allowUserInput is off', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" typeahead')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(input)
      await combobox.updateComplete

      input.value = 'zzzzz'
      fireEvent.input(input, { target: { value: 'zzzzz' } })
      await combobox.updateComplete

      const listbox = combobox.querySelector('pkt-listbox') as any
      await listbox?.updateComplete

      const visibleOptions = combobox.querySelectorAll('.pkt-listbox__option')
      expect(visibleOptions.length).toBe(0)
    })

    test('shows add-value banner when search has no exact match and allowUserInput is on', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(input)
      await combobox.updateComplete

      input.value = 'NewFruit'
      fireEvent.input(input, { target: { value: 'NewFruit' } })
      await combobox.updateComplete

      const listbox = combobox.querySelector('pkt-listbox') as any
      await listbox?.updateComplete

      const addBanner = combobox.querySelector('.pkt-listbox__banner--new-option')
      expect(addBanner).toBeInTheDocument()
    })

    test('dispatches search event on internal search state change', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" include-search')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      let searchEventDetail: string | null = null
      combobox.addEventListener('search', (e: Event) => {
        searchEventDetail = (e as CustomEvent).detail
      })

      combobox['_search'] = 'test query'
      await combobox.updateComplete

      expect(searchEventDetail).toBe('test query')
    })

    test('resets search when option is selected via toggleValue', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" typeahead')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      // Set some search state
      combobox['_search'] = 'app'
      await combobox.updateComplete

      // Select an option
      ;(combobox as any).toggleValue('apple')
      await combobox.updateComplete

      expect(combobox['_search']).toBe('')
    })
  })

  describe('Listbox search (includeSearch)', () => {
    test('passes includeSearch to listbox', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" include-search')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const listbox = combobox.querySelector('pkt-listbox')
      expect(listbox?.hasAttribute('include-search')).toBe(true)
    })

    test('passes searchPlaceholder to listbox', async () => {
      const container = await createCombobox(
        'id="test" name="test" label="Test" include-search search-placeholder="Søk her..."',
      )
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const listbox = combobox.querySelector('pkt-listbox') as any
      expect(listbox?.searchPlaceholder).toBe('Søk her...')
    })

    test('updates search state on listbox search-change event', async () => {
      const container = await createCombobox('id="test" name="test" label="Test" include-search')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      combobox.options = [...defaultOptions]
      await combobox.updateComplete

      // Open dropdown first
      const arrowButton = combobox.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)
      await combobox.updateComplete

      // Simulate search input in the listbox search field
      const listbox = combobox.querySelector('pkt-listbox') as any
      await listbox?.updateComplete

      const searchInput = combobox.querySelector('[role="searchbox"]') as HTMLInputElement
      if (searchInput) {
        fireEvent.input(searchInput, { target: { value: 'app' } })
        await combobox.updateComplete
      }

      expect(combobox['_search']).toBe('app')
    })
  })

  describe('Disconnected callback cleanup', () => {
    test('cleans up body click handler on disconnect', async () => {
      const container = await createCombobox('id="test" name="test" label="Test"')
      const combobox = container.querySelector('pkt-combobox') as PktCombobox
      await combobox.updateComplete

      const arrowButton = combobox.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)
      await combobox.updateComplete
      expect(combobox['_isOptionsOpen']).toBe(true)

      combobox.remove()

      expect(() => fireEvent.click(document.body)).not.toThrow()
    })
  })
})
