"use client";

import { SearchIcon, XIcon } from "lucide-react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useRef, useState, useTransition } from "react";

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
} from "@/components/ui/input-group";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";

/**
 * Updates one or more query parameters and resets pagination, so filters,
 * search and sorting are shareable URLs and survive a reload.
 */
function useQueryUpdater() {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const [pending, startTransition] = useTransition();

  const update = (changes: Record<string, string | null>) => {
    const params = new URLSearchParams(searchParams.toString());
    for (const [key, value] of Object.entries(changes)) {
      if (value) params.set(key, value);
      else params.delete(key);
    }
    params.delete("page");
    const query = params.toString();
    startTransition(() => {
      router.replace(query ? `${pathname}?${query}` : pathname, {
        scroll: false,
      });
    });
  };

  return { update, pending, searchParams };
}

export function SearchInput({
  placeholder = "Search…",
  param = "q",
  className,
}: {
  placeholder?: string;
  param?: string;
  className?: string;
}) {
  const { update, pending, searchParams } = useQueryUpdater();
  const current = searchParams.get(param) ?? "";
  const [value, setValue] = useState(current);
  const [syncedFrom, setSyncedFrom] = useState(current);
  const timer = useRef<ReturnType<typeof setTimeout>>(undefined);

  // Follow back/forward navigation, without clobbering what's being typed.
  if (current !== syncedFrom) {
    setSyncedFrom(current);
    if (current !== value.trim()) setValue(current);
  }

  const onChange = (next: string) => {
    setValue(next);
    clearTimeout(timer.current);
    timer.current = setTimeout(() => update({ [param]: next.trim() || null }), 300);
  };

  return (
    <InputGroup className={cn("w-full sm:w-72", className)}>
      <InputGroupAddon>
        {pending ? <Spinner /> : <SearchIcon />}
      </InputGroupAddon>
      <InputGroupInput
        type="search"
        value={value}
        placeholder={placeholder}
        aria-label={placeholder}
        onChange={(event) => onChange(event.target.value)}
        onKeyDown={(event) => {
          if (event.key === "Escape" && value) onChange("");
        }}
        className="[&::-webkit-search-cancel-button]:hidden"
      />
      {value && (
        <InputGroupAddon align="inline-end">
          <InputGroupButton
            size="icon-xs"
            aria-label="Clear search"
            onClick={() => onChange("")}
          >
            <XIcon />
          </InputGroupButton>
        </InputGroupAddon>
      )}
    </InputGroup>
  );
}

export type FilterOption = { value: string; label: string };

export function FilterSelect({
  param,
  options,
  allLabel,
  defaultValue,
  className,
  label,
}: {
  param: string;
  options: FilterOption[];
  /** Label for "no filter". Omit for params that always have a value, like sort. */
  allLabel?: string;
  defaultValue?: string;
  className?: string;
  label: string;
}) {
  const { update, searchParams } = useQueryUpdater();
  const value = searchParams.get(param) ?? defaultValue ?? "__all";

  return (
    <Select
      value={value}
      onValueChange={(next) =>
        update({ [param]: next === "__all" || next === defaultValue ? null : next })
      }
    >
      <SelectTrigger aria-label={label} className={cn("min-w-36", className)}>
        <SelectValue placeholder={allLabel ?? label} />
      </SelectTrigger>
      <SelectContent position="popper" align="start">
        {allLabel && <SelectItem value="__all">{allLabel}</SelectItem>}
        {options.map((option) => (
          <SelectItem key={option.value} value={option.value}>
            {option.label}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  );
}
