import { useCallback, useEffect, useId, useRef, useState, type KeyboardEvent } from 'react'
import { Button, Input } from '@platform-modules/ui-primitives'

export interface AsyncFieldOption<T=unknown> { readonly key:string;readonly label:string;readonly value:T;readonly description?:string }
export interface AsyncFieldOptionPage<T=unknown> { readonly items:readonly AsyncFieldOption<T>[];readonly nextCursor:string|null }
export interface AsyncFieldOptionRequest { readonly query:string;readonly cursor:string|null;readonly limit:number;readonly signal:AbortSignal }
export interface AsyncFieldOptionSource<T=unknown> { search(request:AsyncFieldOptionRequest):Promise<AsyncFieldOptionPage<T>> }
export interface AsyncOptionSearchProps<T=unknown> {
  readonly source:AsyncFieldOptionSource<T>
  readonly onChoose:(option:AsyncFieldOption<T>)=>void
  readonly selectedKeys?:readonly string[]
  readonly label:string
  readonly placeholder?:string
  readonly pageSize?:number
  readonly minQueryLength?:number
  readonly disabled?:boolean
}

export function AsyncOptionSearch<T>({source,onChoose,selectedKeys=[],label,placeholder,pageSize=25,minQueryLength=0,disabled=false}:AsyncOptionSearchProps<T>){
  const [query,setQuery]=useState(''),[items,setItems]=useState<readonly AsyncFieldOption<T>[]>([]),[cursor,setCursor]=useState<string|null>(null),[loading,setLoading]=useState(false),[error,setError]=useState<string|null>(null),[announcement,setAnnouncement]=useState(''),[activeIndex,setActiveIndex]=useState(-1)
  const requestRef=useRef<AbortController|null>(null),requestSerial=useRef(0),instanceId=useId(),resultsId=`${instanceId}-results`
  const run=useCallback(async(nextQuery:string,nextCursor:string|null,append:boolean)=>{
    requestRef.current?.abort();const controller=new AbortController();requestRef.current=controller;const serial=++requestSerial.current
    if(nextQuery.length<minQueryLength){setItems([]);setActiveIndex(-1);setCursor(null);setError(null);setLoading(false);setAnnouncement(`Enter at least ${minQueryLength} characters`);return}
    setLoading(true);setError(null)
    try{const page=await source.search({query:nextQuery,cursor:nextCursor,limit:pageSize,signal:controller.signal});if(controller.signal.aborted||serial!==requestSerial.current)return;setItems((current)=>append?Object.freeze([...current,...page.items]):Object.freeze([...page.items]));if(!append)setActiveIndex(-1);setCursor(page.nextCursor);setAnnouncement(`${page.items.length} option${page.items.length===1?'':'s'} loaded${page.nextCursor?' with more available':''}`)}catch(cause){if(controller.signal.aborted)return;setError(cause instanceof Error?cause.message:String(cause));setAnnouncement('Option search failed')}finally{if(!controller.signal.aborted&&serial===requestSerial.current)setLoading(false)}
  },[minQueryLength,pageSize,source])
  useEffect(()=>{void run(query,null,false);return()=>{requestRef.current?.abort()}},[query,run])
  const onKeyDown=(event:KeyboardEvent<HTMLInputElement>)=>{
    if(!items.length)return
    if(event.key==='ArrowDown'){event.preventDefault();setActiveIndex((current)=>Math.min(current+1,items.length-1));return}
    if(event.key==='ArrowUp'){event.preventDefault();setActiveIndex((current)=>current<=0?0:current-1);return}
    if(event.key==='Home'){event.preventDefault();setActiveIndex(0);return}
    if(event.key==='End'){event.preventDefault();setActiveIndex(items.length-1);return}
    if(event.key==='Escape'){setActiveIndex(-1);return}
    if(event.key==='Enter'&&activeIndex>=0){event.preventDefault();const option=items[activeIndex];if(option)onChoose(option)}
  }
  return <section className="fields-async-options" style={{containerType:'inline-size',containerName:'fields-async-options'}} aria-label={`${label} option search`}>
    <style>{`.fields-async-options__results{display:grid;gap:.35rem;padding:0;list-style:none}.fields-async-options__option{display:grid;gap:.15rem;text-align:left;width:100%;cursor:pointer}.fields-async-options__option[data-active="true"]{outline:2px solid currentColor;outline-offset:2px}@container fields-async-options (max-width:30rem){.fields-async-options__option{min-height:2.75rem}}`}</style>
    <label>{label}<Input role="combobox" aria-expanded={items.length>0} aria-controls={resultsId} aria-autocomplete="list" aria-activedescendant={activeIndex>=0?`${resultsId}-option-${activeIndex}`:undefined} disabled={disabled} placeholder={placeholder} value={query} onKeyDown={onKeyDown} onChange={(event)=>setQuery(event.currentTarget.value)}/></label>
    <div role="status" aria-live="polite">{announcement}</div>
    {error?<div role="alert" tabIndex={0}>{error}</div>:null}
    <ul id={resultsId} role="listbox" className="fields-async-options__results" aria-busy={loading||undefined}>
      {items.map((option,index)=><li key={option.key} id={`${resultsId}-option-${index}`} role="option" aria-selected={selectedKeys.includes(option.key)} data-active={activeIndex===index||undefined} className="fields-async-options__option" onMouseDown={(event)=>event.preventDefault()} onClick={()=>onChoose(option)}><span>{option.label}</span>{option.description?<small>{option.description}</small>:null}</li>)}
    </ul>
    {cursor?<Button type="button" tone="surface" disabled={disabled||loading} aria-busy={loading||undefined} onClick={()=>void run(query,cursor,true)}>{loading?'Loading…':'Load more'}</Button>:null}
  </section>
}
