/**
 * Shared search/debounce/in-flight-token plumbing for async option
 * compositions (AsyncSelectPicker, …).
 *
 *   const { query, results, isLoading, onInput, dispose } = useAsyncSearch({
 *     onSearch: (q) => fetch(`/api/users?q=${q}`).then(r => r.json()),
 *     debounceMs: 250,
 *     minSearchLength: 1,
 *     initialOptions: [],
 *   })
 *
 * The composable is intentionally HTTP-agnostic — `onSearch` is just a
 * Promise-returning callback supplied by the consumer.
 */
import { onBeforeUnmount, ref, shallowRef } from "vue"
import type { Ref } from "vue"
import type { SelectOptionLike } from "@/definitions/shared/select-options"

export interface UseAsyncSearchOptions {
  onSearch?: (query: string) => Promise<SelectOptionLike[]>
  initialOptions?: SelectOptionLike[]
  minSearchLength?: number
  debounceMs?: number
}

export interface UseAsyncSearchReturn {
  query: Ref<string>
  results: Ref<SelectOptionLike[]>
  isLoading: Ref<boolean>
  /** Trigger a search for the current query (respecting debounce). */
  trigger: (next: string) => void
  /** Synchronously cancel any in-flight token + pending debounce. */
  dispose: () => void
}

export function useAsyncSearch(
  options: UseAsyncSearchOptions,
): UseAsyncSearchReturn {
  const query = ref<string>("")
  const results = shallowRef<SelectOptionLike[]>(
    options.initialOptions ? [...options.initialOptions] : [],
  )
  const isLoading = ref<boolean>(false)

  let token = 0
  let timer: ReturnType<typeof setTimeout> | null = null

  const minLen = options.minSearchLength ?? 1
  const debounceMs = options.debounceMs ?? 250

  function clearTimer(): void {
    if (timer !== null) {
      clearTimeout(timer)
      timer = null
    }
  }

  function runSearch(q: string): void {
    const fn = options.onSearch
    const myToken = ++token
    if (!fn || q.length < minLen) {
      isLoading.value = false
      results.value = options.initialOptions
        ? [...options.initialOptions]
        : []
      return
    }
    isLoading.value = true
    fn(q)
      .then((next) => {
        if (myToken !== token) {
          return
        }
        results.value = Array.isArray(next) ? next : []
        isLoading.value = false
      })
      .catch(() => {
        if (myToken !== token) {
          return
        }
        results.value = []
        isLoading.value = false
      })
  }

  function trigger(next: string): void {
    query.value = next
    clearTimer()
    if (debounceMs <= 0) {
      runSearch(next)
      return
    }
    timer = setTimeout(() => {
      timer = null
      runSearch(next)
    }, debounceMs)
  }

  function dispose(): void {
    clearTimer()
    token++ // invalidate any in-flight promise
    isLoading.value = false
  }

  onBeforeUnmount(dispose)

  return { query, results, isLoading, trigger, dispose }
}
