import { using } from '@furystack/utils'
import { describe, expect, it, vi } from 'vitest'
import { ListService } from './list-service.js'

type TestItem = { id: number; name: string }

describe('ListService', () => {
  const createTestService = (options?: ConstructorParameters<typeof ListService<TestItem>>[0]) => {
    const service = new ListService<TestItem>(options)
    const items: TestItem[] = [
      { id: 1, name: 'First' },
      { id: 2, name: 'Second' },
      { id: 3, name: 'Third' },
    ]
    service.items.setValue(items)
    return { service, items }
  }

  describe('selection helpers', () => {
    it('should check if item is selected', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.selection.setValue([items[0]])

        expect(service.isSelected(items[0])).toBe(true)
        expect(service.isSelected(items[1])).toBe(false)
      })
    })

    it('should add item to selection', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.addToSelection(items[0])

        expect(service.selection.getValue()).toContain(items[0])
      })
    })

    it('should remove item from selection', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.selection.setValue([items[0], items[1]])
        service.removeFromSelection(items[0])

        expect(service.selection.getValue()).not.toContain(items[0])
        expect(service.selection.getValue()).toContain(items[1])
      })
    })

    it('should toggle selection on', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.toggleSelection(items[0])

        expect(service.isSelected(items[0])).toBe(true)
      })
    })

    it('should toggle selection off', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.selection.setValue([items[0]])
        service.toggleSelection(items[0])

        expect(service.isSelected(items[0])).toBe(false)
      })
    })
  })

  describe('handleKeyDown', () => {
    it('should not handle keyboard when not focused', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(false)
        service.focusedItem.setValue(items[0])

        service.handleKeyDown(new KeyboardEvent('keydown', { key: 'ArrowDown' }))

        expect(service.focusedItem.getValue()).toBe(items[0])
      })
    })

    it('should not handle ArrowDown (delegated to spatial navigation)', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)
        service.focusedItem.setValue(items[0])

        const ev = new KeyboardEvent('keydown', { key: 'ArrowDown', cancelable: true })
        const preventSpy = vi.spyOn(ev, 'preventDefault')
        service.handleKeyDown(ev)

        expect(service.focusedItem.getValue()).toBe(items[0])
        expect(preventSpy).not.toHaveBeenCalled()
      })
    })

    it('should not handle ArrowUp (delegated to spatial navigation)', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)
        service.focusedItem.setValue(items[1])

        const ev = new KeyboardEvent('keydown', { key: 'ArrowUp', cancelable: true })
        const preventSpy = vi.spyOn(ev, 'preventDefault')
        service.handleKeyDown(ev)

        expect(service.focusedItem.getValue()).toBe(items[1])
        expect(preventSpy).not.toHaveBeenCalled()
      })
    })

    it('should handle Home to move focus to first item', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)
        service.focusedItem.setValue(items[2])

        service.handleKeyDown(new KeyboardEvent('keydown', { key: 'Home' }))

        expect(service.focusedItem.getValue()).toBe(items[0])
      })
    })

    it('should handle End to move focus to last item', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)
        service.focusedItem.setValue(items[0])

        service.handleKeyDown(new KeyboardEvent('keydown', { key: 'End' }))

        expect(service.focusedItem.getValue()).toBe(items[2])
      })
    })

    it('should handle Space to toggle selection of focused item', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)
        service.focusedItem.setValue(items[0])

        service.handleKeyDown(new KeyboardEvent('keydown', { key: ' ' }))
        expect(service.selection.getValue()).toContain(items[0])

        service.handleKeyDown(new KeyboardEvent('keydown', { key: ' ' }))
        expect(service.selection.getValue()).not.toContain(items[0])
      })
    })

    it('should handle + to select all items', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)

        service.handleKeyDown(new KeyboardEvent('keydown', { key: '+' }))

        expect(service.selection.getValue().length).toBe(3)
        expect(service.selection.getValue()).toEqual(items)
      })
    })

    it('should handle - to deselect all items', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)
        service.selection.setValue([...items])

        service.handleKeyDown(new KeyboardEvent('keydown', { key: '-' }))

        expect(service.selection.getValue().length).toBe(0)
      })
    })

    it('should handle * to invert selection', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)
        service.selection.setValue([items[0]])

        service.handleKeyDown(new KeyboardEvent('keydown', { key: '*' }))

        const selection = service.selection.getValue()
        expect(selection).not.toContain(items[0])
        expect(selection).toContain(items[1])
        expect(selection).toContain(items[2])
      })
    })

    it('should handle Insert to toggle selection and move to next item', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)
        service.focusedItem.setValue(items[0])

        service.handleKeyDown(new KeyboardEvent('keydown', { key: 'Insert' }))

        expect(service.selection.getValue()).toContain(items[0])
        expect(service.focusedItem.getValue()).toBe(items[1])
      })
    })

    it('should handle Insert to deselect already selected item', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)
        service.focusedItem.setValue(items[0])
        service.selection.setValue([items[0]])

        service.handleKeyDown(new KeyboardEvent('keydown', { key: 'Insert' }))

        expect(service.selection.getValue()).not.toContain(items[0])
        expect(service.focusedItem.getValue()).toBe(items[1])
      })
    })

    it('should handle Escape to clear selection and search term', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.hasFocus.setValue(true)
        service.selection.setValue([items[0], items[1]])
        service.searchTerm.setValue('test')

        service.handleKeyDown(new KeyboardEvent('keydown', { key: 'Escape' }))

        expect(service.selection.getValue()).toEqual([])
        expect(service.searchTerm.getValue()).toBe('')
      })
    })

    it('should handle type-ahead search when searchField is set', () => {
      const { service, items } = createTestService({ searchField: 'name' })
      using(service, () => {
        service.hasFocus.setValue(true)

        service.handleKeyDown(new KeyboardEvent('keydown', { key: 'S' }))

        expect(service.searchTerm.getValue()).toBe('S')
        expect(service.focusedItem.getValue()).toBe(items[1])
      })
    })

    it('should accumulate type-ahead search characters', () => {
      const { service, items } = createTestService({ searchField: 'name' })
      using(service, () => {
        service.hasFocus.setValue(true)

        service.handleKeyDown(new KeyboardEvent('keydown', { key: 'T' }))
        service.handleKeyDown(new KeyboardEvent('keydown', { key: 'h' }))
        service.handleKeyDown(new KeyboardEvent('keydown', { key: 'i' }))

        expect(service.searchTerm.getValue()).toBe('Thi')
        expect(service.focusedItem.getValue()).toBe(items[2])
      })
    })
  })

  describe('handleItemClick', () => {
    it('should set focused item on click', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.handleItemClick(items[1], new MouseEvent('click'))

        expect(service.focusedItem.getValue()).toBe(items[1])
      })
    })

    it('should add to selection on Ctrl+Click', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.handleItemClick(items[0], new MouseEvent('click', { ctrlKey: true }))

        expect(service.selection.getValue()).toContain(items[0])
      })
    })

    it('should remove from selection on Ctrl+Click when already selected', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.selection.setValue([items[0]])

        service.handleItemClick(items[0], new MouseEvent('click', { ctrlKey: true }))

        expect(service.selection.getValue()).not.toContain(items[0])
      })
    })

    it('should select range on Shift+Click', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.focusedItem.setValue(items[0])

        service.handleItemClick(items[2], new MouseEvent('click', { shiftKey: true }))

        const selection = service.selection.getValue()
        expect(selection).toContain(items[0])
        expect(selection).toContain(items[1])
        expect(selection).toContain(items[2])
      })
    })

    it('should select range backwards on Shift+Click', () => {
      const { service, items } = createTestService()
      using(service, () => {
        service.focusedItem.setValue(items[2])

        service.handleItemClick(items[0], new MouseEvent('click', { shiftKey: true }))

        const selection = service.selection.getValue()
        expect(selection).toContain(items[0])
        expect(selection).toContain(items[1])
        expect(selection).toContain(items[2])
      })
    })
  })

  describe('handleItemDoubleClick', () => {
    it('should not throw on double-click', () => {
      const { service, items } = createTestService()
      using(service, () => {
        expect(() => service.handleItemDoubleClick(items[0])).not.toThrow()
      })
    })
  })

  describe('dispose', () => {
    it('should dispose all observables', () => {
      const { service } = createTestService()

      expect(() => service[Symbol.dispose]()).not.toThrow()
    })
  })
})
