import { computed, nextTick, ref, shallowRef } from 'vue';
import type { RouteRecordRaw } from 'vue-router';
import { defineStore } from 'pinia';
import { useBoolean } from '@sa/hooks';
import type { CustomRoute, ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types';
import { router } from '@/router';
import { fetchGetRoutes } from '@/service/api';
import { isNotNull } from '@/utils/common';
import { SetupStoreId } from '@/enum';
import { createDynamicRoutes, createStaticRoutes, getAuthVueRoutes } from '@/router/routes';
import { ROOT_ROUTE } from '@/router/routes/builtin';
import { getRouteName, getRoutePath } from '@/router/elegant/transform';
import { useAuthStore } from '../auth';
import { useTabStore } from '../tab';
import {
  filterAuthRoutesByRoles,
  getBreadcrumbsByRoute,
  getCacheRouteNames,
  getGlobalMenusByAuthRoutes,
  getSelectedMenuKeyPathByKey,
  isRouteExistByRouteName,
  sortRoutesByOrder,
  transformMenuToSearchMenus,
  updateLocaleOfGlobalMenus
} from './shared';

const defaultIcon = import.meta.env.VITE_MENU_ICON;

export const useRouteStore = defineStore(SetupStoreId.Route, () => {
  const authStore = useAuthStore();
  const tabStore = useTabStore();
  const { bool: isInitConstantRoute, setBool: setIsInitConstantRoute } = useBoolean();
  const { bool: isInitAuthRoute, setBool: setIsInitAuthRoute } = useBoolean();

  /**
   * Auth route mode
   *
   * It recommends to use static mode in the development environment, and use dynamic mode in the production
   * environment, if use static mode in development environment, the auth routes will be auto generated by plugin
   * "@elegant-router/vue"
   */
  const authRouteMode = ref(import.meta.env.VITE_AUTH_ROUTE_MODE);

  /** Home route key */
  const routeHome = ref(import.meta.env.VITE_ROUTE_HOME || 'home');

  /** constant routes */
  const constantRoutes = shallowRef<ElegantConstRoute[]>([]);

  function addConstantRoutes(routes: ElegantConstRoute[]) {
    const constantRoutesMap = new Map<string, ElegantConstRoute>([]);

    routes.forEach(route => {
      constantRoutesMap.set(route.name, route);
    });

    constantRoutes.value = Array.from(constantRoutesMap.values());
  }

  /** auth routes */
  const authRoutes = shallowRef<ElegantConstRoute[]>([]);

  function addAuthRoutes(routes: ElegantConstRoute[]) {
    const authRoutesMap = new Map<string, ElegantConstRoute>([]);

    routes.forEach(route => {
      if (authRouteMode.value === 'dynamic') {
        if (route.path === '/' && route.children?.length) {
          const child = route.children[0];
          // @ts-expect-error no hidden field
          child.hidden = route.hidden;
          parseRouter(child);
          child.name = Math.random().toString(36).slice(2, 12);
          Object.assign(route, child);
          delete route.children;
          authRoutesMap.set(route.name, route);
          return;
        }
        parseRouter(route);
      }
      authRoutesMap.set(route.name, route);
    });

    const dynamicRoutes = createDynamicRoutes();

    dynamicRoutes.authRoutes.forEach(route => {
      const parent = authRoutesMap.get(route.name);
      if (parent && route.children) {
        parent.children?.push(...route.children);
        return;
      }
      authRoutesMap.set(route.name, route);
    });

    authRoutes.value = Array.from(authRoutesMap.values());
  }

  // eslint-disable-next-line complexity
  function parseRouter(route: ElegantConstRoute, parent?: ElegantConstRoute) {
    route.meta = route.meta ? route.meta : { title: route.name };
    if (route.meta.title.startsWith('route.') || route.meta.title.startsWith('menu.')) {
      route.meta.i18nKey = route.meta.title as App.I18n.I18nKey;
    }
    const isLayout = route.component === 'Layout';
    const isFramePage = route.component === 'FrameView';
    const isParentLayout = route.component === 'ParentView';
    const isBlankLayout = route.component?.startsWith('layout.blank$view.');
    const isExternalLink = isNotNull(route.meta.link);

    route.path = route.path.startsWith('/') ? route.path : `/${route.path}`;
    route.path = parent ? parent.path + route.path : route.path;

    route.name = route
      .component!.replace(/\/index$/, '')
      .replace(/\//g, '_')
      .replace(/([A-Z])/g, '-$1')
      .toLowerCase();
    if (isLayout || isFramePage || isParentLayout) {
      const name = route.path.substring(1).replaceAll('/', '_');
      route.name = parent ? `${parent.name}_${name}` : name;
    }

    if (route.meta.icon?.startsWith('local-icon-')) {
      route.meta.localIcon = route.meta.icon.replace('local-icon-', 'menu-');
      delete route.meta.icon;
    } else if (!isNotNull(route.meta.icon)) {
      route.meta.icon = defaultIcon;
    }
    // @ts-expect-error no hidden field
    route.meta.hideInMenu = route.hidden;
    // @ts-expect-error route.meta.activeMenu is activeMenu type
    route.meta.activeMenu = route.meta?.activeMenu?.substring(1);
    if (route.meta.hideInMenu && parent && !route.meta.activeMenu) {
      // @ts-expect-error parent.name is activeMenu type
      route.meta.activeMenu = parent.name;
    }
    // 是否需要keepAlive
    route.meta.keepAlive = !route.meta.noCache;

    if (isFramePage) {
      if (isExternalLink) {
        route.meta.href = String(route.meta.link);
        const random = Math.random().toString(36).slice(2, 12);
        route.name = random;
        route.path = `/${random}`;
        route.component = 'layout.base$view.iframe-page';
      } else {
        try {
          route.props = {
            // @ts-expect-error no query field
            url: JSON.parse(route.query)?.url
          };
        } catch {}
      }
      route.component = parent && !isExternalLink ? 'view.iframe-page' : 'layout.base$view.iframe-page';
    } else if (!isLayout && !isParentLayout && !isBlankLayout) {
      route.component = parent ? `view.${route.name}` : `layout.base$view.${route.name}`;
    } else if (!isBlankLayout) {
      route.component = isParentLayout ? undefined : 'layout.base';
    }

    delete route.meta.link;
    delete route.meta.noCache;
    // @ts-expect-error no query field
    delete route.query;
    // @ts-expect-error no hidden field
    delete route.hidden;

    route.children?.forEach(child => parseRouter(child, route));
  }

  const removeRouteFns: (() => void)[] = [];

  /** Global menus */
  const menus = ref<App.Global.Menu[]>([]);
  const searchMenus = computed(() => transformMenuToSearchMenus(menus.value));

  /** Get global menus */
  function getGlobalMenus(routes: ElegantConstRoute[]) {
    menus.value = getGlobalMenusByAuthRoutes(routes);
  }

  /** Update global menus by locale */
  function updateGlobalMenusByLocale() {
    menus.value = updateLocaleOfGlobalMenus(menus.value);
  }

  /** Cache routes */
  const cacheRoutes = ref<RouteKey[]>([]);

  /**
   * Exclude cache routes
   *
   * for reset route cache
   */
  const excludeCacheRoutes = ref<RouteKey[]>([]);

  /**
   * Get cache routes
   *
   * @param routes Vue routes
   */
  function getCacheRoutes(routes: RouteRecordRaw[]) {
    cacheRoutes.value = getCacheRouteNames(routes);
  }

  /**
   * Reset route cache
   *
   * @default router.currentRoute.value.name current route name
   * @param routeKey
   */
  async function resetRouteCache(routeKey?: RouteKey) {
    const routeName = routeKey || (router.currentRoute.value.name as RouteKey);

    excludeCacheRoutes.value.push(routeName);

    await nextTick();

    excludeCacheRoutes.value = [];
  }

  /** Global breadcrumbs */
  const breadcrumbs = computed(() => getBreadcrumbsByRoute(router.currentRoute.value, menus.value));

  /** Reset store */
  async function resetStore() {
    const routeStore = useRouteStore();

    routeStore.$reset();

    resetVueRoutes();

    // after reset store, need to re-init constant route
    await initConstantRoute();
  }

  /** Reset vue routes */
  function resetVueRoutes() {
    removeRouteFns.forEach(fn => fn());
    removeRouteFns.length = 0;
  }

  /** init constant route */
  async function initConstantRoute() {
    if (isInitConstantRoute.value) return;

    const staticRoute = createStaticRoutes();

    if (authRouteMode.value === 'static') {
      addConstantRoutes(staticRoute.constantRoutes);
    } else {
      addConstantRoutes(staticRoute.constantRoutes);
    }

    handleConstantAndAuthRoutes();

    setIsInitConstantRoute(true);

    tabStore.initHomeTab();
  }

  /** Init auth route */
  async function initAuthRoute() {
    // check if user info is initialized
    if (!authStore.userInfo.user?.userId) {
      await authStore.initUserInfo();
    }

    if (authRouteMode.value === 'static') {
      initStaticAuthRoute();
    } else {
      await initDynamicAuthRoute();
    }

    tabStore.initHomeTab();
  }

  /** Init static auth route */
  function initStaticAuthRoute() {
    const { authRoutes: staticAuthRoutes } = createStaticRoutes();

    if (authStore.isStaticSuper) {
      addAuthRoutes(staticAuthRoutes);
    } else {
      const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.roles);

      addAuthRoutes(filteredAuthRoutes);
    }

    handleConstantAndAuthRoutes();

    setIsInitAuthRoute(true);
  }

  /** Init dynamic auth route */
  async function initDynamicAuthRoute() {
    const { data, error } = await fetchGetRoutes();

    if (!error) {
      addAuthRoutes(data);

      handleConstantAndAuthRoutes();

      handleUpdateRootRouteRedirect(routeHome.value);

      setIsInitAuthRoute(true);
    } else {
      // if fetch user routes failed, reset store
      authStore.resetStore();
    }
  }

  /** handle constant and auth routes */
  function handleConstantAndAuthRoutes() {
    const allRoutes = [...constantRoutes.value, ...authRoutes.value];

    const sortRoutes = sortRoutesByOrder(allRoutes);

    const vueRoutes = getAuthVueRoutes(sortRoutes);

    resetVueRoutes();

    addRoutesToVueRouter(vueRoutes);

    getGlobalMenus(sortRoutes);

    getCacheRoutes(vueRoutes);
  }

  /**
   * Add routes to vue router
   *
   * @param routes Vue routes
   */
  function addRoutesToVueRouter(routes: RouteRecordRaw[]) {
    routes.forEach(route => {
      const removeFn = router.addRoute(route);
      addRemoveRouteFn(removeFn);
    });
  }

  /**
   * Add remove route fn
   *
   * @param fn
   */
  function addRemoveRouteFn(fn: () => void) {
    removeRouteFns.push(fn);
  }

  /**
   * Update root route redirect when auth route mode is dynamic
   *
   * @param redirectKey Redirect route key
   */
  function handleUpdateRootRouteRedirect(redirectKey: LastLevelRouteKey) {
    const redirect = getRoutePath(redirectKey);

    if (redirect) {
      const rootRoute: CustomRoute = { ...ROOT_ROUTE, redirect };

      router.removeRoute(rootRoute.name);

      const [rootVueRoute] = getAuthVueRoutes([rootRoute]);

      router.addRoute(rootVueRoute);
    }
  }

  /**
   * Get is auth route exist
   *
   * @param routePath Route path
   */
  async function getIsAuthRouteExist(routePath: RouteMap[RouteKey]) {
    const routeName = getRouteName(routePath);

    if (!routeName) {
      return false;
    }

    const { authRoutes: staticAuthRoutes } = createStaticRoutes();
    return isRouteExistByRouteName(routeName, staticAuthRoutes);
  }

  /**
   * Get selected menu key path
   *
   * @param selectedKey Selected menu key
   */
  function getSelectedMenuKeyPath(selectedKey: string) {
    return getSelectedMenuKeyPathByKey(selectedKey, menus.value);
  }

  async function onRouteSwitchWhenLoggedIn() {
    // some global init logic when logged in and switch route
  }

  async function onRouteSwitchWhenNotLoggedIn() {
    // some global init logic if it does not need to be logged in
  }

  return {
    resetStore,
    routeHome,
    menus,
    searchMenus,
    updateGlobalMenusByLocale,
    cacheRoutes,
    excludeCacheRoutes,
    resetRouteCache,
    breadcrumbs,
    initConstantRoute,
    isInitConstantRoute,
    initAuthRoute,
    isInitAuthRoute,
    setIsInitAuthRoute,
    getIsAuthRouteExist,
    getSelectedMenuKeyPath,
    onRouteSwitchWhenLoggedIn,
    onRouteSwitchWhenNotLoggedIn
  };
});
