import { useState, useEffect, useLayoutEffect, useContext, useRef, useCallback } from 'react';
import * as queryStringHelper from 'query-string';
import { takeUntil } from 'rxjs/operators';
import { useImmer } from 'use-immer';
import AppContext from '../../components1/AppProvider/AppContext';
import { IObject } from '../../interfaces';
import { CreateRange, deleteEmpty, objectExcept } from '../../services';
import { useDidUpdateDeepCompareEffect } from '../useDidUpdateDeepCompareEffect';
import { useDidUpdateDeepCompareLayoutEffect } from '../useDidUpdateDeepCompareLayoutEffect';
import { useListener } from '../useListener';
import { useQueryString } from '../useQueryString/useQueryString';
import { useStateRef } from '../useStateRef';
import { useView } from '../useView';
import { IFilterOptions, IStatusInfoViewList, IUseViewListProps, IViewList } from './useViewList.interfaces';

export function useViewList<IFilter = any, IQuery = IObject, IResource = any>({
   filtersDefault: filtersDefaultOriginal = {},
   limit = 20,
   infiniteScroll = false,
   disableQueryChange = false,
   ignoreFiltersInQuery = [],
   ignoreQueryInFilters = [],
   reverseItems = false,
   resolves = {},
   firstLoad = true,
   changeOnlyPath = null,
   handleRequestBody: handleRequestBodyInitial = (filter: IFilter) => filter,
   handleFilterToQuery: handleFilterToQueryInitial = (filter: IFilter) => filter,
   handleQueryToFilter: handleQueryToFilterInitial = (filter: IQuery) => filter,
}: IUseViewListProps): IViewList<IFilter, IQuery, IResource> {
   const {
      statusInfo,
      setStatusInfo,
      registerOnInit,
      registerOnInitError,
      getParamsResolve,
      registerResolveParams,
      $destroy,
      reloadPage,
   } = useView({ resolves, firstLoad });

   const { history } = useContext(AppContext);

   const skeletonResources = useRef<number[]>(CreateRange(limit)).current;

   /**
    * Filtros Defaults
    */
   const [filtersDefault, setFiltersDefault] = useStateRef<Partial<IFilter>>(filtersDefaultOriginal);
   useEffect(() => {
      setFiltersDefault(filtersDefaultOriginal);
   }, [filtersDefaultOriginal]);

   /**
    * Items
    */
   const [resources, setResources] = useImmer<IResource[]>([]);
   const [resourcesTotal, setResourceTotal] = useState<number>(0);

   /**
    * Valores iniciais
    */
   useLayoutEffect(() => {
      setStatusInfo({
         searchingMore: false,
         searching: true, // Só ativa searching se houver um primeiro carregamento
         lastPage: false,
         errorLoadMore: false,
      });
   }, []);

   /**
    * Tratar dados do filter que serão enviados para o Body da Request
    */
   const [handleRequestBody, setHandleRequestBody] = useStateRef<(filter: IFilter) => any>(handleRequestBodyInitial);

   /**
    * Tratar dados do Filter que serão enviados para Url Query
    */
   const [handleFilterToQuery, setHandleFilterToQuery] = useStateRef<(query: IFilter) => any>(
      handleFilterToQueryInitial,
   );

   /**
    * Tratar dados do Query que são enviados para o filtro
    */
   const [handleQueryToFilter, setHandleQueryToFilter] = useStateRef<(filter: IQuery) => any>(
      handleQueryToFilterInitial,
   );

   /**
    * Url Query String
    */
   const queryString = useQueryString(changeOnlyPath);

   /**
    * Filtros
    *
    * Inicia com valores da URL Query ignorando valores de ignoreQueryInFilters
    */
   const [filters, __setFilters__] = useImmer<any>(
      objectExcept({ ...filtersDefault.current, ...handleQueryToFilter.current(queryString) }, ignoreQueryInFilters),
   );

   // Opções ao definir filtros
   const [filtersOptions, setFiltersOptions] = useImmer<IFilterOptions>({});

   const setFilters = function setFilters(f: (draft: any) => any, options: IFilterOptions = {}): void {
      setFiltersOptions(() => options);
      __setFilters__((draft) => {
         const newDraft = f(draft);

         if (newDraft === undefined || newDraft === null) {
            draft = deleteEmpty({ ...filtersDefault.current, ...draft });
         } else {
            return deleteEmpty({ ...filtersDefault.current, ...newDraft });
         }
      });
   };

   /**
    * Adicionar filtros para request do resolve Items
    *
    * handleRequestBody
    */
   useLayoutEffect(() => {
      registerResolveParams('items', () => handleRequestBody.current({ ...filtersDefault.current, ...filters }));
   }, []);

   /**
    * Registra uma função para ser chamada no load da página ou quando a página é recarregada (reloadPage)
    */
   const [registerOnSearch, callOnSearch] = useListener<(resolves: { [key: string]: any }) => void>();

   /**
    * Resgatar dados da inicialização
    *
    * Resgatar resolve items para definir items
    */
   useLayoutEffect(() => {
      registerOnInit((res) => {
         if (res.items) {
            const { items } = res;

            // Se não tiver menos resultados que o limit
            if (limit > items.count || items.count === 0) {
               setStatusInfo({
                  lastPage: true,
               });
            }

            setResources((draft) => (!reverseItems ? items.results : Array.from(items.results).reverse()));
            setResourceTotal(items.count);
         }

         setStatusInfo({
            searching: false,
         });
      });
   }, []);

   /**
    * Em caso de erro na inicialização, atualiza status info
    */
   useLayoutEffect(() => {
      registerOnInitError(() => {
         setStatusInfo({
            searching: false,
         });
      });
   }, []);

   /**
    * - Atualizar query url
    * - Realizar nova request
    * Obs: Não atualiza na primeira renderização
    */
   useDidUpdateDeepCompareLayoutEffect(() => {
      if (!disableQueryChange && !filtersOptions.disableQueryChange) {
         let searchString = `?${queryStringHelper.stringify(
            deleteEmpty(
               objectExcept(
                  handleFilterToQuery.current({ ...filtersDefault.current, ...filters }),
                  ignoreFiltersInQuery,
               ),
            ),
         )}`;
         const searchStringDefault = `?${queryStringHelper.stringify(
            objectExcept(filtersDefault.current, ignoreFiltersInQuery),
         )}`;

         if (searchString === searchStringDefault) {
            searchString = '';
         }

         // Replace url
         if (typeof document !== 'undefined') {
            if (window.location.search != searchString) {
               if (filtersOptions.replaceUrl) {
                  if (!history) {
                     window.location.replace(searchString);
                  } else {
                     history.replace({
                        search: searchString,
                     });
                  }
               } else if (!history) {
                  window.location.search = searchString;
               } else {
                  history.push({
                     search: searchString,
                  });
               }
            }
         }
      }

      // Se for primeira página
      const resetResources = (!filters.page || filters.page <= 1) && (!filters.offset || filters.offset == 0);

      // Não realizar request
      if (!filtersOptions.noRequest) {
         // Chamar eventos
         callOnSearch();

         loadResources({
            push: infiniteScroll,
            reset: resetResources,
         });
      }

      // Resetar opções de filtros
      setFiltersOptions(() => ({}));
   }, [filters]);

   /**
    * Atualizar filtro, quando houver alterações na query da url
    */
   useDidUpdateDeepCompareEffect(() => {
      setFilters(() => objectExcept(handleQueryToFilter.current(queryString), ignoreQueryInFilters), {
         disableQueryChange: true,
      });
   }, [queryString]);

   /**
    * Resgatar items
    *
    * @param options
    */
   function loadResources(options: { push?: boolean; reset?: boolean } = {}): void {
      if (resolves && resolves.items) {
         if (options.push && !options.reset) {
            setStatusInfo({
               errorLoadMore: false,
               errorLoadData: false,
               searchingMore: true,
            });
         } else {
            setStatusInfo({
               errorLoadMore: false,
               errorLoadData: false,
               searching: true,
            });
         }

         // Cancelar requisição anterior
         $destroy.next();

         resolves
            .items(handleRequestBody.current({ ...filtersDefault.current, ...filters }))
            .pipe(takeUntil($destroy))
            .subscribe(
               (response) => {
                  let lastPage = false;
                  if (!options.push || options.reset) {
                     setResources(() => (!reverseItems ? response.results : response.results.reverse()));
                     setResourceTotal(response.count);

                     if (
                        (filters?.offset ?? 0) + response.results.length >= response.count ||
                        response.results.length === 0
                     ) {
                        lastPage = true;
                     }
                  } else {
                     setResources((draft) =>
                        reverseItems ? [...response.results.reverse(), ...draft] : [...draft, ...response.results],
                     );
                     setResourceTotal(response.count);

                     // Verifica se é a ultima pagina
                     if (
                        resources.length + response.results.length >= response.count ||
                        response.results.length === 0
                     ) {
                        lastPage = true;
                     }
                  }

                  setStatusInfo({
                     errorLoadData: false,
                     errorLoadMore: false,
                     searching: false,
                     searchingMore: false,
                     lastPage,
                  });
               },
               () => {
                  if (options.push) {
                     setStatusInfo({
                        errorLoadMore: true,
                        searchingMore: false,
                     });

                     /**
                      * Atualizar offset para valor anterior antes do erro
                      *
                      * Não deve fazer nova request e nem alterar query da url
                      */
                     setFilters(
                        (draft) => {
                           const offset = draft.offset - limit;
                           return { ...draft, offset: offset > 0 ? 0 : offset };
                        },
                        {
                           noRequest: true,
                           disableQueryChange: true,
                        },
                     );
                  } else {
                     setStatusInfo({
                        errorLoadData: true,
                        searching: false,
                     });
                  }
               },
            );
      }
   }

   /**
    * Deletar 1 item da array
    *
    * @param id
    * @param softDelete Se deve deletar o item ou apenas atribuir a key delete nele
    */
   function removeResource(id: any, softDelete = false): void {
      setResources((items: any[]) => {
         const index = items.findIndex((i: any) => i.id === id);

         if (index !== -1) {
            if (softDelete) {
               items[index].deleted = true;
            } else {
               items.splice(index, 1);
            }

            // Remove 1 da quantidade total dos items
            setResourceTotal(resourcesTotal - 1);
         }
      });
   }

   /**
    * Deletar muitos itens da array
    *
    * @param ids
    * @param softDelete
    */
   function removeManyResources(ids: any[], softDelete = false): void {
      let deletedItems = 0;
      setResources((items: any[]) => {
         ids.forEach((id) => {
            const index = items.findIndex((i: any) => i.id === id);

            if (index !== -1) {
               if (softDelete) {
                  items[index].deleted = true;
               } else {
                  items.splice(index, 1);
               }

               deletedItems += 1;
            }
         });

         // Remove quantidade total dos items deletados
         setResourceTotal(resourcesTotal - deletedItems);
      });
   }

   /**
    * Definir página
    *
    * Página precisa ser mair ou igual a 1
    *
    * @param page
    * @param options
    */
   const setPage = useCallback(
      function setPage(page: number, options?: IFilterOptions): void {
         if (page < 1) page = 1;
         setFilters((draft) => ({ ...draft, page: null, offset: (page - 1) * limit }), options);
      },
      [filters],
   );

   /**
    * Definir offset
    *
    * @param offset Offset precisa ser mair ou igual a 0
    * @param options
    */
   const setOffset = useCallback(
      function setOffset(offset: number, options?: IFilterOptions): void {
         setFilters((draft) => ({ ...draft, page: null, offset }), options);
      },
      [filters],
   );

   /**
    * Definir ordem dos items
    *
    * Faz um novo carregamento e limpa offset e pages
    *
    * @param sort
    * @param options
    */
   const setSort = useCallback(
      function setSort(sort: string, options?: IFilterOptions): void {
         setFilters((draft) => ({ ...draft, page: null, offset: 0, sort }), options);
      },
      [filters],
   );

   /**
    * Carrega items da próxima página
    *
    * Se infiniteScroll = true, adicionar nos items atuais
    * Se infiniteScroll = true e houver erro, volta offset anterior
    *
    * @param options
    */
   const loadNextPage = useCallback(
      function loadNextPage(options?: IFilterOptions): void {
         setFilters((draft) => ({ ...draft, offset: draft.offset ? draft.offset + limit : limit }), options);
      },
      [filters],
   );

   /**
    * Recarregar items novamente (Atualização)
    *
    * @param wait1s
    * @param options
    */
   const reloadResources = useCallback(
      function reloadResources(wait1s = true, options?: IFilterOptions): void {
         loadResources({ reset: true });
      },
      [filters, resolves],
   );

   /**
    * Reiniciar página
    *
    * @param wait1s
    * @param options
    */
   const resetPageViewList = useCallback(
      function resetPageViewList(wait1s = true, options?: IFilterOptions): void {
         setFilters(() => ({}), {
            noRequest: true,
         });

         if (history && !disableQueryChange) {
            history.push({
               search: `?`,
            });
         }

         reloadPage(wait1s);
      },
      [filters],
   );

   return {
      ...(statusInfo as IStatusInfoViewList),
      statusInfo,
      setStatusInfo,
      registerOnInit,
      registerOnInitError,
      getParamsResolve,
      registerResolveParams,
      $destroy,
      reloadPage,
      //
      registerOnSearch,
      skeletonResources,
      resources,
      resourcesTotal,
      removeResource,
      removeManyResources,
      filters,
      setFilters,
      limit,
      setHandleRequestBody,
      setHandleFilterToQuery,
      setHandleQueryToFilter,
      setPage,
      setOffset,
      setSort,
      loadNextPage,
      reloadResources,
      resetPageViewList,
   };
}
