import { describe, expect, it, vi } from 'vitest'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { AsyncOptionSearch, type AsyncFieldOptionRequest, type AsyncFieldOptionSource } from './async-options.js'

describe('AsyncOptionSearch',()=>{
  it('aborts stale searches when the query changes',async()=>{
    const calls:AsyncFieldOptionRequest[]=[]
    const source:AsyncFieldOptionSource<string>={search:(request)=>{calls.push(request);if(request.query==='ab')return Promise.resolve({items:[{key:'ab1',label:'AB result',value:'ab1'}],nextCursor:null});return new Promise(()=>{})}}
    render(<AsyncOptionSearch source={source} onChoose={vi.fn()} label="Entity" minQueryLength={1}/>)
    const input=screen.getByRole('combobox')
    fireEvent.change(input,{target:{value:'a'}})
    await waitFor(()=>expect(calls).toHaveLength(1))
    fireEvent.change(input,{target:{value:'ab'}})
    await waitFor(()=>expect(calls).toHaveLength(2))
    expect(calls[0]!.signal.aborted).toBe(true)
    expect(await screen.findByRole('option',{name:/AB result/})).toBeTruthy()
  })

  it('paginates without dropping previous results and chooses an option',async()=>{
    const onChoose=vi.fn(),source:AsyncFieldOptionSource<string>={search:vi.fn(async({cursor})=>cursor===null?{items:[{key:'1',label:'One',value:'one'}],nextCursor:'next'}:{items:[{key:'2',label:'Two',value:'two'}],nextCursor:null})}
    render(<AsyncOptionSearch source={source} onChoose={onChoose} label="Users"/>)
    expect(await screen.findByRole('option',{name:/One/})).toBeTruthy()
    fireEvent.click(screen.getByRole('button',{name:'Load more'}))
    expect(await screen.findByRole('option',{name:/Two/})).toBeTruthy()
    expect(screen.getByRole('option',{name:/One/})).toBeTruthy()
    fireEvent.click(screen.getByRole('option',{name:/Two/}))
    expect(onChoose).toHaveBeenCalledWith({key:'2',label:'Two',value:'two'})
  })


  it('supports combobox arrow navigation and Enter selection',async()=>{
    const onChoose=vi.fn(),source:AsyncFieldOptionSource<string>={search:vi.fn(async()=>({items:[{key:'1',label:'One',value:'one'},{key:'2',label:'Two',value:'two'}],nextCursor:null}))}
    render(<AsyncOptionSearch source={source} onChoose={onChoose} label="Entity"/>)
    const input=screen.getByRole('combobox')
    const first=await screen.findByRole('option',{name:'One'})
    fireEvent.keyDown(input,{key:'ArrowDown'})
    expect(input.getAttribute('aria-activedescendant')).toBe(first.id)
    fireEvent.keyDown(input,{key:'Enter'})
    expect(onChoose).toHaveBeenCalledWith({key:'1',label:'One',value:'one'})
  })

  it('uses unique result ids for multiple searches',async()=>{
    const source:AsyncFieldOptionSource={search:async()=>({items:[],nextCursor:null})}
    render(<><AsyncOptionSearch source={source} onChoose={vi.fn()} label="One"/><AsyncOptionSearch source={source} onChoose={vi.fn()} label="Two"/></>)
    const inputs=screen.getAllByRole('combobox')
    expect(inputs[0]!.getAttribute('aria-controls')).toBeTruthy()
    expect(inputs[0]!.getAttribute('aria-controls')).not.toBe(inputs[1]!.getAttribute('aria-controls'))
  })

  it('surfaces loader failures accessibly',async()=>{
    const source:AsyncFieldOptionSource={search:async()=>{throw new Error('lookup failed')}}
    render(<AsyncOptionSearch source={source} onChoose={vi.fn()} label="Media"/>)
    const alert=await screen.findByRole('alert')
    expect(alert.textContent).toContain('lookup failed')
    expect(alert.getAttribute('tabindex')).toBe('0')
  })
})
