import '@testing-library/jest-dom'

import { act, fireEvent, render } from '@testing-library/react'
import { vi } from 'vitest'

import type { IPktComboboxOption } from 'shared-types/combobox'
import { PktCombobox } from './Combobox'
import type { IPktCombobox } from './types'

const comboboxId = 'test-combobox'
const label = 'Test Combobox'

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

const createComboboxTest = (props: Partial<IPktCombobox> = {}) => {
  const defaultProps: IPktCombobox = {
    label,
    id: comboboxId,
    ...props,
  }

  return render(<PktCombobox {...defaultProps} />)
}

const getFormInputValue = (container: HTMLElement) => {
  return (container.querySelector('input.pkt-visually-hidden') as HTMLInputElement)?.value ?? ''
}

describe('PktCombobox', () => {
  describe('Keyboard navigation', () => {
    test('opens dropdown with Enter on arrow button', () => {
      const { container } = createComboboxTest()

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

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
    })

    test('opens dropdown with Space on arrow button', () => {
      const { container } = createComboboxTest()

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

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
    })

    test('opens dropdown with ArrowDown on arrow button', () => {
      const { container } = createComboboxTest()

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

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
    })

    test('toggles dropdown closed with Enter on arrow button', () => {
      const { container } = createComboboxTest()

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

      // Open
      fireEvent.keyDown(arrowButton!, { key: 'Enter' })
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')

      // Close
      fireEvent.keyDown(arrowButton!, { key: 'Enter' })
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
    })

    test('does not toggle on non-toggle keys', () => {
      const { container } = createComboboxTest()

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

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
    })

    test('submits value with Enter in text input', () => {
      const handleValueChange = vi.fn()
      const { container } = createComboboxTest({
        allowUserInput: true,
        options: getDefaultOptions(),
        onValueChange: handleValueChange,
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(textInput)

      textInput.value = 'apple'
      fireEvent.change(textInput, { target: { value: 'apple' } })
      fireEvent.keyDown(textInput, { key: 'Enter' })

      expect(handleValueChange).toHaveBeenCalledWith(['apple'])
    })

    test('closes dropdown with Escape in text input', () => {
      const { container } = createComboboxTest({
        allowUserInput: true,
        options: getDefaultOptions(),
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(textInput)

      expect(textInput.getAttribute('aria-expanded')).toBe('true')

      fireEvent.keyDown(textInput, { key: 'Escape' })

      expect(textInput.getAttribute('aria-expanded')).toBe('false')
    })

    test('does not open dropdown when disabled', () => {
      const { container } = createComboboxTest({ disabled: true })

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

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
    })

    test('selects option with Enter key on option element', () => {
      const handleValueChange = vi.fn()
      const { container } = createComboboxTest({
        options: getDefaultOptions(),
        onValueChange: handleValueChange,
      })

      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)

      const option = container.querySelector('[data-value="apple"][role="option"]')
      fireEvent.keyDown(option!, { key: 'Enter' })

      expect(handleValueChange).toHaveBeenCalledWith(['apple'])
    })

    test('selects option with Space key on option element', () => {
      const handleValueChange = vi.fn()
      const { container } = createComboboxTest({
        options: getDefaultOptions(),
        onValueChange: handleValueChange,
      })

      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)

      const option = container.querySelector('[data-value="banana"][role="option"]')
      fireEvent.keyDown(option!, { key: ' ' })

      expect(handleValueChange).toHaveBeenCalledWith(['banana'])
    })

    test('closes dropdown with Escape on option element', () => {
      const { container } = createComboboxTest({
        options: getDefaultOptions(),
      })

      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')

      const option = container.querySelector('[data-value="apple"][role="option"]')
      fireEvent.keyDown(option!, { key: 'Escape' })

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
    })

    test('adds value with comma separator in multiple mode', () => {
      const handleValueChange = vi.fn()
      const { container } = createComboboxTest({
        allowUserInput: true,
        multiple: true,
        options: getDefaultOptions(),
        onValueChange: handleValueChange,
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(textInput)

      textInput.value = 'apple'
      fireEvent.change(textInput, { target: { value: 'apple' } })
      fireEvent.keyDown(textInput, { key: ',' })

      expect(handleValueChange).toHaveBeenCalledWith(['apple'])
    })
  })

  describe('Focus handling', () => {
    test('opens dropdown on input focus', () => {
      const { container } = createComboboxTest({
        allowUserInput: true,
        options: getDefaultOptions(),
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(textInput)

      expect(textInput.getAttribute('aria-expanded')).toBe('true')
    })

    test('populates input with current value on focus in single-select', () => {
      const { container } = createComboboxTest({
        allowUserInput: true,
        defaultValue: 'apple',
        options: getDefaultOptions(),
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(textInput)

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

    test('opens dropdown on input container click (hidden input mode)', () => {
      const { container } = createComboboxTest({
        options: getDefaultOptions(),
      })

      const inputDiv = container.querySelector('.pkt-combobox__input')
      fireEvent.click(inputDiv!)

      const arrowButton = container.querySelector('.pkt-combobox__input')
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
    })

    test('toggles dropdown on input container click (hidden input mode)', () => {
      const { container } = createComboboxTest({
        options: getDefaultOptions(),
      })

      const inputDiv = container.querySelector('.pkt-combobox__input')

      fireEvent.click(inputDiv!)
      const arrowButton = container.querySelector('.pkt-combobox__input')
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')

      fireEvent.click(inputDiv!)
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
    })

    test('does not open when disabled and input container is clicked', () => {
      const { container } = createComboboxTest({ disabled: true })

      const inputDiv = container.querySelector('.pkt-combobox__input')
      fireEvent.click(inputDiv!)

      const arrowButton = container.querySelector('.pkt-combobox__input')
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
    })

    test('focuses text input when placeholder is clicked', () => {
      const { container } = createComboboxTest({
        allowUserInput: true,
        placeholder: 'Select...',
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      expect(textInput.placeholder).toBe('Select...')
      fireEvent.click(textInput)

      expect(document.activeElement).toBe(textInput)
    })
  })

  describe('Focus-out behavior', () => {
    test('closes dropdown when clicking outside combobox', () => {
      const { container } = createComboboxTest({
        options: getDefaultOptions(),
      })

      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')

      fireEvent.click(document.body)

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
    })

    test('adds custom value on focus-out when allowUserInput is on', () => {
      const handleValueChange = vi.fn()
      const { container } = createComboboxTest({
        allowUserInput: true,
        options: getDefaultOptions(),
        onValueChange: handleValueChange,
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(textInput)

      textInput.value = 'NewFruit'
      fireEvent.change(textInput, { target: { value: 'NewFruit' } })

      // Click outside to trigger close and process input
      fireEvent.click(document.body)

      expect(handleValueChange).toHaveBeenCalledWith(['NewFruit'])
    })

    test('selects matching option on focus-out when typeahead', () => {
      const handleValueChange = vi.fn()
      const { container } = createComboboxTest({
        typeahead: true,
        options: getDefaultOptions(),
        onValueChange: handleValueChange,
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(textInput)

      textInput.value = 'Apple'
      fireEvent.change(textInput, { target: { value: 'Apple' } })

      // Click outside to trigger close and process input
      fireEvent.click(document.body)

      expect(handleValueChange).toHaveBeenCalledWith(['apple'])
    })
  })

  describe('Search and filtering', () => {
    test('shows add-value banner when search has no exact match and allowUserInput is on', () => {
      const { container } = createComboboxTest({
        allowUserInput: true,
        options: getDefaultOptions(),
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(textInput)

      fireEvent.input(textInput, { target: { value: 'NewFruit' } })

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

    test('renders search input in listbox when includeSearch is true', () => {
      const { container } = createComboboxTest({ includeSearch: true })

      const searchInput = container.querySelector('.pkt-listbox__search input')
      expect(searchInput).toBeInTheDocument()
      expect(searchInput?.getAttribute('role')).toBe('searchbox')
    })

    test('sets search placeholder in listbox', () => {
      const { container } = createComboboxTest({
        includeSearch: true,
        searchPlaceholder: 'Søk her...',
      })

      const searchInput = container.querySelector('.pkt-listbox__search input') as HTMLInputElement
      expect(searchInput.placeholder).toBe('Søk her...')
    })

    test('filters options via listbox search', () => {
      const { container } = createComboboxTest({
        includeSearch: true,
        options: getDefaultOptions(),
      })

      // Open dropdown
      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)

      // Type in listbox search
      const searchInput = container.querySelector('.pkt-listbox__search input') as HTMLInputElement
      fireEvent.change(searchInput, { target: { value: 'app' } })

      // Options should be filtered
      const visibleOptions = container.querySelectorAll('.pkt-listbox__option')
      expect(visibleOptions.length).toBeLessThan(4)
    })

    test('search input navigates to first option with ArrowDown', () => {
      const { container } = createComboboxTest({
        includeSearch: true,
        options: getDefaultOptions(),
      })

      // Open dropdown
      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)

      const searchInput = container.querySelector('.pkt-listbox__search input') as HTMLInputElement

      // ArrowDown in search should not throw
      fireEvent.keyDown(searchInput, { key: 'ArrowDown' })

      expect(searchInput).toBeInTheDocument()
    })

    test('search input closes dropdown with Escape', () => {
      const { container } = createComboboxTest({
        includeSearch: true,
        options: getDefaultOptions(),
      })

      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')

      const searchInput = container.querySelector('.pkt-listbox__search input') as HTMLInputElement
      fireEvent.keyDown(searchInput, { key: 'Escape' })

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
    })
  })

  describe('New option banner interaction', () => {
    test('adds new option when clicking add-value banner', () => {
      const handleValueChange = vi.fn()
      const { container } = createComboboxTest({
        allowUserInput: true,
        options: getDefaultOptions(),
        onValueChange: handleValueChange,
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(textInput)

      fireEvent.input(textInput, { target: { value: 'NewFruit' } })

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

      fireEvent.click(addBanner!)

      expect(handleValueChange).toHaveBeenCalledWith(['NewFruit'])
    })

    test('adds new option when pressing Enter on add-value banner', () => {
      const handleValueChange = vi.fn()
      const { container } = createComboboxTest({
        allowUserInput: true,
        options: getDefaultOptions(),
        onValueChange: handleValueChange,
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      fireEvent.focus(textInput)

      fireEvent.input(textInput, { target: { value: 'NewFruit' } })

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

      fireEvent.keyDown(addBanner!, { key: 'Enter' })

      expect(handleValueChange).toHaveBeenCalledWith(['NewFruit'])
    })
  })

  describe('Children as options', () => {
    test('handles options from children correctly', () => {
      const handleValueChange = vi.fn()
      const { container } = render(
        <PktCombobox id={comboboxId} label={label} onValueChange={handleValueChange}>
          <option value="opt1">Option 1</option>
          <option value="opt2">Option 2</option>
          <option value="opt3">Option 3</option>
        </PktCombobox>,
      )

      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)

      const option = container.querySelector('[data-value="opt2"][role="option"]')
      fireEvent.click(option!)

      expect(handleValueChange).toHaveBeenCalledWith(['opt2'])
    })

    test('handles children with defaultValue', () => {
      const { container } = render(
        <PktCombobox id={comboboxId} label={label} defaultValue="opt1">
          <option value="opt1">Option 1</option>
          <option value="opt2">Option 2</option>
        </PktCombobox>,
      )

      const valueSpan = container.querySelector('.pkt-combobox__value')
      expect(valueSpan?.textContent?.trim()).toBe('Option 1')
      expect(getFormInputValue(container)).toBe('opt1')
    })

    test('handles children re-render with new options', () => {
      const { container, rerender } = render(
        <PktCombobox id={comboboxId} label={label}>
          <option value="a">A</option>
          <option value="b">B</option>
        </PktCombobox>,
      )

      let options = container.querySelectorAll('.pkt-listbox__option')
      expect(options.length).toBe(2)

      rerender(
        <PktCombobox id={comboboxId} label={label}>
          <option value="a">A</option>
          <option value="b">B</option>
          <option value="c">C</option>
        </PktCombobox>,
      )

      options = container.querySelectorAll('.pkt-listbox__option')
      expect(options.length).toBe(3)
    })
  })

  describe('Form reset', () => {
    beforeEach(() => {
      vi.useFakeTimers()
    })
    afterEach(() => {
      vi.useRealTimers()
    })

    test('resets single value to defaultValue on form reset', () => {
      const { container } = render(
        <form>
          <PktCombobox id={comboboxId} label={label} defaultValue="apple" options={getDefaultOptions()} />
        </form>,
      )

      // Select a different value
      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)
      const bananaOption = container.querySelector('[data-value="banana"][role="option"]')
      fireEvent.click(bananaOption!)

      expect(getFormInputValue(container)).toBe('banana')

      // Reset the form
      const form = container.querySelector('form')!
      act(() => {
        fireEvent.reset(form)
        vi.advanceTimersByTime(10)
      })

      expect(getFormInputValue(container)).toBe('apple')
    })

    test('resets multiple values to defaultValue on form reset', () => {
      const { container } = render(
        <form>
          <PktCombobox
            id={comboboxId}
            label={label}
            defaultValue={['apple', 'banana']}
            multiple
            options={getDefaultOptions()}
          />
        </form>,
      )

      // Select an additional value
      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)
      const dateOption = container.querySelector('[data-value="date"][role="option"]')
      fireEvent.click(dateOption!)

      expect(getFormInputValue(container)).toBe('apple,banana,date')

      // Reset the form
      const form = container.querySelector('form')!
      act(() => {
        fireEvent.reset(form)
        vi.advanceTimersByTime(10)
      })

      expect(getFormInputValue(container)).toBe('apple,banana')
    })

    test('resets to empty when no defaultValue on form reset', () => {
      const { container } = render(
        <form>
          <PktCombobox id={comboboxId} label={label} options={getDefaultOptions()} />
        </form>,
      )

      // Select a value
      const arrowButton = container.querySelector('.pkt-combobox__input')
      fireEvent.click(arrowButton!)
      const appleOption = container.querySelector('[data-value="apple"][role="option"]')
      fireEvent.click(appleOption!)

      expect(getFormInputValue(container)).toBe('apple')

      // Reset the form
      const form = container.querySelector('form')!
      act(() => {
        fireEvent.reset(form)
        vi.advanceTimersByTime(10)
      })

      expect(getFormInputValue(container)).toBe('')
    })
  })
})
