import { Combobox, Transition } from "@headlessui/react";
import AnchorDownIcon from "@/assets/icons/anchor-down";
import React, { Fragment, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import fetcher from "@/lib/fetcher";
import { useDebounce } from "@/hook/use-debounce";
import { useInfiniteQuery } from "@tanstack/react-query";
import { buildUrlQueryParams } from "@/utils/build-url-query-params";
import { Paginate, ParamsType } from "@/types/global";
import clsx from "clsx";
import CheckIcon from "@/assets/icons/check";
import EmptyCheckIcon from "@/assets/icons/empty-check";
import { LoadingCard } from "@/components/loading";
import { extractDataFromPagination } from "@/utils/extract-data";
import { InfiniteLoader } from "@/components/infinite-loader";
import { IconButton } from "@/components/icon-button";
import CrossIcon from "@/assets/icons/cross";
import { useMediaQuery } from "@/hook/use-media-query";

const sizes = {
  large: "py-[19px]",
  medium: "py-[11px]",
};

interface AsyncSelectFieldProps<T extends { id: number }> {
  value?: T;
  label?: string;
  onSelect: (value: T) => void;
  extractTitle: (value?: T) => string;
  extractKey: (value: T) => string | number;
  queryKey?: string;
  status?: "default" | "error" | "success";
  queryParams?: ParamsType;
  size?: keyof typeof sizes;
  disabled?: boolean;
  error?: string;
  required?: boolean;
}

export const AsyncSelect = <T extends { id: number }>({
  value,
  onSelect,
  extractKey,
  extractTitle,
  label,
  queryKey,
  status = "default",
  queryParams,
  size = "large",
  disabled,
  error,
  required,
}: AsyncSelectFieldProps<T>) => {
  const { t } = useTranslation();
  const [query, setQuery] = useState("");
  const isMobile = useMediaQuery("(max-width: 768px)");
  const [isMobileDrawerOpen, setIsMobileDrawerOpen] = useState(false);
  const [isDesktopOpen, setIsDesktopOpen] = useState(false);
  const searchInputRef = useRef<HTMLInputElement>(null);
  const debouncedQuery = useDebounce(query);
  const listOpen = isMobile ? isMobileDrawerOpen : isDesktopOpen;

  const {
    data: options,
    isLoading,
    hasNextPage,
    fetchNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: [queryKey, debouncedQuery, queryParams],
    queryFn: ({ pageParam }) =>
      fetcher<Paginate<T>>(
        buildUrlQueryParams(queryKey as string, {
          search: debouncedQuery || undefined,
          page: pageParam,
          ...queryParams,
        })
      ),
    enabled: !!queryKey && listOpen,
    getNextPageParam: (lastPage) =>
      lastPage?.meta && lastPage?.links?.next && lastPage.meta.current_page + 1,
  });

  const optionsList = extractDataFromPagination(options?.pages);

  useEffect(() => {
    if (!listOpen) {
      setQuery("");
      return;
    }
    const timer = window.setTimeout(() => searchInputRef.current?.focus(), 50);
    return () => window.clearTimeout(timer);
  }, [listOpen]);

  return (
    <Combobox
      disabled={disabled}
      value={value}
      onChange={(val) => {
        if (val) onSelect(val);
        setIsMobileDrawerOpen(false);
        setIsDesktopOpen(false);
        setQuery("");
      }}
      by={(a, b) => (!!a && !!b ? extractKey(a) === extractKey(b) : a === b)}
    >
      {({ open }) => (
        <>
          <OpenStateSync open={open} isMobile={isMobile} setDesktopOpen={setIsDesktopOpen} />
          <div className="relative">
            {!!label && (
              <Combobox.Label className="text-sm">
                {t(label)}
                {required && "*"}:
              </Combobox.Label>
            )}

            <div className="relative w-full text-left sm:text-sm">
              <Combobox.Button
                type="button"
                disabled={disabled}
                className={clsx(
                  "hidden md:flex px-4 pr-10 rtl:pl-10 rtl:pr-4 w-full text-sm text-start bg-transparent rounded-2xl border appearance-none focus:outline-none focus:ring-0 items-center",
                  status === "default" && "border-gray-inputBorder focus-visible:border-primary",
                  status === "error" && "border-badge-product",
                  status === "success" && "border-green-500",
                  sizes[size],
                  disabled ? "text-gray-field cursor-not-allowed" : "cursor-pointer"
                )}
              >
                <span className={clsx("block truncate", !value && "text-gray-field")}>
                  {value ? extractTitle(value) : t("select") || "انتخاب کنید"}
                </span>
              </Combobox.Button>

              <button
                type="button"
                disabled={disabled}
                onClick={() => !disabled && setIsMobileDrawerOpen((v) => !v)}
                className={clsx(
                  "md:hidden flex items-center px-4 pr-10 w-full text-sm text-start bg-transparent rounded-2xl border border-gray-inputBorder h-11",
                  disabled && "text-gray-field cursor-not-allowed"
                )}
              >
                <span className={clsx("block truncate", !value && "text-gray-field")}>
                  {value ? extractTitle(value) : t("select") || "انتخاب کنید"}
                </span>
              </button>

              <span className="pointer-events-none absolute inset-y-0 right-0 rtl:left-1 rtl:right-auto flex items-center pr-2">
                <AnchorDownIcon className="h-5 w-5" aria-hidden="true" />
              </span>
            </div>

            {!!error && (
              <p role="alert" className="text-sm text-red mt-1">
                {error}
              </p>
            )}

            <InfiniteLoader
              hasMore={hasNextPage}
              loadMore={fetchNextPage}
              loading={isFetchingNextPage}
            >
              <Transition
                as={Fragment}
                show={isMobile ? isMobileDrawerOpen : open}
                enter="ease-out duration-300"
                enterFrom="sm:translate-y-0 translate-y-full"
                leave="transition ease-in duration-100"
                leaveFrom="opacity-100"
                leaveTo="opacity-0 sm:translate-y-0 translate-y-full"
              >
                <Combobox.Options
                  static
                  className="md:absolute fixed bottom-0 left-0 md:bottom-auto pt-4 min-h-[40vh] md:min-h-0 md:pt-0 rounded-t-2xl md:rounded-t-md md:rounded-b-md w-screen md:w-full mt-1 md:max-h-72 max-h-[80vh] overflow-auto bg-white dark:bg-dark py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm z-20"
                >
                  <div className="md:hidden flex items-center justify-between px-2 pb-2">
                    <span className="text-base">{t(label || "")}</span>
                    <IconButton onClick={() => setIsMobileDrawerOpen(false)} type="button">
                      <CrossIcon />
                    </IconButton>
                  </div>

                  <div className="sticky top-0 z-10 bg-white dark:bg-dark px-3 pb-2 pt-1 border-b border-gray-inputBorder">
                    <input
                      ref={searchInputRef}
                      type="text"
                      className="w-full px-3 py-2 text-sm rounded-xl border border-gray-inputBorder bg-transparent focus:outline-none focus:border-primary"
                      placeholder={t("search") || "جستجو..."}
                      value={query}
                      onChange={(e) => setQuery(e.target.value)}
                      onClick={(e) => e.stopPropagation()}
                      onKeyDown={(e) => e.stopPropagation()}
                    />
                  </div>

                  {isLoading ? (
                    <LoadingCard />
                  ) : !optionsList?.length ? (
                    <div className="relative cursor-default select-none py-2 px-4 text-gray-700">
                      {t("nothing.found")}
                    </div>
                  ) : (
                    optionsList.map((option, i) => (
                      <Combobox.Option
                        key={extractKey(option)}
                        className={({ active }) =>
                          `relative cursor-pointer font-medium select-none py-4 flex items-center gap-2.5 px-5 ${
                            active ? "bg-gray-100 dark:bg-gray-bold" : ""
                          } ${i !== 0 ? "border-t border-gray-inputBorder" : ""}`
                        }
                        value={option}
                      >
                        {({ selected }) => (
                          <>
                            {selected ? (
                              <div className="text-primary dark:text-white">
                                <CheckIcon />
                              </div>
                            ) : (
                              <div className="text-gray-field">
                                <EmptyCheckIcon />
                              </div>
                            )}
                            <span
                              className={`block truncate ${
                                selected ? "font-medium" : "font-normal"
                              }`}
                            >
                              {extractTitle(option)}
                            </span>
                          </>
                        )}
                      </Combobox.Option>
                    ))
                  )}
                </Combobox.Options>
              </Transition>
            </InfiniteLoader>
          </div>
        </>
      )}
    </Combobox>
  );
};

function OpenStateSync({
  open,
  isMobile,
  setDesktopOpen,
}: {
  open: boolean;
  isMobile: boolean;
  setDesktopOpen: (v: boolean) => void;
}) {
  useEffect(() => {
    if (!isMobile) setDesktopOpen(open);
  }, [open, isMobile, setDesktopOpen]);
  return null;
}
