import { boot } from 'quasar/wrappers';
import { FG_Plugin } from 'src/plugins';
import routes from 'src/router/routes';
import { api } from 'boot/axios';
import { AxiosResponse } from 'axios';
import { RouteRecordRaw } from 'vue-router';
import { Notify } from 'quasar';

const config: { [key: string]: Array<string> } = {
  // Do not change required Modules !!
  requiredModules: ['User'],
  // here you can import plugins.
  loadModules: ['Balance', 'Schedule', 'Pricelist'],
};

/* Stop!

// do not change anything here !!

// You can not even read? I said stop!

// Really are you stupid? Stop scrolling down here!

// Every line you scroll down, an unicorn will die painfully!

// Ok you must hate unicorns... But what if I say you I joked... Baby otters will die!

         .-"""-.
        /      o\
       |    o   0).-.
       |       .-;(_/     .-.
        \     /  /)).---._|  `\   ,
         '.  '  /((       `'-./ _/|
           \  .'  )        .-.;`  /
            '.             |  `\-'
              '._        -'    /
                 ``""--`------`
*/

/****************************************************
 ******** Internal area for some magic **************
 ****************************************************/

interface BackendPlugin {
  permissions: string[];
  version: string;
}

interface BackendPlugins {
  [key: string]: BackendPlugin;
}

interface Backend {
  plugins: BackendPlugins;
  version: string;
}
export { Backend };

// Combine routes, shortcuts and widgets from plugins

/**
 * Helper function, set permissions from MenuRoute to meta from RouteRecordRaw
 * @param object MenuRoute to set route meta
 */
function setPermissions(object: FG_Plugin.MenuRoute) {
  if (object.permissions !== undefined) {
    if (object.route.meta === undefined) object.route.meta = {};
    object.route.meta['permissions'] = object.permissions;
  }
}

/**
 * Helper function to convert MenuRoute to the parents RouteRecordRaw
 * @param parent Parent RouteRecordRaw
 * @param children MenuRoute to convert
 */
function convertRoutes(parent: RouteRecordRaw, children?: FG_Plugin.MenuRoute[]) {
  if (children !== undefined) {
    children.forEach((child) => {
      setPermissions(child);
      convertRoutes(child.route, child.children);
      if (parent.children === undefined) parent.children = [];
      parent.children.push(child.route);
    });
  }
}

/**
 * Combines routes from plugin MenuRoute to Vue-Router RouteRecordRaw to get a clean route-tree
 * @param target
 * @param source
 * @param mainPath
 */
function combineMenuRoutes(
  target: RouteRecordRaw[],
  source: FG_Plugin.MenuRoute[],
  mainPath: '/' | '/in' = '/'
): RouteRecordRaw[] {
  // Search parent
  target.forEach((target) => {
    if (target.path === mainPath) {
      // Parent found = target
      source.forEach((sourceMainConfig: FG_Plugin.MenuRoute) => {
        // Check if source is already in target
        const targetMainConfig = target.children?.find((targetMainConfig: RouteRecordRaw) => {
          return sourceMainConfig.route.path === targetMainConfig.path;
        });
        // Already in target routes, add only children
        if (targetMainConfig) {
          convertRoutes(targetMainConfig, sourceMainConfig.children);
        } else {
          // Append to target
          if (target.children === undefined) {
            target.children = [];
          }
          convertRoutes(sourceMainConfig.route, sourceMainConfig.children);
          if (
            sourceMainConfig.children &&
            sourceMainConfig.children.length > 0 &&
            !sourceMainConfig.route.component
          )
            Object.assign(sourceMainConfig.route, {
              component: () => import('src/components/navigation/EmptyParent.vue'),
            });
          target.children.push(sourceMainConfig.route);
        }
      });
    }
  });
  return target;
}

function combineRoutes(
  target: RouteRecordRaw[],
  source: FG_Plugin.NamedRouteRecordRaw[],
  mainPath: '/' | '/in'
) {
  // Search parent
  target.forEach((target) => {
    if (target.path === mainPath) {
      // Parent found = target
      source.forEach((sourceRoute) => {
        // Check if source is already in target
        const targetRoot = target.children?.find(
          (targetRoot) => sourceRoute.path === targetRoot.path
        );
        // Already in target routes, add only children
        if (targetRoot) {
          if (targetRoot.children === undefined) targetRoot.children = [];
          targetRoot.children.push(...(sourceRoute.children || []));
        } else {
          // Append to target
          if (target.children === undefined) target.children = [];
          if (
            sourceRoute.children &&
            sourceRoute.children.length > 0 &&
            sourceRoute.component === undefined
          )
            Object.assign(sourceRoute, {
              component: () => import('src/components/navigation/EmptyParent.vue'),
            });
          target.children.push(sourceRoute);
        }
      });
    }
  });
}

/**
 * Combine MenuRoutes into Flaschengeist MenuLinks for the main menu
 * @param target Flaschengeist list of menu links
 * @param source MenuRoutes to combine
 */
function combineMenuLinks(target: FG_Plugin.MenuLink[], source: FG_Plugin.MenuRoute) {
  let idx = target.findIndex((link) => link.title == source.title);
  // Link not found, add new one
  if (idx === -1) {
    idx += target.push({
      title: source.title,
      icon: source.icon,
      link: source.route.name,
      permissions: source.permissions,
    });
  }
  if (target[idx].children === undefined) {
    target[idx].children = [];
  }
  source.children?.forEach((sourceChild) => {
    target[idx].children?.push({
      title: sourceChild.title,
      icon: sourceChild.icon,
      link: sourceChild.route.name,
      permissions: sourceChild.permissions,
    });
  });
}

/**
 * Combine shortcuts from Plugin MenuRouts into the Flaschenbeist Shortcut list
 * @param target Flaschengeist list of shortcuts
 * @param source MenuRoutes to extract shortcuts from
 */
function combineShortcuts(target: FG_Plugin.Shortcut[], source: FG_Plugin.MenuRoute[]) {
  source.forEach((route) => {
    if (route.shortcut) {
      target.push(<FG_Plugin.Shortcut>{
        link: route.route.name,
        icon: route.icon,
        permissions: route.permissions,
      });
    }
    if (route.children) {
      combineShortcuts(target, route.children);
    }
  });
}

/**
 * Load a Flaschengeist plugin
 * @param loadedPlugins Flaschgeist object
 * @param pluginName Plugin to load
 * @param context RequireContext of plugins
 * @param router VueRouter instance
 */
function loadPlugin(
  loadedPlugins: FG_Plugin.Flaschengeist,
  pluginName: string,
  context: __WebpackModuleApi.RequireContext,
  backend: Backend
) {
  // Check if already loaded
  if (loadedPlugins.plugins.findIndex((p) => p.name === pluginName) !== -1) return true;

  // Search if plugin is installed
  const available = context.keys();
  const plugin = available.includes(`./${pluginName.toLowerCase()}/plugin.ts`)
    ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
      <FG_Plugin.Plugin>context(`./${pluginName.toLowerCase()}/plugin.ts`).default
    : undefined;

  if (!plugin) {
    // Plugin is not found, results in an error
    console.exception(`Could not find required Plugin ${pluginName}`);
    return false;
  } else {
    // Plugin found. Check backend dependencies
    if (
      !plugin.requiredBackendModules.every((required) => backend.plugins[required] !== undefined)
    ) {
      console.error(`Plugin ${pluginName}: Backend modules not satisfied`);
      return false;
    }

    // Check frontend dependencies
    if (
      !plugin.requiredModules.every((required) =>
        loadPlugin(loadedPlugins, required, context, backend)
      )
    ) {
      console.error(`Plugin ${pluginName}: Backend modules not satisfied`);
      return false;
    }

    // Start combining and loading routes, shortcuts etc
    if (plugin.internalRoutes) {
      combineRoutes(loadedPlugins.routes, plugin.internalRoutes, '/in');
    }

    if (plugin.innerRoutes) {
      // Routes for Vue Router
      combineMenuRoutes(loadedPlugins.routes, plugin.innerRoutes, '/in');
      // Combine links for menu
      plugin.innerRoutes.forEach((route) => combineMenuLinks(loadedPlugins.menuLinks, route));
      // Combine shortcuts
      combineShortcuts(loadedPlugins.shortcuts, plugin.innerRoutes);
    }

    if (plugin.outerRoutes) {
      combineMenuRoutes(loadedPlugins.routes, plugin.outerRoutes);
      combineShortcuts(loadedPlugins.outerShortcuts, plugin.outerRoutes);
    }

    if (plugin.widgets.length > 0) {
      plugin.widgets.forEach((widget) => (widget.name = plugin.name + '_' + widget.name));
      Array.prototype.push.apply(loadedPlugins.widgets, plugin.widgets);
    }

    loadedPlugins.plugins.push({
      name: plugin.name,
      version: plugin.version,
    });

    return plugin;
  }
}

/**
 * Loading backend information
 * @returns Backend object or null
 */
async function getBackend() {
  try {
    const { data }: AxiosResponse<Backend> = await api.get('/');
    return data;
  } catch (e) {
    console.warn(e);
    return null;
  }
}

/**
 * Boot file, load all required plugins, check for dependencies
 */
export default boot(async ({ router, app }) => {
  const backend = await getBackend();
  if (!backend) {
    void router.push({ name: 'error' });
    return;
  }

  const loadedPlugins: FG_Plugin.Flaschengeist = {
    routes,
    plugins: [],
    menuLinks: [],
    shortcuts: [],
    outerShortcuts: [],
    widgets: [],
  };

  // get all plugins
  const pluginsContext = require.context('src/plugins', true, /.+\/plugin.ts$/);

  // Start loading plugins
  // Load required modules, if not found or error when loading this will forward the user to the error page
  config.requiredModules.forEach((required) => {
    const plugin = loadPlugin(loadedPlugins, required, pluginsContext, backend);
    if (!plugin) {
      void router.push({ name: 'error' });
      return;
    }
  });

  // Load user defined plugins
  // If there is an error with loading a plugin, the user will get informed.
  const failed: string[] = [];
  config.loadModules.forEach((required) => {
    const plugin = loadPlugin(loadedPlugins, required, pluginsContext, backend);
    if (!plugin) {
      failed.push(required);
    }
  });
  if (failed.length > 0) {
    // Log failed plugins
    console.error('Could not load all plugins', failed);
    // Inform user about error
    Notify.create({
      type: 'negative',
      message:
        'Fehler beim Laden: Nicht alle Funktionen stehen zur Verfügung. Bitte wende dich an den Admin!',
      timeout: 10000,
      progress: true,
    });
  }

  // Sort widgets by priority
  loadedPlugins.widgets.sort((a, b) => b.priority - a.priority);

  // Add loaded routes to router
  loadedPlugins.routes.forEach((route) => router.addRoute(route));

  // save plugins in VM-variable
  app.provide('flaschengeist', loadedPlugins);
});