"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import { IoCloseOutline } from "react-icons/io5";
import { useDebounced } from "@/hooks/useDebounced";
import { SortDropdown, type SortOption } from "@/components/SortDropdown";
import { FilterDropdown, type FilterGroup } from "@/components/FilterDropdown";
import { IconButton } from "@/components/IconButton";

const EMPTY_FILTERS: Record<string, string[]> = {};

type Props<T extends string> = {
  initialQuery: string;
  initialSort?: T;
  initialPage?: number;
  defaultSort: T;
  placeholder: string;
  sortOptions: SortOption<T>[];
  sortAriaLabel: string;
  sortMenuClassName?: string;
  filterGroups?: FilterGroup[];
  initialFilters?: Record<string, string[]>;
  filterMenuClassName?: string;
};

export function SearchBar<T extends string>({
  initialQuery,
  initialSort,
  initialPage = 1,
  defaultSort,
  placeholder,
  sortOptions,
  sortAriaLabel,
  sortMenuClassName,
  filterGroups,
  initialFilters,
  filterMenuClassName,
}: Props<T>) {
  const sort0 = initialSort ?? defaultSort;
  const filters0 = initialFilters ?? EMPTY_FILTERS;

  const router = useRouter();
  const pathname = usePathname();

  const [q, setQ] = useState(initialQuery);
  const debouncedQ = useDebounced(q, 250);

  const [sort, setSort] = useState<T>(sort0);
  const [filters, setFilters] = useState<Record<string, string[]>>(filters0);

  const prevPropsRef = useRef({ initialQuery, initialSort: sort0, initialFilters: filters0 });
  const lastPushedQRef = useRef(initialQuery);

  useEffect(() => {
    if (initialQuery !== lastPushedQRef.current) {
      setQ(initialQuery);
    }
    lastPushedQRef.current = initialQuery;
  }, [initialQuery]);

  useEffect(() => {
    setSort(sort0);
  }, [sort0]);

  useEffect(() => {
    setFilters(filters0);
  }, [filters0]);

  const handleFilterChange = useCallback((param: string, selected: string[]) => {
    setFilters((prev) => ({ ...prev, [param]: selected }));
  }, []);

  const filtersKey = useMemo(
    () => JSON.stringify(filters, Object.keys(filters).sort()),
    [filters],
  );
  const filters0Key = useMemo(
    () => JSON.stringify(filters0, Object.keys(filters0).sort()),
    [filters0],
  );

  const nextUrl = useMemo(() => {
    const params = new URLSearchParams();

    const normalizedQ = debouncedQ.trim();
    const q0 = (initialQuery ?? "").trim();

    const qChanged = normalizedQ !== q0;
    const sortChanged = sort !== sort0;
    const filtersChanged = filtersKey !== filters0Key;

    if (normalizedQ) params.set("q", normalizedQ);
    if (sort !== defaultSort) params.set("sort", sort);

    for (const [param, selected] of Object.entries(filters)) {
      if (selected.length > 0) params.set(param, selected.join(","));
    }

    if (!qChanged && !sortChanged && !filtersChanged && initialPage > 1) {
      params.set("page", String(initialPage));
    }

    const qs = params.toString();
    return qs ? `${pathname}?${qs}` : pathname;
  }, [debouncedQ, initialQuery, initialPage, sort0, defaultSort, pathname, sort, filtersKey, filters0Key, filters]);

  useEffect(() => {
    const propsChanged =
      prevPropsRef.current.initialQuery !== initialQuery ||
      prevPropsRef.current.initialSort !== sort0 ||
      JSON.stringify(prevPropsRef.current.initialFilters) !== JSON.stringify(filters0);

    prevPropsRef.current = { initialQuery, initialSort: sort0, initialFilters: filters0 };

    if (propsChanged) {
      return;
    }

    const params = new URLSearchParams();
    const q0 = (initialQuery ?? "").trim();
    if (q0) params.set("q", q0);
    if (sort0 && sort0 !== defaultSort) params.set("sort", sort0);
    for (const [param, selected] of Object.entries(filters0)) {
      if (selected.length > 0) params.set(param, selected.join(","));
    }
    if (initialPage > 1) params.set("page", String(initialPage));
    const qs = params.toString();
    const currentUrlFromProps = qs ? `${pathname}?${qs}` : pathname;

    if (nextUrl !== currentUrlFromProps) {
      lastPushedQRef.current = debouncedQ.trim();
      router.replace(nextUrl, { scroll: false });
    }
  }, [nextUrl, debouncedQ, initialQuery, sort0, defaultSort, initialPage, pathname, router, filters0]);

  return (
    <div className="flex items-center gap-3">
      <input
        value={q}
        onChange={(e) => setQ(e.target.value)}
        placeholder={placeholder}
        className="no-ring relative z-10 w-full rounded-xl border border-lightline bg-lightbg px-6 py-3 text-base text-lighttext shadow-lg transition-all duration-200 ease-in-out hover:border-lightline-hover hover:text-trptkblue focus:border-lightline-focus dark:border-darkline dark:bg-darkbg dark:text-darktext dark:hover:border-darkline-hover dark:hover:text-white dark:focus:border-darkline-focus"
        aria-label={placeholder}
      />

      {q || Object.values(filters).some((arr) => arr.length > 0) ? (
        <IconButton
          onClick={() => {
            setQ("");
            setFilters(Object.fromEntries(Object.keys(filters).map((k) => [k, []])));
          }}
          className="text-lg"
          aria-label="Clear search and filters"
        >
          <IoCloseOutline />
        </IconButton>
      ) : null}

      {filterGroups && filterGroups.length > 0 ? (
        <FilterDropdown
          groups={filterGroups}
          values={filters}
          onChange={handleFilterChange}
          menuClassName={filterMenuClassName}
        />
      ) : null}

      <SortDropdown
        options={sortOptions}
        value={sort}
        onChange={setSort}
        ariaLabel={sortAriaLabel}
        menuClassName={sortMenuClassName}
      />
    </div>
  );
}
