import React, { useEffect, useState } from "react";
import { getCookie, setCookie } from "cookies-next";
import { useRouter } from "next/router";
import { useTranslation } from "react-i18next";
import InfiniteScroll from "react-infinite-scroll-component";

import api from "~utils/api";

import * as Dialog from "@radix-ui/react-dialog";

import CharacterSearchFilterBar from "~components/CharacterSearchFilterBar";
import WeaponSearchFilterBar from "~components/WeaponSearchFilterBar";
import SummonSearchFilterBar from "~components/SummonSearchFilterBar";
import JobSkillSearchFilterBar from "~components/JobSkillSearchFilterBar";

import CharacterResult from "~components/CharacterResult";
import WeaponResult from "~components/WeaponResult";
import SummonResult from "~components/SummonResult";
import JobSkillResult from "~components/JobSkillResult";

import type { SearchableObject, SearchableObjectArray } from "~types";

import "./index.scss";
import CrossIcon from "~public/icons/Cross.svg";
import cloneDeep from "lodash.clonedeep";

interface Props {
  send: (object: SearchableObject, position: number) => any;
  placeholderText: string;
  fromPosition: number;
  job?: Job;
  object: "weapons" | "characters" | "summons" | "job_skills";
  children: React.ReactNode;
}

const SearchModal = (props: Props) => {
  // Set up router
  const router = useRouter();
  const locale = router.locale;

  // Set up translation
  const { t } = useTranslation("common");

  let searchInput = React.createRef<HTMLInputElement>();
  let scrollContainer = React.createRef<HTMLDivElement>();

  const [firstLoad, setFirstLoad] = useState(true);
  const [filters, setFilters] = useState<{ [key: string]: any }>();
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const [results, setResults] = useState<SearchableObjectArray>([]);

  // Pagination states
  const [recordCount, setRecordCount] = useState(0);
  const [currentPage, setCurrentPage] = useState(1);
  const [totalPages, setTotalPages] = useState(1);

  useEffect(() => {
    if (searchInput.current) searchInput.current.focus();
  }, [searchInput]);

  function inputChanged(event: React.ChangeEvent<HTMLInputElement>) {
    const text = event.target.value;
    if (text.length) {
      setQuery(text);
    } else {
      setQuery("");
    }
  }

  function fetchResults({ replace = false }: { replace?: boolean }) {
    api
      .search({
        object: props.object,
        query: query,
        job: props.job?.id,
        filters: filters,
        locale: locale,
        page: currentPage,
      })
      .then((response) => {
        setTotalPages(response.data.total_pages);
        setRecordCount(response.data.count);

        if (replace) {
          replaceResults(response.data.count, response.data.results);
        } else {
          appendResults(response.data.results);
        }
      })
      .catch((error) => {
        console.error(error);
      });
  }

  function replaceResults(count: number, list: SearchableObjectArray) {
    if (count > 0) {
      setResults(list);
    } else {
      setResults([]);
    }
  }

  function appendResults(list: SearchableObjectArray) {
    setResults([...results, ...list]);
  }

  function storeRecentResult(result: SearchableObject) {
    const key = `recent_${props.object}`;
    const cookie = getCookie(key);
    const cookieObj: SearchableObjectArray = cookie
      ? JSON.parse(cookie as string)
      : [];
    let recents: SearchableObjectArray = [];

    if (props.object === "weapons") {
      recents = cloneDeep(cookieObj as Weapon[]) || [];
      if (
        !recents.find(
          (item) =>
            (item as Weapon).granblue_id === (result as Weapon).granblue_id
        )
      ) {
        recents.unshift(result as Weapon);
      }
    } else if (props.object === "summons") {
      recents = cloneDeep(cookieObj as Summon[]) || [];
      if (
        !recents.find(
          (item) =>
            (item as Summon).granblue_id === (result as Summon).granblue_id
        )
      ) {
        recents.unshift(result as Summon);
      }
    }

    if (recents && recents.length > 5) recents.pop();
    setCookie(`recent_${props.object}`, recents, { path: "/" });
    sendData(result);
  }

  function sendData(result: SearchableObject) {
    props.send(result, props.fromPosition);
    openChange();
  }

  function receiveFilters(filters: { [key: string]: any }) {
    setCurrentPage(1);
    setResults([]);
    setFilters(filters);
  }

  useEffect(() => {
    // Current page changed
    if (open && currentPage > 1) {
      fetchResults({ replace: false });
    } else if (open && currentPage == 1) {
      fetchResults({ replace: true });
    }
  }, [currentPage]);

  useEffect(() => {
    // Filters changed
    const key = `recent_${props.object}`;
    const cookie = getCookie(key);
    const cookieObj: Weapon[] | Summon[] | Character[] = cookie
      ? JSON.parse(cookie as string)
      : [];

    if (open) {
      if (firstLoad && cookieObj && cookieObj.length > 0) {
        setResults(cookieObj);
        setRecordCount(cookieObj.length);
        setFirstLoad(false);
      } else {
        setCurrentPage(1);
        fetchResults({ replace: true });
      }
    }
  }, [filters]);

  useEffect(() => {
    // Query changed
    if (open && query.length != 1) {
      setCurrentPage(1);
      fetchResults({ replace: true });
    }
  }, [query]);

  function renderResults() {
    let jsx;

    switch (props.object) {
      case "weapons":
        jsx = renderWeaponSearchResults();
        break;
      case "summons":
        jsx = renderSummonSearchResults(results);
        break;
      case "characters":
        jsx = renderCharacterSearchResults(results);
        break;
      case "job_skills":
        jsx = renderJobSkillSearchResults(results);
        break;
    }

    return (
      <InfiniteScroll
        dataLength={results && results.length > 0 ? results.length : 0}
        next={() => setCurrentPage(currentPage + 1)}
        hasMore={totalPages > currentPage}
        scrollableTarget="Results"
        loader={<div className="footer">Loading...</div>}
      >
        {jsx}
      </InfiniteScroll>
    );
  }

  function renderWeaponSearchResults() {
    let jsx: React.ReactNode;

    const castResults: Weapon[] = results as Weapon[];
    if (castResults && Object.keys(castResults).length > 0) {
      jsx = castResults.map((result: Weapon) => {
        return (
          <WeaponResult
            key={result.id}
            data={result}
            onClick={() => {
              storeRecentResult(result);
            }}
          />
        );
      });
    }

    return jsx;
  }

  function renderSummonSearchResults(results: { [key: string]: any }) {
    let jsx: React.ReactNode;

    const castResults: Summon[] = results as Summon[];
    if (castResults && Object.keys(castResults).length > 0) {
      jsx = castResults.map((result: Summon) => {
        return (
          <SummonResult
            key={result.id}
            data={result}
            onClick={() => {
              storeRecentResult(result);
            }}
          />
        );
      });
    }

    return jsx;
  }

  function renderCharacterSearchResults(results: { [key: string]: any }) {
    let jsx: React.ReactNode;

    const castResults: Character[] = results as Character[];
    if (castResults && Object.keys(castResults).length > 0) {
      jsx = castResults.map((result: Character) => {
        return (
          <CharacterResult
            key={result.id}
            data={result}
            onClick={() => {
              storeRecentResult(result);
            }}
          />
        );
      });
    }

    return jsx;
  }

  function renderJobSkillSearchResults(results: { [key: string]: any }) {
    let jsx: React.ReactNode;

    const castResults: JobSkill[] = results as JobSkill[];
    if (castResults && Object.keys(castResults).length > 0) {
      jsx = castResults.map((result: JobSkill) => {
        return (
          <JobSkillResult
            key={result.id}
            data={result}
            onClick={() => {
              storeRecentResult(result);
            }}
          />
        );
      });
    }

    return jsx;
  }

  function openChange() {
    if (open) {
      setQuery("");
      setFirstLoad(true);
      setResults([]);
      setRecordCount(0);
      setCurrentPage(1);
      setOpen(false);
    } else {
      setOpen(true);
    }
  }

  return (
    <Dialog.Root open={open} onOpenChange={openChange}>
      <Dialog.Trigger asChild>{props.children}</Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Content className="Search Dialog">
          <div id="Header">
            <div id="Bar">
              <label className="search_label" htmlFor="search_input">
                <input
                  autoComplete="off"
                  type="text"
                  name="query"
                  className="Input"
                  id="search_input"
                  ref={searchInput}
                  value={query}
                  placeholder={props.placeholderText}
                  onChange={inputChanged}
                />
              </label>
              <Dialog.Close className="DialogClose" onClick={openChange}>
                <CrossIcon />
              </Dialog.Close>
            </div>
            {props.object === "characters" ? (
              <CharacterSearchFilterBar sendFilters={receiveFilters} />
            ) : (
              ""
            )}
            {props.object === "weapons" ? (
              <WeaponSearchFilterBar sendFilters={receiveFilters} />
            ) : (
              ""
            )}
            {props.object === "summons" ? (
              <SummonSearchFilterBar sendFilters={receiveFilters} />
            ) : (
              ""
            )}
            {props.object === "job_skills" ? (
              <JobSkillSearchFilterBar sendFilters={receiveFilters} />
            ) : (
              ""
            )}
          </div>

          <div id="Results" ref={scrollContainer}>
            <h5 className="total">
              {t("search.result_count", { record_count: recordCount })}
            </h5>
            {open ? renderResults() : ""}
          </div>
        </Dialog.Content>
        <Dialog.Overlay className="Overlay" />
      </Dialog.Portal>
    </Dialog.Root>
  );
};

export default SearchModal;
