import type { WritableComputedRef } from 'vue'
import type { LocationQuery, LocationQueryRaw } from 'vue-router'
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'

function buildQuery(
	current: LocationQuery,
	key: string,
	value: string | undefined,
): LocationQueryRaw {
	const query: LocationQueryRaw = { ...current }
	if (value === undefined) delete query[key]
	else query[key] = value
	return query
}

type QueryDefaults<T> = { [K in keyof T]?: T[K] }
type QueryResult<T, D extends QueryDefaults<T>> = {
	[K in keyof T]: WritableComputedRef<K extends keyof D ? NonNullable<T[K]> : T[K] | undefined>
}

/**
 * Reactive URL query param bindings.
 * Each key becomes a writable computed that reads/writes a single query param.
 *
 * @example
 * const q = useQuery<{ search: string; page: string }>({ page: '1' })
 * q.search.value = 'hello'  // → ?search=hello&page=1
 */
export function useQuery<
	T extends Record<string, string> = Record<string, string>,
	D extends QueryDefaults<T> = QueryDefaults<T>,
>(defaults?: D): QueryResult<T, D> {
	const route = useRoute()
	const router = useRouter()
	const cache: Record<string, WritableComputedRef<string | undefined>> = {}

	return new Proxy({} as QueryResult<T, D>, {
		get(_, key: string) {
			if (!(key in cache)) {
				const fallback = defaults?.[key as keyof T] as string | undefined
				cache[key] = computed({
					get: () => (route.query[key] as string | undefined) ?? fallback,
					set: value => router.replace({ query: buildQuery(route.query, key, value) }),
				})
			}
			return cache[key] as QueryResult<T, D>[keyof T]
		},
	})
}
