/* eslint-disable import/no-extraneous-dependencies */
import React, { useState, useEffect, useRef, useCallback, useContext } from 'react';
import PropTypes from 'prop-types';
import { navigate } from 'gatsby';
import { FaSearch } from '@react-icons/all-files/fa/FaSearch';
import loadable from '@loadable/component';
import { translate } from 'gatsby-core-theme/src/helpers/getters';
import { Context } from 'gatsby-core-theme/src/context/MainProvider';
import loadingImg from '../../../../images/loading.svg';
import LazyImage from '~hooks/lazy-image';
import ConditionalWrapper from '~atoms/conditional-wrapper';
import { getURLParam } from '~helpers/getters';
import keygen from '~helpers/keygen';
import { filterByKey, sortDateOn, sortIntOn, sortStringOn } from '~helpers/search';
import loadSource from '~helpers/search-source';
import { leftTrim } from '~helpers/strings';
import ModuleTitle from '~atoms/module-title';
import { NavigationContext } from '~organisms/navigation/navigationContext';

/* eslint-disable no-underscore-dangle */

import styles from './search.module.scss';

const Search = ({
  searchIcon = null,
  uniqueField = 'id',
  indexFields = ['title'],
  searchUrlParam = 's',
  searchURLPageName = 's',
  formSearchOptions = null,
  pageSearchOptions = null,
  inputPlaceholder = 'Search',
  inputPlaceholderPage = 'Search Again',
  children,
  noResults = 'We can’t find anything that matches your query. Please try again with other keywords.',
  page,
  pageContext,
  showTabIcon = false,
  icon = null,
  showNumberOfResults = false,
}) => {
  const { setShowSearch } = useContext(NavigationContext);
  const pageSearchOptionsCopy = pageSearchOptions;
  const formSearchOptionsCopy = formSearchOptions;
  const searchQueryInit =
    pageSearchOptionsCopy !== null && typeof window !== 'undefined'
      ? getURLParam(searchUrlParam)
      : '';

  const [isLoading, setIsLoading] = useState(true);
  const [sortQuery, setSortQuery] = useState({});
  const [searchQuery, setSearchQuery] = useState(searchQueryInit);
  const [searchBoxQuery, setSearchBoxQuery] = useState('');
  const noResultsText = noResults.replace('[keyword]', searchQuery);

  const loadingRef = useRef(React.createRef());
  const searchInputRef = useRef(React.createRef());
  const formBoxInputRef = useRef(React.createRef());

  const { translations } = useContext(Context) || {};

  // Store search results, in case of page search with tabs it's a multidimensal array grouped by types
  const searchResultsRef = useRef([]);
  // Store the search function object
  const dataSetRef = useRef([]);
  // Store a copy of a result - used to get children details without the need to pass results
  const sampleChildRef = useRef({});
  // Store a boolean to check when search data is loaded - used for auto complete functionaity
  const dataLoading = useRef(false);
  // FUNCTIONS

  // Get the types of children provided
  const getChildrenTypes = useCallback(
    () => children(sampleChildRef.current).props.children.map((child) => child.props.type),
    [children]
  );

  // Get the child by the given type - a data object is required
  const getChildType = (item, type) =>
    children(item, searchQuery).props.children.find((child) => child.props.type === type);

  // Prepare search result for auto complete
  const loadAutoComplete = useCallback(() => {
    if (dataSetRef.current.length !== 0 || dataLoading.current) {
      if (dataLoading.current) {
        setTimeout(loadAutoComplete, 100);
        return;
      }

      if (searchQuery.length >= 3) {
        searchResultsRef.current = dataSetRef.current.search(searchQuery);

        if (formSearchOptionsCopy.sort) {
          const types = getChildrenTypes();
          let filteredResults = [];

          types.forEach((type) => {
            filteredResults = [
              ...filteredResults,
              ...searchResultsRef.current.filter((object) => {
                if (object && object.market === pageContext.page?.market && object.type === type) {
                  return true;
                }
                return false;
              }),
            ];
          });

          searchResultsRef.current = filteredResults;
        }

        if (formSearchOptionsCopy.limit !== undefined) {
          searchResultsRef.current.splice(formSearchOptionsCopy.limit);
        }

        setIsLoading(false);
      } else {
        searchResultsRef.current = [];
        setIsLoading(false);
      }
    }
  }, [
    searchQuery,
    formSearchOptionsCopy?.sort,
    formSearchOptionsCopy?.limit,
    getChildrenTypes,
    pageContext?.page?.market,
  ]);

  // PROCESSES

  // Sort the results. Used in conjunction with the sortComponent for page search.
  useEffect(() => {
    if (searchResultsRef.current.length !== 0) {
      let sortQueryField = sortQuery.field;

      if (sortQuery.field === '') sortQueryField = 'id';

      switch (sortQuery.type) {
        case 'date':
          searchResultsRef.current.sort(sortDateOn(sortQueryField));
          break;
        case 'string':
          searchResultsRef.current.sort(sortStringOn(sortQueryField));
          break;
        default:
          searchResultsRef.current.sort(sortIntOn(sortQueryField));
          break;
      }

      setIsLoading(false);
    }
  }, [sortQuery, pageSearchOptionsCopy]);

  // Load search source and prepare search dataset for page
  useEffect(() => {
    if (pageSearchOptionsCopy !== null) {
      if (searchQuery !== null) {
        loadSource(uniqueField, indexFields).then((data) => {
          const [sample] = data._documents;
          sampleChildRef.current = sample;
          searchResultsRef.current = data.search(searchQuery);
          setIsLoading(false);
        });
      }
    }
  }, [searchQuery, pageSearchOptionsCopy, uniqueField, indexFields]);

  // Load search source and prepare search dataset for form
  useEffect(() => {
    if (
      formSearchOptionsCopy !== null &&
      formSearchOptionsCopy.autoComplete &&
      dataSetRef.current &&
      dataSetRef.current.length === 0
    ) {
      dataLoading.current = true;
      loadSource(uniqueField, indexFields).then((data) => {
        dataSetRef.current = data;
        const [sample] = data?._documents;
        sampleChildRef.current = sample;
        dataLoading.current = false;
        searchInputRef.current.focus();
      });
    }

    return undefined;
  }, [formSearchOptionsCopy, indexFields, uniqueField]);

  // Refresh auto complete results when the form search query is changed
  useEffect(() => {
    if (formSearchOptionsCopy !== null && formSearchOptionsCopy.autoComplete) {
      loadAutoComplete();
    }
  }, [searchQuery, loadAutoComplete, formSearchOptionsCopy]);

  // HANDLERS
  const formSubmitHandler = (e) => {
    e.preventDefault();
    if (searchQuery.length > formSearchOptionsCopy.limit - 1 && searchQuery.length < 60) {
      setShowSearch(false);
      navigate(`/${searchURLPageName}?${searchUrlParam}=${searchQuery}`);
    }
  };

  const formSubmitBoxHandler = (e) => {
    e.preventDefault();
    if (searchBoxQuery === '') {
      e.target.querySelector('input').focus();
    } else {
      navigate(`/${searchURLPageName}?${searchUrlParam}=${searchBoxQuery}`);
    }
  };

  const searchHandler = async (e) => {
    setIsLoading(true);
    await setSearchQuery(leftTrim(e.target.value));
  };

  const searchBoxHandler = async (e) => {
    setSearchBoxQuery(leftTrim(e.target.value));
  };

  const sortHandle = (field, name, type) => {
    setIsLoading(true);
    setSortQuery({
      type,
      field,
      name,
    });
  };

  useEffect(() => {
    if (pageSearchOptionsCopy !== null && pageSearchOptionsCopy.sort !== undefined) {
      setSortQuery(pageSearchOptionsCopy.sort[0]);
    }
  }, [pageSearchOptionsCopy]);
  if (pageSearchOptionsCopy !== null && pageSearchOptionsCopy.sort !== undefined) {
    const CustomSelect = loadable(() => import('~atoms/custom-select'));
    pageSearchOptionsCopy.tabsOptions.HeaderComp = (
      <div className={styles.selectwrapper}>
        <CustomSelect
          items={pageSearchOptionsCopy.sort}
          onClick={sortHandle}
          activeItem={sortQuery}
        />
      </div>
    );
  }

  // BODY

  if (pageSearchOptionsCopy !== null && typeof children !== 'function') {
    return 'Search page component needs to wrap a function which returns components to display results.';
  }

  if (
    formSearchOptionsCopy !== null &&
    formSearchOptionsCopy.autoComplete !== undefined &&
    formSearchOptionsCopy.autoComplete &&
    typeof children !== 'function'
  ) {
    return 'Search form component needs to wrap a function which returns components to display autocomplete results.';
  }

  // Use a default or given close button
  if (formSearchOptionsCopy !== null && formSearchOptionsCopy.searchButtonComponent === undefined) {
    formSearchOptionsCopy.searchButtonComponent = (
      <button aria-label="Search" type="submit" className="search-gtm btn-cta">
        {searchIcon === null ? (
          <FaSearch title="Search Icon" />
        ) : (
          <img alt="search" src={searchIcon} />
        )}
      </button>
    );
  }

  const Tabs =
    searchResultsRef.current.length !== 0 &&
    loadable(() => import('gatsby-core-theme/src/hooks/tabs'));

  return (
    <>
      {formSearchOptionsCopy !== null && (
        <form onSubmit={formSubmitHandler} className={styles?.form || ''}>
          <input
            type="text"
            aria-label="Search"
            placeholder={inputPlaceholder}
            value={searchQuery}
            onChange={searchHandler}
            ref={searchInputRef}
            maxLength="60"
            className={styles?.formInput || ''}
          />
          {isLoading && <span ref={loadingRef} />}
          <div className={styles?.searchButton || ''}>
            {formSearchOptionsCopy?.searchButtonComponent || ''}
          </div>
          {formSearchOptionsCopy.autoComplete && searchResultsRef.current.length > 0 && (
            <div className={styles?.autocomplete || ''}>
              {searchResultsRef.current.map((item) => getChildType(item, item.type))}
              {formSearchOptionsCopy.showViewResultsText && (
                <div className={styles?.viewAllResults || ''}>
                  <button
                    aria-label={translate(translations, 'view_all', 'View All Results')}
                    type="submit"
                    className={`${styles?.viewAllResultsBtn || ''} search-gtm btn-cta`}
                  >
                    <h1>{translate(translations, 'view_all', 'View All Results')}</h1>
                  </button>
                </div>
              )}
            </div>
          )}
        </form>
      )}
      {pageSearchOptionsCopy !== null && isLoading && (
        <span ref={loadingRef} className={styles?.loading || ''}>
          <LazyImage src={loadingImg} alt="loading" />
        </span>
      )}
      {pageSearchOptionsCopy !== null && !isLoading && (
        <>
          <h1>{`${pageSearchOptionsCopy.searchTitle} "${searchQuery}"`}</h1>
          <form onSubmit={formSubmitBoxHandler} className={styles?.formBox || ''}>
            <input
              type="text"
              aria-label="Search Again"
              placeholder={inputPlaceholderPage}
              onChange={searchBoxHandler}
              className={styles?.formInput || ''}
              ref={formBoxInputRef}
              maxLength="60"
            />
            <div className={styles?.searchButton || ''}>
              <button aria-label="Search" type="submit" className="search-gtm btn-cta">
                {searchIcon === null ? (
                  <FaSearch title="Search Icon" />
                ) : (
                  <img alt="search" src={searchIcon} />
                )}
              </button>
            </div>
          </form>
          {searchResultsRef.current.length === 0 && formSearchOptionsCopy === null ? (
            <div>
              <span className={styles?.noResults || ''}>{noResultsText}</span>
            </div>
          ) : null}
          {searchResultsRef.current.length !== 0 && (
            <ConditionalWrapper
              condition={pageSearchOptionsCopy.tabs}
              wrapper={(items) => (
                <Tabs
                  {...pageSearchOptionsCopy.tabsOptions}
                  activeTabClass="searchActive"
                  headerClass="searchHeader"
                  gtmClass="search-tabs-menu-gtm"
                  showIcon={showTabIcon}
                  icon={icon}
                  showNumberOfResults={showNumberOfResults}
                  totalResults={searchResultsRef?.current?.length}
                >
                  {items}
                </Tabs>
              )}
            >
              {getChildrenTypes().map((type, index) => {
                let items = [];

                searchResultsRef.current.forEach((object) => {
                  if (object && object.market === page?.market && object.type === type) {
                    const filteredItems = searchResultsRef.current.filter(
                      filterByKey('id', object.id)
                    );
                    items = items.concat(filteredItems);
                  }
                });

                const titleObj = pageSearchOptionsCopy.archiveOptions.titles.find(
                  (title) => title.type === type
                );
                if (
                  pageSearchOptionsCopy.useArchive === undefined ||
                  !pageSearchOptionsCopy.useArchive
                ) {
                  items = searchResultsRef.current
                    .filter((item) => item.type === type)
                    .map((item) => getChildType(item, type));
                } else if (items.length) {
                  items = {
                    items,
                    model_type: type,
                    title: titleObj ? titleObj.title : '',
                    pagination_type: 'load_more',
                    ...pageSearchOptionsCopy.archiveOptions.includeOptions,
                  };
                }

                const loadMore = { ...pageSearchOptionsCopy.loadMore, dependency: sortQuery };

                if (items.length === 0) return null;

                const Archive = loadable(() => import('~organisms/archive'));

                return (
                  <div
                    results={items?.items?.length}
                    label={titleObj.title}
                    tabId={type + index}
                    key={keygen()}
                  >
                    {pageSearchOptionsCopy.useArchive === undefined ||
                    !pageSearchOptionsCopy.useArchive ? (
                      <div className={styles?.searchGrid || ''}>{items}</div>
                    ) : (
                      <>
                        <ModuleTitle
                          module={{
                            module_title: items.title,
                            module_title_tag: 'h2',
                            style: 'search',
                          }}
                        />
                        <Archive
                          module={items}
                          loadMore={loadMore}
                          page={page}
                          gtmClass="archive-gtm"
                        />
                      </>
                    )}
                  </div>
                );
              })}
            </ConditionalWrapper>
          )}
        </>
      )}
    </>
  );
};

Search.propTypes = {
  searchIcon: PropTypes.string,
  searchTitle: PropTypes.string,
  uniqueField: PropTypes.string,
  indexFields: PropTypes.arrayOf(PropTypes.string),
  searchUrlParam: PropTypes.string,
  searchURLPageName: PropTypes.string,
  inputPlaceholder: PropTypes.string,
  inputPlaceholderPage: PropTypes.string,
  noResults: PropTypes.string,
  formSearchOptions: PropTypes.shape({
    autoComplete: PropTypes.bool,
    sort: PropTypes.bool,
    limit: PropTypes.number,
    searchButtonComponent: PropTypes.element,
    searchFormRef: PropTypes.element,
    searchFormShowStyle: PropTypes.string,
  }),
  pageSearchOptions: PropTypes.shape({
    useArchive: PropTypes.bool,
    archiveOptions: PropTypes.shape({
      titles: PropTypes.arrayOf(PropTypes.shape({})),
      includeOptions: PropTypes.objectOf(PropTypes.string),
    }),
    tabs: PropTypes.bool,
    tabsOptions: PropTypes.shape({
      children: PropTypes.element,
      showAll: PropTypes.string,
      tabsAlign: PropTypes.string,
      HeaderComp: PropTypes.elementType,
    }),
    sort: PropTypes.arrayOf(
      PropTypes.shape({
        field: PropTypes.string,
        type: PropTypes.string,
        name: PropTypes.string,
      })
    ),
    loadMore: PropTypes.shape({
      label: PropTypes.string,
      limit: PropTypes.number,
      increment: PropTypes.number,
      usePrimaryButton: PropTypes.bool,
      useInvertColors: PropTypes.bool,
    }),
  }),
  children: PropTypes.elementType,
  styles: PropTypes.shape({}),
  page: PropTypes.shape({
    translations: PropTypes.shape({}),
    market: PropTypes.string,
  }),
  pageContext: PropTypes.shape({
    page: PropTypes.shape({
      market: PropTypes.string,
    }),
  }),
  showTabIcon: PropTypes.bool,
  icon: PropTypes.func,
  showNumberOfResults: PropTypes.bool,
};

export default Search;
