# Source: controllers/chiefPlugin.js

    /**
     * @file chiefPlugin.js
     * @module chiefPlugin
     * @description Contains all of the functions to manage the loading, unloading, reloading,
     * registering, unregistering of plugins and plugin metaData.
     * @requires module:pluginBroker
     * @requires module:loggers
     * @requires module:stack
     * @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants}
     * @requires {@link https://www.npmjs.com/package/path|path}
     * @author Seth Hollingsead
     * @date 2022/09/02
     * @copyright Copyright © 2022-… by Seth Hollingsead. All rights reserved
     */

    // Internal imports
    import pluginBroker from '../brokers/pluginBroker.js';
    import loggers from '../executrix/loggers.js'
    import stack from '../structures/stack.js'
    // External imports
    import hayConst from '@haystacks/constants';
    import path from 'path';

    const {bas, msg, sys, wrd} = hayConst;
    const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url));
    // controllers.chiefPlugin.
    const namespacePrefix = wrd.ccontrollers + bas.cDot + baseFileName + bas.cDot;

    /**
     * @function loadPluginRegistryData
     * @description Loads the plugin registry meta data which includes paths to plugins.
     * @param {string} pluginRegistryPath
     * @return {object} A JSON object loaded from the specified path with the plugin paths and plugin metaData.
     * @author Seth Hollingsead
     * @date 2022/09/13
     */
    async function loadPluginRegistryData(pluginRegistryPath) {
      let functionName = loadPluginRegistryData.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      // pluginRegistryPath
      await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryPathIs + pluginRegistryPath);
      let returnData = {};
      returnData = await pluginBroker.loadPluginRegistry(pluginRegistryPath);
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function persistPluginRegistryToDataStructure
     * @description Saves the plugin registry data to the D-data structure.
     * @param {object} pluginRegistryData
     * @return {boolean} True or False to indicate if the persistense was successful or not.
     * @author Seth Hollingsead
     * @date 2022/09/13
     */
    async function persistPluginRegistryToDataStructure(pluginRegistryData) {
      let functionName = persistPluginRegistryToDataStructure.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      // pluginRegistryData is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryDataIs + JSON.stringify(pluginRegistryData));
      let returnData = false;
      returnData = await pluginBroker.storePluginRegistryInDataStructure(pluginRegistryData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function getAllPluginsInRegistry
     * @description This is a wrapper function for pluginBroker.listPluginsInRegistry.
     * @return {array<string>} A list array of the names of the plugins in the plugin registry.
     * @author Seth Hollingsead
     * @date 2022/09/15
     */
    async function getAllPluginsInRegistry() {
      let functionName = getAllPluginsInRegistry.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      let returnData = [];
      returnData = await pluginBroker.listPluginsInRegistry();
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function getAllPluginsPathsInRegistry
     * @description This is a wrapper function for pluginBroker.listPluginsPathsInRegistry
     * The purpose is to get the array of paths for all of the plugins that must be loaded.
     * @return {array<string>} A list array of all the paths of all the plugins in the plugin registry.
     * @author Seth Hollingsead
     * @date 2022/09/20
     */
    async function getAllPluginsPathsInRegistry() {
      let functionName = getAllPluginsPathsInRegistry.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      let returnData = [];
      returnData = await pluginBroker.listPluginsPathsInRegistry();
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function getAllPluginsInRegistryPath
     * @description This is a wrapper function for pluginBroker.listPluginsInRegistryPath.
     * @return {array<string>} A list array of the names of the plugins located at the specified path on
     * the local system from the plugins registry data hive.
     * @author Seth Hollingsead
     * @date 2022/09/15
     */
    async function getAllPluginsInRegistryPath() {
      let functionName = getAllPluginsInRegistryPath.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      let returnData = [];
      returnData = await pluginBroker.listPluginsInRegistryPath()
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function countAllPluginsInRegistry
     * @description This is a wrapper function for pluginBroker.countPluginsInRegistry.
     * @return {integer} The count of the number of plugins listed in the plugin registry data hive.
     * @author Seth Hollingsead
     * @date 2022/09/15
     */
    async function countAllPluginsInRegistry() {
      let functionName = countAllPluginsInRegistry.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      let returnData = 0;
      returnData = await pluginBroker.countPluginsInRegistry();
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function countAllPluginsInRegistryPath
     * @description This is a wrapper function for pluginBroker.countPluginsInRegistryPath.
     * @return {integer} The count of the number of plugin sub-folders in the plugins path listed in the plugin registry data hive.
     * @author Seth Hollingsead
     * @date 2022/09/15
     */
    async function countAllPluginsInRegistryPath() {
      let functionName = countAllPluginsInRegistryPath.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      let returnData = 0;
      returnData = await pluginBroker.countPluginsInRegistryPath();
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function registerNamedPlugin
     * @description This is a wrapper function for pluginBroker.registerPlugin.
     * @param {string} pluginName The name of the plugin that should be registered.
     * @param {string} pluginPath The path to the plugin, to be added to the plugin registry.
     * This should be the path to the plugin/package.json file, but not including the package.json as part of the path URI.
     * @return {boolean} True or False to indicate if the plugin was added to the plugin registry successfully or not.
     * @author Seth Hollingsead
     * @date 2022/09/15
     */
    async function registerNamedPlugin(pluginName, pluginPath) {
      let functionName = registerNamedPlugin.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      // pluginName is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName);
      // pluginPath is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginPathIs + pluginPath);
      let returnData = false;
      returnData = await pluginBroker.registerPlugin(pluginName, pluginPath);
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function unregisterNamedPlugin
     * @description This is a wrapper function for pluginBroker.unregisterPlugin.
     * @param {string} pluginName The name of the plugin that should be removed from the plugin registry.
     * @return {boolean} True or False to indicate if the plugin was removed from the plugin registry successfully or not.
     * @author Seth Hollingsead
     * @date 2022/09/15
     */
    async function unregisterNamedPlugin(pluginName) {
      let functionName = unregisterNamedPlugin.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      // pluginName is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName);
      let returnData = false;
      returnData = await pluginBroker.unregisterPlugin(pluginName);
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function synchronizePluginRegistryWithPath
     * @description This is a wrapper function for pluginBroker.syncPluginRegistryWithPluginRegistryPath.
     * @return {boolean} True or False to indicate if the synchronization was performed successfully or not.
     * @author Seth Hollingsead
     * @date 2022/09/15
     * @NOTE It is expected that the number of plugins loaded at any one time will not be crazy high.
     * The function that is called in the pluginBroker.syncPluginRegistryWithPluginRegistryPath will
     * do a O(n^2) brute force search,
     * if the number of plugins needed at any one time ever grows much over 100, then this solution will need to be re-evaluated!
     */
    async function synchronizePluginRegistryWithPath() {
      let functionName = synchronizePluginRegistryWithPath.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      let returnData = false;
      returnData = await pluginBroker.syncPluginRegistryWithPluginRegistryPath();
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function clearPluginRegistry
     * @description This is a wrapper function for pluginBroker.unregisterAllPlugins.
     * @return {boolean} True or False to indicate if the plugin registry data hive was cleared successfully or not.
     * @author Seth Hollingsead
     * @date 2022/09/15
     */
    async function clearPluginRegistry() {
      let functionName = clearPluginRegistry.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      let returnData = false;
      returnData = await pluginBroker.unregisterAllPlugins();
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function savePluginRegistryDisk
     * @description This is a wrapper function for pluginBroker.savePluginRegistry.
     * @return {boolean} True or False to indicate if the plugin registry was successfully saved to the disk or not.
     * @author Seth Hollingsead
     * @date 2022/09/15
     */
    async function savePluginRegistryDisk() {
      let functionName = savePluginRegistryDisk.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      let returnData = false;
      returnData = await pluginBroker.savePluginRegistry();
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function loadAllPluginsMetaData
     * @description Loads the plugin meta data for all of the plugins in the input plugins path array.
     * @param {array<string>} pluginsPaths
     * @return {array<object>} An array that contains all of the meta-data,
     * for each of the plugins in the input pluginsPaths array.
     * @author Seth Hollingsead
     * @date 2022/09/02
     */
    async function loadAllPluginsMetaData(pluginsPaths) {
      let functionName = loadAllPluginsMetaData.name
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      // pluginPaths are:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginsPathsAre + JSON.stringify(pluginsPaths));
      let returnData = [];
      let index = 0;
      if (pluginsPaths && pluginsPaths.length > 0) {
        for (let pluginKey in pluginsPaths) {
          // pluginKey is:
          await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginKeyIs + pluginKey);
          let pluginPath = pluginsPaths[pluginKey];
          // pluginPath is:
          await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginPathIs + pluginPath);
          returnData[index] = {};
          if (pluginPath != '') {
            returnData[index] = await pluginBroker.loadPluginMetaData(pluginPath);
          } // End-if (pluginPath != '')
          // loadedPluginMetaData at index:
          await loggers.consoleLog(namespacePrefix + functionName, msg.cloadedPluginMetaDataAtIndex +
            index + ' data: ' + JSON.stringify(returnData[index]));
          index = index + 1;
        } // End-for (let pluginPath in pluginsPaths)
      } else {
        // ERROR: No plugin paths provided, please provide paths and try again.
        let errorMessage = msg.cErrorLoadAllPluginsMetaDataMessage01;
        console.log(errorMessage); // Make sure to output the error!
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function loadAllPluginsExecutionPaths
     * @description Extracts the path to the main entry point for each plugin and does a path join to form a
     * fully qualified path, then converts the fully qualified path to a file URI so it can be imported.
     * @param {array<object>} pluginsMetaData An array of JSON objects that contain all of the package.json data
     * loaded for each plugin that should be imported.
     * @param {array<string>} pluginsPaths An array of fully qualified paths where each plugin was loaded from,
     * used to path.join with the execution path entry point file for each plugin.
     * @return {array<string>} An array of fully qualified path URI's for each of the main entry points for
     * all of the plugins that should be loaded.
     * @author Seth Hollingsead
     * @date 2022/09/02
     */
    async function loadAllPluginsExecutionPaths(pluginsMetaData, pluginsPaths) {
      let functionName = loadAllPluginsExecutionPaths.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      // pluginsMetaData is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginsMetaDataIs + JSON.stringify(pluginsMetaData));
      // pluginPaths are:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginsPathsAre + JSON.stringify(pluginsPaths));
      let returnData = [];
      let index = 0;
      if (pluginsMetaData && pluginsMetaData.length > 0) {
        for (let pluginMetaDataKey in pluginsMetaData) {
          // pluginMetaDataKey is:
          await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginMetaDataKeyIs + pluginMetaDataKey);
          let pluginMetaData = pluginsMetaData[pluginMetaDataKey];
          // pluginMetaData is:
          await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginMetaDataIs + JSON.stringify(pluginMetaData));
          returnData[index] = '';
          if (pluginMetaData) {
            returnData[index] = await pluginBroker.extractAndProcessPluginEntryPointURI(pluginMetaData, pluginsPaths[index]);
            // pluginPathURI at index:
            await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginPathUriAtIndex + index + bas.cSpace + wrd.cData + bas.cColon + bas.cSpace + returnData[index]);
          }
          index = index + 1;
        } // End-for (let pluginMetaData in pluginsMetaData)
      } else {
        // ERROR: No plugin meta data was found. Please ensure the correct path is provided and try again.
        let errorMessage = msg.cErrorLoadAllPluginsExecutionPathsMessage01;
        console.log(errorMessage); // Make sure to output the error!
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function loadAllPlugins
     * @description This is a wrapper function that iterates over the list of plugin URI paths to import and calls the
     * loadPlugin function on the pluginBroker to actually load the plugin data, metaData, business rules, commands,
     * constants, constants validation, command aliases, configuration settings, debug settings and workflows.
     * @param {array<string>} pluginsExecutionPaths An array of fully qualified path URI's to be imported and executed for each plugin.
     * @param {array<object>} pluginsMetaData An array of JSON objects that contain all of the package.json data
     * loaded for each plugin that should be imported, we are passing this along here,
     * to have access to the name of each plugin as it is loaded.
     * @return {object} An object containing objects that contain all of the data and functions from each plugin,
     * these will need to be decomposed and merged with the D-data structure as appropriate.
     * @author Seth Hollingsead
     * @date 2022/09/01
     */
    async function loadAllPlugins(pluginsExecutionPaths, pluginsMetaData) {
      let functionName = loadAllPlugins.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      // pluginsExecutionPaths is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginsExecutionPathsIs + JSON.stringify(pluginsExecutionPaths));
      // pluginsMetaData is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cplugnsMetaDataIs + JSON.stringify(pluginsMetaData));
      let returnData = {};
      let index = 0;
      if (pluginsExecutionPaths && pluginsMetaData && pluginsExecutionPaths.length > 0 && pluginsMetaData.length > 0) {
        stack.initStack(sys.cpluginsLoaded);
        for (let pluginExecutionPathKey in pluginsExecutionPaths) {
          let pluginExecutionPath = pluginsExecutionPaths[pluginExecutionPathKey];
          let pluginMetaData = pluginsMetaData[index];
          if (pluginExecutionPath && pluginMetaData) {
            // pluginExectuionPath is:
            await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginExecutionPathIs + pluginExecutionPath);
            // pluginMetaData is:
            await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginMetaDataIs + JSON.stringify(pluginMetaData));
            returnData[pluginMetaData[wrd.cname]] = {}; // Initialize the data structure
            // Load the data and add it.
            try {
              returnData[pluginMetaData[wrd.cname]] = await pluginBroker.loadPlugin(pluginExecutionPath);
            } catch (err) {
              // Failed to load the plugin:
              console.log(msg.cERROR_Colon + namespacePrefix + functionName + msg.cloadAllPluginsMessage01 + pluginMetaData[wrd.cname]);
              // plugin entry point path:
              console.log(msg.cloadAllPluginsMessage02 + pluginExecutionPath);
              console.log(msg.cERROR_Colon + err);
              stack.push(sys.cpluginsLoaded, [pluginMetaData[wrd.cname], false]);
            }
            // Push to a stack that we have successfully loaded the currently named plugin,
            // so that all the plugins loaded can be verified.
            stack.push(sys.cpluginsLoaded, [pluginMetaData[wrd.cname], true]);
          } else {
            // Failed to load the plugin:
            console.log(msg.cERROR_Colon + namespacePrefix + functionName + msg.cloadAllPluginsMessage01 + pluginMetaData[wrd.cname]);
            // plugin entry point path:
            console.log(msg.cloadAllPluginsMessage02 + pluginExecutionPath);
            stack.push(sys.cpluginsLoaded, [pluginMetaData[wrd.cname], false]);
          }
        } // End-for (let pluginExecutionPath in pluginsExecutionPaths)
      } else {
        // ERROR: No plugin execution paths or plugins metaData was specified:
        console.log(msg.cloadAllPluginsMessage03 + namespacePrefix + functionName);
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function verifyAllPluginsLoaded
     * @description Examins the pluginsLoaded stack to confirm that none of the plugins which
     * were supposed to have been loaded failed to load.
     * @return {boolean} True or False to indicate if all of the plugins were loaded successfully or not.
     * @author Seth Hollingsead
     * @date 2022/09/06
     */
     async function verifyAllPluginsLoaded() {
      let functionName = verifyAllPluginsLoaded.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      let returnData = false;
      let foundFailedPlugin = false;
      let stackLength = stack.length(sys.cpluginsLoaded);
      for (let i = 0; i <= stackLength; i++) {
        let pluginLoadedObject = stack.pop(sys.cpluginsLoaded);
        if (Array.isArray(pluginLoadedObject)) {
          if (pluginLoadedObject[1] === false) {
            foundFailedPlugin = true;
          }
        } // End-if (Array.isArray(pluginLoadedObject))
      } // End-for (let i = 0; i <= stackLength; i++)
      returnData = !foundFailedPlugin; // Invert the logic
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    export default {
      loadPluginRegistryData,
      persistPluginRegistryToDataStructure,
      getAllPluginsInRegistry,
      getAllPluginsPathsInRegistry,
      getAllPluginsInRegistryPath,
      countAllPluginsInRegistry,
      countAllPluginsInRegistryPath,
      registerNamedPlugin,
      unregisterNamedPlugin,
      synchronizePluginRegistryWithPath,
      clearPluginRegistry,
      savePluginRegistryDisk,
      loadAllPluginsMetaData,
      loadAllPluginsExecutionPaths,
      loadAllPlugins,
      verifyAllPluginsLoaded
    };