import { ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useSearchSuggestion } from './searchSuggestion'

// Keep a list page's current page and search term in the URL query so the
// browser Back button restores them. Without this a list keeps page in local
// state and remounts at page 1 after visiting a detail page and going Back.
//
// Usage in a list view:
//   const { page, search, setPage, setSearch } = useListQuery({ onChange: loadRows })
//   goToPage(p)      -> setPage(p); loadRows()
//   debouncedSearch  -> setSearch(search.value); loadRows()
//   filter reset     -> setPage(1); loadRows()
//
// Options:
//   onChange   callback run when the query changes from outside (Back/Forward,
//              deep link) so the list reloads at the restored page.
//   extraKeys  optional extra query keys to persist (e.g. ['typeid']); each
//              gets a ref exposed under the returned `extras` object.
//
// SPELLING SUGGESTIONS come along for free, because this composable already
// owns the search term. A list reports how many rows it got and gets back a
// suggestion to offer when that number is zero:
//
//   const { search, suggestion, reportCount } = useListQuery({ onChange: load })
//   // at the end of load():  reportCount(rows.value.length)
//   // in the empty state:    <SearchSuggestion :suggestion="suggestion"
//   //                                          @pick="applySuggestion" />
//
// A list that never calls reportCount simply never offers one, which is what
// every list did before.
export function useListQuery(options = {}) {
  const route = useRoute()
  const router = useRouter()
  const onChange = options.onChange || (() => {})
  const extraKeys = options.extraKeys || []

  const page = ref(parseInt(route.query.page, 10) || 1)
  const search = ref(route.query.q || '')

  const extras = {}
  for (const key of extraKeys) {
    extras[key] = ref(route.query[key] || '')
  }

  // Build the next query. Keep unrelated keys intact. Write page only when > 1
  // and search only when non-empty so the clean state is a bare path.
  function buildQuery() {
    const query = { ...route.query }
    if (page.value > 1) query.page = String(page.value)
    else delete query.page
    if (search.value) query.q = search.value
    else delete query.q
    for (const key of extraKeys) {
      if (extras[key].value) query[key] = String(extras[key].value)
      else delete query[key]
    }
    return query
  }

  // replace (not push) so paging does not spam history; Back leaves the list.
  function syncUrl() {
    const query = buildQuery()
    const current = route.query
    const keys = new Set([...Object.keys(query), ...Object.keys(current)])
    let same = true
    for (const key of keys) {
      if (String(query[key] ?? '') !== String(current[key] ?? '')) {
        same = false
        break
      }
    }
    if (!same) router.replace({ query })
  }

  function setPage(newPage) {
    page.value = newPage
    syncUrl()
  }

  // Changing the search resets to page 1.
  function setSearch(term) {
    search.value = term
    page.value = 1
    syncUrl()
  }

  // Changing an extra filter resets to page 1.
  function setExtra(key, value) {
    extras[key].value = value
    page.value = 1
    syncUrl()
  }

  // Re-sync refs when the query changes from outside (Back/Forward, deep link).
  // Fire onChange only when page/search/extras actually changed so a self
  // syncUrl() call does not trigger a redundant reload.
  watch(() => route.query, (newQuery) => {
    const newPage = parseInt(newQuery.page, 10) || 1
    const newSearch = newQuery.q || ''
    let changed = false
    if (newPage !== page.value) { page.value = newPage; changed = true }
    if (newSearch !== search.value) { search.value = newSearch; changed = true }
    for (const key of extraKeys) {
      const newValue = newQuery[key] || ''
      if (newValue !== String(extras[key].value || '')) {
        extras[key].value = newValue
        changed = true
      }
    }
    if (changed) onChange()
  })

  const { suggestion, checkSuggestion, clearSuggestion } = useSearchSuggestion()

  // Called by the list after a fetch. Zero rows for a non-empty search is the
  // only moment a suggestion is worth asking for.
  function reportCount(count) {
    if (count > 0 || !search.value) clearSuggestion()
    else checkSuggestion(search.value)
  }

  return {
    page, search, extras, setPage, setSearch, setExtra,
    suggestion, reportCount,
  }
}
