import { useDispatch, useSelector } from "react-redux";
import { useCallback, useEffect, useRef, useState } from "react";
import {
  resetSearch,
  selectSearch,
  selectTemp,
  setSearchData,
  setTemp,
} from "@/utils/store/features/data/search.slice";
import { usePathname, useSearchParams } from "next/navigation";
import { setNotification } from "@/utils/store/features/ui/notification.slice";

/**
 * Hook to handle search.
 * Feature: search with debounce
 *
 * Created with ❤️ by [Dimas Vindyawan]
 *
 *
 * @param {Object} props
 * @param {string} props.term
 */
export const useSearch = (props) => {
  const dispatch = useDispatch();

  const pathname = usePathname();
  const searchParams = useSearchParams();

  const debounceTimeout = useRef(null);
  const callback = useRef({
    searchFn: null,
    setDataFn: null,
  });

  const [initialData, setInitialData] = useState([]);
  const [search, setSearch] = useState({
    key: "",
    isLoading: false,
    error: false,
  });
  const [isSearch, setIsSearch] = useState(false);

  const temp = useSelector(selectTemp);
  const searchData = useSelector(selectSearch);

  const setSearchFn = (cb) => {
    if (cb && typeof cb === "function") return (callback.current.searchFn = cb);
  };
  const setSetDataFn = (cb) => {
    if (cb && typeof cb === "function")
      return (callback.current.setDataFn = cb);
  };

  const setFallbackFn = (cb) => {
    if (cb && typeof cb === "function")
      return (callback.current.fallbackFn = cb);
  };

  const resetTimeout = useCallback(() => {
    if (!debounceTimeout.current) return;

    clearTimeout(debounceTimeout.current);
    debounceTimeout.current = null;
  }, [debounceTimeout]);

  const getDataSearch = useCallback(
    async (value = null) => {
      try {
        const params = new URLSearchParams(searchParams);

        if (!value) return;

        setSearch({ ...search, isLoading: true });
        const key = value || search?.key;
        const result = [];

        params.set("search", value);
        window.history.replaceState({}, "", `${pathname}?${params.toString()}`);

        if (!searchData?.[key]) {
          // Operation get data
          const data = await callback.current.searchFn(key);

          result.push(...data);
        } else {
          result.push(...searchData[key]);
        }

        dispatch(setTemp(initialData));
        dispatch(setSearchData({ key, value: result }));
        setSearch({ ...search, isLoading: false });

        callback.current.setDataFn(result);
      } catch (error) {
        setSearch({ ...search, isLoading: false, error: true });
        dispatch(
          setNotification({
            status: true,
            type: "error",
            message: `Failed to search ${props?.term || ""}`,
            timeout: 3000,
          })
        );
      }
    },
    [
      searchData,
      search,
      dispatch,
      callback,
      initialData,
      searchParams,
      pathname,
      props,
    ]
  );

  const handleSubmit = async (e) => {
    e.preventDefault();
    resetTimeout();

    if (!search.key) return;

    getDataSearch(search.key);
    setIsSearch(true);
  };

  const cancel = useCallback(() => {
    if (temp.length > 0) callback.current.setDataFn(temp);
    else if (
      callback.current.fallbackFn &&
      typeof callback.current.fallbackFn === "function"
    ) {
      callback.current.fallbackFn();
    }
    dispatch(resetSearch());
    setSearch({ key: "", isLoading: false });
    setIsSearch(false);

    const params = new URLSearchParams(searchParams);
    params.delete("search");

    window.history.replaceState({}, "", `${pathname}?${params.toString()}`);
  }, [temp, dispatch, pathname, searchParams]);

  const handleInput = useCallback(
    (e) => {
      const value = e.target.value;
      setSearch({ ...search, key: value });

      resetTimeout();
      debounceTimeout.current = setTimeout(() => {
        if (!value) {
          cancel();

          return;
        }

        getDataSearch(value);
        setIsSearch(true);
      }, 1000);
    },
    [resetTimeout, getDataSearch, search, cancel]
  );

  useEffect(() => {
    const params = new URLSearchParams(searchParams);
    if (params.get("search")) {
      setIsSearch(true);

      // on search, delete page from params
      params.delete("page");
      window.history.replaceState({}, "", `${pathname}?${params.toString()}`);
    }
  }, [searchParams, pathname]);

  return {
    handleSubmit, // handle submit
    handleInput, // handle input search
    isLoading: search.isLoading, // is loading on getting data from server
    isSearch, // is on search mode
    isError: search.error, // is error on getting data from server
    setInitialData, // setInitial data from display
    setSearchFn, // search function
    setSetDataFn, // setData to display
    setFallbackFn, // fallback is use when data not found on temp of initialData
    cancel, // cancel search
  };
};
