"use client";

import React, {
  memo,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import styles from "@/components/administrator/form/form.module.css";
import { useOutsideClick } from "@/hooks/useOutsideClick";

// icons
import { IoCloseOutline } from "react-icons/io5";
import { useSearch } from "@/hooks/useSearch";
import Loader from "@/components/web/Loader/loader";

/**
 * A generic input component with a label and a visible password toggle.
 *
 * if use data as static, you must include all data and set selected if you want to use it
 * if on async data, you must set async to true and set asyncData.call function to get data asyncronously by keyword
 * and asyncData.init function to get data asyncronously for the first time by get props definition
 *
 * On createNew.status = true, set createNewFn with args (data[]) to create new data.
 * Data is string splited by comma and trimmed
 *
 * @type {React.FC<React.InputHTMLAttributes<HTMLInputElement> & {
 *   label: string;
 *   labelclassname?: string;
 *   async: boolean;
 *   asyncData: {
 *      init: Function;
 *      call: Function;
 *   };
 *   createNew: {
 *      status: boolean;
 *      createNewFn: Function;
 *      label: string;
 *   };
 *   data: any[];
 *   icon?: React.ReactNode;
 *   iconAction?: Function;
 *   display?: string;
 *   get: string|number;
 *   onChange?: Function;
 *   selected: string[];
 * }>}
 */
const MainComponent = (props) => {
  const inputref = useRef(null);
  const containerref = useRef(null);
  const [search, setSearch] = useState(
    props.async ? "Waiting for data..." : ""
  );
  const [selectedIndex, setSelectedIndex] = useState(0);
  const [selectedData, setSelectedData] = useState(
    props?.data?.filter((item) => props?.selected?.includes(item[props.get])) ||
      []
  );
  const [showSuggest, setShowSuggest] = useState(false);
  const [top, setTop] = useState(0);
  const [asyncData, setAsyncData] = useState([]);

  const {
    handleInput,
    isLoading,
    isSearch,
    setInitialData,
    setSearchFn,
    setSetDataFn,
  } = useSearch();

  const data = useMemo(() => {
    if (!props.async) {
      const filtered = props?.data?.filter(
        (item) =>
          item?.name?.toLowerCase()?.includes(search) &&
          !selectedData.map((el) => el[props.get]).includes(item[props.get])
      );

      return filtered;
    }

    return asyncData.filter(
      (item) =>
        !selectedData.map((el) => el[props.get]).includes(item[props.get])
    );
    // use for async data
  }, [search, props, selectedData, asyncData]);

  const handleAsyncData = (keyword) => {
    if (props?.asyncData?.call && typeof props?.asyncData?.call === "function")
      return props.asyncData.call(keyword);

    return [];
  };

  // intercept setSelected data
  const setSelected = useCallback(
    (data) => {
      if (!data) return;

      const intercept = (_data) => {
        // listen on changes
        if (props.onChange && typeof props.onChange === "function")
          props.onChange(_data.map((item) => item[props.get]));
      };

      if (data && typeof data === "function")
        return setSelectedData((prev) => {
          const result = data(prev);
          intercept(result);

          return result;
        });

      setSelectedData((prev) => {
        const result = [...prev, data];
        intercept(result);

        return result;
      });
    },
    [setSelectedData, props]
  );

  const getSelectedData = useCallback(async () => {
    try {
      if (
        !props?.asyncData?.init ||
        typeof props?.asyncData?.init !== "function"
      )
        return;

      const data = await props.asyncData.init(props.selected);
      setSelectedData(data);
    } catch (error) {
    } finally {
      setTimeout(() => {
        // on first time
        setSearch((prev) => (prev === "Waiting for data..." ? "" : prev));
      });
    }
  }, [props]);

  useEffect(() => {
    if (!props.async) {
      setSearch("");
      return;
    }

    getSelectedData();
  }, [getSelectedData, props]);

  useEffect(() => {
    const height = containerref.current?.getBoundingClientRect().height + 10;
    setTop(height);
  }, [containerref, selectedData]);

  useEffect(() => {
    if (props?.data?.length > 0) {
      setSelectedData(
        props?.data?.filter((item) =>
          props?.selected?.includes(item[props.get])
        )
      );
    }
  }, [props]);

  return (
    <div
      className={styles.input_container}
      ref={containerref}>
      <div className={styles.input_header}>
        <p className={`${styles.label} ${props.labelclassname}`}>
          {props.label}
        </p>
      </div>
      <div
        className={styles.input_multiple_container}
        onClick={(e) => {
          e.stopPropagation();
          inputref.current.focus();
          setShowSuggest(true);
        }}>
        {selectedData.map((item, index) => (
          <div
            className={styles.input_multiple_selected}
            key={index}
            onClick={(e) => {
              e.stopPropagation();
              setSelected((prev) => prev.filter((id) => id !== item._id));
            }}>
            {item[props.display]}
            <IoCloseOutline
              className={styles.input_multiple_close}
              onClick={() => {
                setSelected((prev) =>
                  prev.filter((data) => data[props.get] !== item[props.get])
                );
              }}
            />
          </div>
        ))}
        <input
          type="text"
          ref={inputref}
          value={search}
          className={styles.input_multiple}
          onChange={(e) => setSearch(e.target.value)}
          onInput={(e) => {
            if (!props?.async) return;
            handleInput(e);
          }}
          onFocus={() => {
            if (!props?.async) return;
            if (isSearch) setInitialData([]);

            setSearchFn(handleAsyncData);
            setSetDataFn(setAsyncData);
          }}
        />
        <div className={styles.input_multiple_icon_container}>
          <span
            className={styles.input_multiple_icon}
            style={!props.iconAction ? { cursor: "default" } : {}}
            onClick={(e) => {
              e.stopPropagation();
              if (props.iconAction && typeof props.iconAction === "function")
                props.iconAction();
            }}>
            {props.icon}
          </span>
        </div>
      </div>
      {showSuggest && (
        <Suggestion
          isLoading={isLoading}
          isSearch={isSearch}
          data={data}
          selectedIndex={selectedIndex}
          setSelectedIndex={setSelectedIndex}
          selectedData={selectedData}
          setSelectedData={setSelected}
          setShowSuggest={(visibility) => {
            setShowSuggest(visibility);

            if (!visibility) {
              setSearch("");
              setAsyncData([]);
            }
          }}
          display={props.display}
          get={props.get}
          top={top}
          setSearch={setSearch}
          search={search}
          createNew={props.createNew}
        />
      )}
    </div>
  );
};

const Suggestion = ({
  isLoading,
  isSearch,
  data,
  selectedIndex,
  setSelectedIndex,
  selectedData,
  setSelectedData,
  setShowSuggest,
  display,
  get,
  top,
  setSearch,
  search,
  createNew = {
    status: false,
    createNewFn: () => {},
    label: "",
  },
}) => {
  const itemsref = useRef([]);
  const suggestion = useRef(null);

  useEffect(() => {
    const onKeyDown = (e) => {
      const key = [
        {
          on: "ArrowDown",
          call: () => {
            e.preventDefault();
            setSelectedIndex((prev) => {
              const current = prev > data.length - 1 ? prev : prev + 1;
              itemsref.current[current]?.focus();
              return current;
            });
          },
        },
        {
          on: "ArrowUp",
          call: () => {
            e.preventDefault();
            setSelectedIndex((prev) => {
              const current = prev === 0 ? prev : prev - 1;
              itemsref.current[current]?.focus();
              return current;
            });
          },
        },
        {
          on: "Enter",
          call: () => {
            e.preventDefault();
            const current = data[selectedIndex];

            if (!current) return;

            setSearch("");
            setSelectedData((prev) => [...prev, current]);
          },
        },
        {
          on: "Backspace",
          call: () => {
            if (search.length > 0) return;

            setSelectedData((prev) => prev.slice(0, -1));
          },
        },
      ];

      // e.preventDefault();
      return key.find((item) => item.on === e.key)?.call();
    };

    document.addEventListener("keydown", onKeyDown);
    return () => document.removeEventListener("keydown", onKeyDown);
  }, [
    data,
    setSelectedIndex,
    selectedIndex,
    setSelectedData,
    setSearch,
    search,
  ]);

  useOutsideClick(suggestion, () => {
    setSelectedIndex(0);
    setShowSuggest(false);
  });

  return (
    <div
      className={styles.input_suggestion}
      style={{ top: top }}
      ref={suggestion}>
      {isLoading ? (
        // <span className={styles.no_data}>
        <span className={styles.suggestion_load}>
          <Loader /> Getting data ...
        </span>
      ) : (
        // </span>
        <>
          {(data?.length === 0 || !data) && (
            <p className={styles.no_data}>
              No data found
              {createNew.status && !!search && isSearch && (
                <span
                  onClick={() => {
                    createNew.createNewFn(
                      search?.split(",").map((item) => item.trim()),
                      selectedData.map((item) => item?.[get]),
                      setSearch
                    );
                  }}>
                  {createNew.label || "Create new"}
                </span>
              )}
            </p>
          )}
          {data?.map((item, index) => {
            return (
              <div
                ref={(el) => (itemsref.current[index] = el)}
                tabIndex={index + 1}
                className={`${styles.input_suggestion_item} ${
                  selectedIndex === index && styles.selected
                }`}
                key={index}
                onClick={() => {
                  setSelectedData((prev) => [...prev, item]);
                }}>
                <p>{item[display]}</p>
              </div>
            );
          })}
        </>
      )}
    </div>
  );
};

const InputMultiple = memo(MainComponent);
export default InputMultiple;
