import { useState, useEffect, useReducer, useRef, ReducerWithoutAction, Reducer } from 'react';
import { forkJoin, Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import useDeepCompareEffect from 'use-deep-compare-effect';
import { useImmerReducer } from 'use-immer';
import { useListener } from '../useListener';
import { useStateRef } from '../useStateRef';
import { IStatusInfo, IUseViewProps, IViewResponse } from './useView.interfaces';

export function useView({ resolves: resolvesOriginal = {}, firstLoad = true }: IUseViewProps): IViewResponse {
   /**
    * Resolves
    */
   const [resolves, setResolves] = useState(resolvesOriginal);
   useDeepCompareEffect(() => {
      setResolves(resolvesOriginal);
   }, [resolvesOriginal]);

   /**
    * Status Info
    *
    * Gerenciamento do status da página
    */
   const [statusInfo, setStatusInfo] = useImmerReducer((prevState, newState) => ({ ...prevState, ...newState }), {
      starting: true,
      errorLoadData: false,
      criticalError: false,
   });

   /**
    * Registra uma função que será chamada como parametro de um determinado resolve
    */
   const getParamsResolve = useRef<{ [key: string]: () => any }>({});
   const registerResolveParams = function registerResolveParams(resolveName: string, fn: () => any): void {
      getParamsResolve.current[resolveName] = fn;
   };

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

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

   /**
    * Subject para cancelar subscribes (Normalmente requests)
    */
   const $destroy = useRef(new Subject()).current;

   //
   function initialize(): void {}

   /**
    * Carregar resolves
    */
   function loadResolves(): void {
      new Observable((observer) => {
         try {
            const params = {};

            // Se não houver nenhum resolves ou firstLoad = false
            if (Object.values(resolves).length === 0 || !firstLoad) {
               observer.next({});
               observer.unsubscribe();
               return;
            }

            const keys = Object.keys(resolves);
            forkJoin(
               Object.values(resolves).map((f: any, i: number) =>
                  // Verifica se existe alguma função registrada para esse resolve
                  f(getParamsResolve.current[keys[i]] ? getParamsResolve.current[keys[i]]() : {}).pipe(
                     takeUntil($destroy),
                  ),
               ),
            )
               .pipe(takeUntil($destroy))
               .subscribe(
                  (results) => {
                     const keysI = Object.keys(resolves);

                     observer.next(Object.fromEntries(results.map((e, i) => [keysI[i], results[i]])));
                     observer.unsubscribe();
                  },
                  (err) => {
                     observer.error(err);
                     observer.unsubscribe();
                  },
               );
         } catch (e) {
            setStatusInfo({
               starting: false,
               criticalError: true,
            });
         }
      }).subscribe(
         (r) => {
            setStatusInfo({
               starting: false,
               errorLoadData: false,
            });

            // Chama todos os onInit registrados
            callOnInit(r);
         },
         (err) => {
            setStatusInfo({
               starting: false,
               errorLoadData: true,
            });

            callOnInitError(err);
         },
      );
   }

   /**
    * Recarregar página
    *
    * @param wait1s Aguardar 1s para debounce
    */
   function reloadPage(wait1s = true): void {
      setStatusInfo({
         starting: true,
         errorLoadData: false,
         criticalError: false,
      });

      if (wait1s) {
         setTimeout(() => loadResolves(), 1000);
      } else {
         loadResolves();
      }
   }

   /**
    * Load resolves
    */
   useEffect(() => {
      initialize();
      loadResolves();

      return () => {
         $destroy.next({});
      };
   }, []);

   return {
      ...(statusInfo as IStatusInfo),
      statusInfo,
      setStatusInfo,
      registerOnInit,
      registerOnInitError,
      getParamsResolve,
      registerResolveParams,
      $destroy,
      reloadPage,
   };
}
