import React, { useRef, useEffect, useLayoutEffect, useState, createContext, useContext } from 'react';
import { Switch, useLocation, Route as RouteDom, SwitchProps, RouteProps } from 'react-router-dom';
import { pathToRegexp } from 'path-to-regexp';

const RoutersManagerContext = createContext<any>({} as any);
const RouterModalGroupContext = createContext<any>({} as any);

/**
 *
 * Para o gerenciamento de rotas igual do facebook
 *
 * Quando um usuário entra em uma página e depois entra
 * em um post, esse post é aberto e um modal, mas se ele
 * copiar esse link e posteriormente abrir, esse modal
 * agora será uma página única
 *
 */
export function RoutersManager({ children }: SwitchProps): any {
   const location = useLocation();
   const [previousLocation, setPreviousLocation] = useState<any>();
   const [animationModal, setAnimationModal] = useState(false);
   const [routes, setRoutes] = useState([]);
   const routeHistory = useRef([]);
   const currentRoute = useRef(undefined);

   useLayoutEffect(() => {
      const route = routes.find((i) => {
         return !!pathToRegexp(i.path).exec(location.pathname);
      });
      const oldRouteModalInfo = routes.find((i) => {
         return !!pathToRegexp(i.path).exec(currentRoute.current?.pathname);
      });

      const currentRoutePos = routeHistory.current.findIndex((i) => i?.key === currentRoute.current.key);
      const newRoutePos = routeHistory.current.findIndex((i) => i?.key === location.key);

      /**
       * Quando deve preservar rota?
       *
       * previousLocation decide se a rota principal vai mostrar o não a rota
       * Se previousLocation não salvar a rota, ela será exibida em um modal
       */
      // Se a página não for modal
      let savePreviousLocation = route && !route.modal;
      // Se a primeira página já for um modal, abre como uma página normal
      savePreviousLocation = savePreviousLocation || !currentRoute.current?.key;
      // Se estiver em uma full page, voltar em um modal não deve exibi-lo
      // já que não existe mais fundo
      //
      // Ex:
      // Página 1 - Calendário
      // Pagina 2 - Modal Adicionar Evento
      // Pagina 3 - Meu perfil
      //
      // Sair da página 3 -> 2, não deverá mostrar como fundo nem a página 1 nem a 3
      savePreviousLocation =
         savePreviousLocation ||
         (currentRoutePos > newRoutePos &&
            newRoutePos !== -1 &&
            !oldRouteModalInfo?.modal &&
            previousLocation?.pathname != location?.pathname);
      // Se estiver em um modal que agora é full page, voltar em um modal não deve exibi-lo
      // já que não existe mais fundo
      //
      // Ex:
      // Página 1 - Calendário
      // Pagina 2 - Modal Adicionar Evento
      // Pagina 3 - Modal Adicionar usuário
      // Pagina 4 - Meu perfil
      //
      // Sair da página 3 -> 2 -> 1, não deverá mostrar como fundo nem a página 1 nem a 4,
      // já que todas agora são full page
      savePreviousLocation =
         savePreviousLocation ||
         (currentRoutePos > newRoutePos &&
            newRoutePos !== -1 &&
            oldRouteModalInfo?.modal &&
            previousLocation?.pathname == currentRoute.current?.pathname);
      // Avançar de um modal que agora é uma página
      // Quando acontecer de o usuário voltar a página e o modal for um full page
      // o proximo modal, também deve ser uma full page
      savePreviousLocation =
         savePreviousLocation ||
         (currentRoutePos < newRoutePos &&
            previousLocation?.pathname == currentRoute.current?.pathname &&
            oldRouteModalInfo?.modal);

      // Mostrar animação do modal
      const modalAnimation = previousLocation?.pathname == currentRoute.current?.pathname;
      setAnimationModal(modalAnimation);

      if (savePreviousLocation) {
         setPreviousLocation(location);
      }

      // Adicionar a history
      if (!routeHistory.current.find((route) => route.key == location.key)) {
         routeHistory.current.push(location);
      }
      // Atualizar com posição atual
      currentRoute.current = location;
   }, [location, routes]);

   function addRoute(data: any): any {
      setRoutes((i) => {
         const a = [...i];
         a.push(data);
         return a;
      });
   }

   return (
      <RoutersManagerContext.Provider
         value={{
            addRoute,
            routes,
            previousLocation,
            animation: animationModal,
            isModal: previousLocation?.key !== location?.key,
         }}
      >
         <Switch location={previousLocation}>{children}</Switch>
         <RouterModalGroupContext.Provider value={{ modal: true }}>{children}</RouterModalGroupContext.Provider>
      </RoutersManagerContext.Provider>
   );
}

export function Route(props: RouteProps<string> & { modal?: boolean }): any {
   const ctx = useContext(RouterModalGroupContext);
   const ctxManager = useContext(RoutersManagerContext);

   useEffect(() => {
      if (ctx.modal) {
         ctxManager.addRoute({
            path: props.path,
            modal: !!props.modal,
         });
      }
   }, []);

   if (
      (ctx.modal && !props.modal) ||
      (ctx.modal && !!pathToRegexp(props.path as string).exec(ctxManager.previousLocation?.pathname)) ||
      ctxManager.previousLocation?.pathname == undefined
   )
      return null;

   return <RouteDom {...props} />;
}

export function useRouteIsModal(): any {
   const ctx = useContext(RouterModalGroupContext);

   return ctx.modal;
}

export function useIsModalRoute(): {
   showModal: boolean;
   animation: boolean;
} {
   const ctxManager = useContext(RoutersManagerContext);

   return {
      showModal: ctxManager.isModal,
      animation: ctxManager.animation,
   };
}
