# Source: commandsBlob/commands/system.js

    /**
    * @file system.js
    * @module system
    * @description Contains all of the system level commands.
    * @requires module:commandBroker
    * @requires module:ruleBroker
    * @requires module:workflowBroker
    * @requires module:configurator
    * @requires module:loggers
    * @requires module:data
    * @requires module:stack
    * @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants}
    * @requires {@link https://www.npmjs.com/package/figlet|figlet}
    * @requires {@link https://www.npmjs.com/package/path|path}
    * @author Seth Hollingsead
    * @date 2022/04/20
    * @copyright Copyright © 2022-… by Seth Hollingsead. All rights reserved
    */

    // Internal imports
    import commandBroker from '../../brokers/commandBroker.js';
    import ruleBroker from '../../brokers/ruleBroker.js';
    import workflowBroker from '../../brokers/workflowBroker.js';
    import configurator from '../../executrix/configurator.js';
    import loggers from '../../executrix/loggers.js';
    import D from '../../structures/data.js';
    import stack from '../../structures/stack.js';
    // External imports
    import hayConst from '@haystacks/constants';
    import figlet from 'figlet';
    import path from 'path';

    const {bas, biz, cfg, msg, sys, wrd} = hayConst;
    const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url));
    // framework.commandsBlob.commands.system.
    const namespacePrefix = wrd.cframework + bas.cDot + sys.ccommandsBlob + bas.cDot + wrd.ccommands + bas.cDot + baseFileName + bas.cDot;

    /**
     * @function echoCommand
     * @description Returns the input as the output without any changes.
     * @param {array<boolean|string|integer>} inputData String that should be echoed.
     * inputData[0] === 'echoCommand'
     * @param {string} inputMetaData Not used for this business rule.
     * @return {array<boolean,string|integer|boolean|object|array>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by the command output.
     * @author Seth Hollingsead
     * @date 2022/02/04
     */
    async function echoCommand(inputData, inputMetaData) {
      let functionName = echoCommand.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, ''];
      let errorMessage = '';
      if (inputData) {
        inputData.shift();
        console.log(inputData.join(bas.cSpace));
        returnData[1] = inputData.join(bas.cSpace);
      } else {
        // Nothing to echo.
        errorMessage = msg.cNothingToEcho;
        console.log(errorMessage);
        returnData[1] = errorMessage;
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function exit
     * @description Returns false so the entire application can exit.
     * @param {array<boolean|string|integer>} inputData Not used for this command.
     * inputData[0] === 'exit'
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,string|integer|boolean|object|array>} An array with a boolean False value to
     * indicate if the application exit, followed by the command output.
     * @author Seth Hollingsead
     * @date 2022/02/04
     */
    async function exit(inputData, inputMetaData) {
      let functionName = exit.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [false, true];
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function version
     * @description Displays the current version number for the current application.
     * @param {array<boolean|string|integer>} inputData Not used for this command.
     * inputData[0] = 'version'
     * inputData[1] === 'application|framework' (optional)
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,string|integer|boolean|object|array>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by the command output.
     * @author Seth Hollingsead
     * @date 2022/02/04
     */
    async function version(inputData, inputMetaData) {
      let functionName = version.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, ''];
      let configVersion = '';
      let appContext = '';
      if (inputData.length === 2) {
         appContext = inputData[1];
         if (appContext.toUpperCase() === wrd.cAPPLICATION) {
            configVersion = await configurator.getConfigurationSetting(wrd.csystem, sys.cApplicationVersionNumber);
         } else if (appContext.toUpperCase() === wrd.cFRAMEWORK) {
            configVersion = await configurator.getConfigurationSetting(wrd.csystem, sys.cFrameworkVersionNumber);
         } else if (appContext.toUpperCase().includes(wrd.cPLUGIN)) {
          configVersion = await configurator.getConfigurationSetting(wrd.csystem, sys.cPluginVersionNumber);
         } else {
            configVersion = await configurator.getConfigurationSetting(wrd.csystem, sys.cApplicationVersionNumber);
         }
      } else {
        configVersion = await configurator.getConfigurationSetting(wrd.csystem, sys.cApplicationVersionNumber);
      }
      console.log(configVersion);
      returnData[1] = configVersion;
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function about
     * @description Displays the message about the current application.
     * @param {array<boolean|string|integer>} inputData Not used for this command.
     * inputData[0] === 'about'
     * inputData[1] === 'application|framework' (optional)
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,string|integer|boolean|object|array>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by the command output.
     * @author Seth Hollingsead
     * @date 2022/02/04
     */
    async function about(inputData, inputMetaData) {
      let functionName = about.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, ''];
      let configDescription = '';
      let appContext = '';
      if (inputData.length === 2) {
        appContext = inputData[1];
        if (appContext.toUpperCase() === wrd.cAPPLICATION) {
          configDescription = await configurator.getConfigurationSetting(wrd.csystem, sys.cApplicationDescription);
        } else if (appContext.toUpperCase() === wrd.cFRAMEWORK) {
          configDescription = await configurator.getConfigurationSetting(wrd.csystem, sys.cFrameworkDescription);
        } else if (appContext.toUpperCase().includes(wrd.cPLUGIN)) {
          configDescription = await configurator.getConfigurationSetting(wrd.csystem, sys.cPluginDescription);
        } else {
          configDescription = await configurator.getConfigurationSetting(wrd.csystem, sys.cApplicationDescription);
        }
      } else {
        configDescription = await configurator.getConfigurationSetting(wrd.csystem, sys.cApplicationDescription);
      }
      console.log(configDescription);
      returnData[1] = configDescription;
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function name
     * @description Displays the name of the current application in standard font format, nothing special.
     * Optional argument to output in figlet font.
     * @param {array<boolean|string|integer>} inputData An array that could really contain anything depending
     * on what the user entered, but the function converts and filters out for a boolean
     * True or False value internally to the function.
     * inputData[0] === 'name'
     * inputData[1] === 'application|framework' (optional)
     * inputData[2] === 'true|false' (optional)
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,string|integer|boolean|object|array>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by the command output.
     * @author Seth Hollingsead
     * @date 2022/02/04
     */
    async function name(inputData, inputMetaData) {
      let functionName = name.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, ''];
      let reportedName = '';
      let figletFont = '';
      let appContext = '';
      let useFancyFont = false;
      if (inputData.length === 2) {
        appContext = inputData[1];
      } // End-if (inputData.length === 2)
      if (inputData.length === 3) {
        appContext = inputData[1];
        useFancyFont = await ruleBroker.processRules([inputData[2], ''], [biz.cstringToDataType]);
      } // End-if (inputData.length === 3)
      if (appContext !== '') {
        if (appContext.toUpperCase() === wrd.cAPPLICATION) {
          reportedName = await configurator.getConfigurationSetting(wrd.csystem, sys.cApplicationName);
        } else if (appContext.toUpperCase() === wrd.cFRAMEWORK) {
          reportedName = await configurator.getConfigurationSetting(wrd.csystem, sys.cFrameworkName);
        } else if (appContext.toUpperCase().includes(wrd.cPLUGIN)) {
          reportedName = await configurator.getConfigurationSetting(wrd.csystem, sys.cPluginName);
        } else {
          reportedName = await configurator.getConfigurationSetting(wrd.csystem, sys.cApplicationName);
        }
      } else {
        reportedName = await configurator.getConfigurationSetting(wrd.csystem, sys.cApplicationName);
      }
      if (useFancyFont === true) {
        figletFont = await configurator.getConfigurationSetting(wrd.csystem, cfg.cfigletFont);
        console.log(figlet.textSync(reportedName, {font: figletFont, horizontalLayout: wrd.cfull}));
      } else {
        console.log(reportedName);
      }
      returnData[1] = reportedName;
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function clearScreen
     * @description Clears all data from the console cache by printing a bunch of blank lines to the screen.
     * @param {string} inputData Not used for this command.
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,string|integer|boolean|object|array>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by the command output.
     * @author Seth Hollingsead
     * @date 2022/02/04
     */
    // eslint-disable-next-line no-unused-vars
    async function clearScreen(inputData, inputMetaData) {
      let functionName = clearScreen.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      // loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      // loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + JSON.stringify(inputMetaData));
      let returnData = [true, {}];
      // console.clear(); // This will clear the screen, but not the cache, you can still scroll up and see the previous commands.
      // process.stdout.write('\u001B[2J\u-001B[0;0f'); // Same as above.
      // eslint-disable-next-line no-undef
      process.stdout.write('\u001b[H\u001b[2J\u001b[3J');
      returnData[1] = true;
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function help
     * @description Displays all the information about all of the commands in the system,
     * including both system defined commands and client defined commands.
     * @param {array<boolean|string|integer>} inputData An array that could possibly include the name of this command,
     * and a list of top-level command data structure data types to print help for.
     * That way we can parameterize and optimize the help to print commands specific to the Haystacks platform,
     * the host application, loaded plugins, or various combinations of these.
     * inputData[0] = 'help'
     * inputData[1] = Could be a coma-separated string list of command catagories to get help for.
     * inputData[n] = Could be additional list of command catagories to get help for if the user entered a space-separated list.
     * Options are: Framework,Platform,Application,App,Plugins,Plugin
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,string|integer|boolean|object|array>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by the command output.
     * @author Seth Hollingsead
     * @date 2022/02/22
     */
    // eslint-disable-next-line no-unused-vars
    async function help(inputData, inputMetaData) {
      let functionName = help.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      let returnData = [true, []];
      let commandNamespaceTypesInputArray = []; // Use this to process any inputs the user may have entered.
      let commandNamespaceTypesConfirmedArray = []; // Use this once we've confirmed valid user entry for inputs given.
      let validUserEntry = false;
      let errorMessage = '';
      let namespaceAllCommandsDataObject = [];
      let validCommandNamespaceTypes = [wrd.cFramework, wrd.cApplication, wrd.cPlugins];

      // Process user input(s).
      if (Array.isArray(inputData) && inputData.length === 2) {
        // The user entered only 1 parameter namespace, but that could actually be a coma-separated list.
        // inputData.length is:
        await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataLengthIs + inputData.length);
        if (inputData[1].includes(bas.cComa)) {
          commandNamespaceTypesInputArray = inputData[1].split(bas.cComa);
        } else if (inputData[1].includes(bas.cSemiColon)) {
          commandNamespaceTypesInputArray = inputData[1].split(bas.cSemiColon);
        } else if (inputData[1].includes(bas.cForwardSlash)) {
          commandNamespaceTypesInputArray = inputData[1].split(bas.cForwardSlash);
        } else if (inputData[1].includes(bas.cBackSlash)) {
          commandNamespaceTypesInputArray = inputData[1].split(bas.cBackSlash);
        } else {
          // shift the data1!
          await loggers.consoleLog(namespacePrefix + functionName, msg.cshiftData1);
          inputData.shift();
          commandNamespaceTypesInputArray = inputData;
        }
      } else if (Array.isArray(inputData) && inputData.length > 2) {
        // shift the data2!
        await loggers.consoleLog(namespacePrefix + functionName, msg.cshiftData2);
        inputData.shift();
        commandNamespaceTypesInputArray = inputData;
      }

      // commandNamespaceTypesInputArray is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandNamespaceTypesInputArrayIs + JSON.stringify(commandNamespaceTypesInputArray));

      // This is where we need to process the array of inputs to normalize them to the command types available by the system,
      // or run a generic search to find the command namespace alias data.
      // Available official types are: Framework,Platform,Application,App,Plugins,Plugin
      if (commandNamespaceTypesInputArray.length > 0) {
        for (let commandNamespaceEntityKey in commandNamespaceTypesInputArray) {
          let commandNamespaceEntity = commandNamespaceTypesInputArray[commandNamespaceEntityKey]
          if (commandNamespaceEntity.toUpperCase().trim() === wrd.cFRAMEWORK || commandNamespaceEntity.toUpperCase().trim() === wrd.cPLATFORM) {
            commandNamespaceTypesConfirmedArray.push(wrd.cFramework);
          } else if (commandNamespaceEntity.toUpperCase().trim() === wrd.cAPPLICATION || commandNamespaceEntity.toUpperCase().trim() === wrd.cAPP) {
            commandNamespaceTypesConfirmedArray.push(wrd.cApplication);
          } else if (commandNamespaceEntity.toUpperCase().trim() === wrd.cPLUGINS || commandNamespaceEntity.toUpperCase().trim() === wrd.cPLUGIN) {
            commandNamespaceTypesConfirmedArray.push(wrd.cPlugins);
          } else if (commandNamespaceEntity === '') {
            // Just ignore it!
          } else {
            // Not sure what the user may have entered here, but it could be some valid namespace, so just add it to the array, and assume best intent.
            // If its going to error out, then it will print an error some place else.
            commandNamespaceTypesConfirmedArray.push(commandNamespaceEntity);
          }
        } // End-for (let commandNamespaceEntity in commandNamespaceTypesInputArray)
        if (commandNamespaceTypesConfirmedArray.length > 0) {
          validUserEntry = true;
        }
      } else {
        // User didn't enter any parameters at all....just gather them all!
        commandNamespaceTypesConfirmedArray = validCommandNamespaceTypes;
        validUserEntry = true;
      }

      // commandNamespaceTypesConfirmedArray is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.ccommandNamespaceTypesConfirmedArrayIs + JSON.stringify(commandNamespaceTypesConfirmedArray));

      if (validUserEntry) {
        if (commandNamespaceTypesConfirmedArray.length > 0) {
          for (const commandNamespaceTypeConfirmedKey in commandNamespaceTypesConfirmedArray) {
            const commandNamespaceTypeConfirmedEntity = commandNamespaceTypesConfirmedArray[commandNamespaceTypeConfirmedKey];
            if (commandNamespaceTypeConfirmedEntity === wrd.cFramework) {
              // processing framework commands
              await loggers.consoleLog(namespacePrefix + functionName, msg.cprocessingFrameworkCommands);
              let frameworkCommandAliases = await commandBroker.getAllCommandAliasData(D[sys.cCommandsAliases][wrd.cFramework]);
              if (frameworkCommandAliases) {
                if (Object.keys(namespaceAllCommandsDataObject).length != 0) {
                  // frameworkCommandAliases is:
                  await loggers.consoleLog(namespacePrefix + functionName, msg.cframeworkCommandAliasesIs + JSON.stringify(frameworkCommandAliases));
                  namespaceAllCommandsDataObject = await ruleBroker.processRules([namespaceAllCommandsDataObject, frameworkCommandAliases], [biz.cobjectDeepMerge]);
                } else {
                  namespaceAllCommandsDataObject = frameworkCommandAliases;
                }
              } // End-if (frameworkCommandAliases)
            } else if (commandNamespaceTypeConfirmedEntity === wrd.cApplication) {
              // processing application commands
              await loggers.consoleLog(namespacePrefix + functionName, msg.cprocessingApplicationCommands);
              let applicationCommandAliases = await commandBroker.getAllCommandAliasData(D[sys.cCommandsAliases][wrd.cApplication]);
              if (applicationCommandAliases) {
                if (Object.keys(namespaceAllCommandsDataObject).length != 0) {
                  // applicationCommandAliases is:
                  await loggers.consoleLog(namespacePrefix + functionName, msg.capplicationCommandAliasesIs + JSON.stringify(applicationCommandAliases));
                  namespaceAllCommandsDataObject = await ruleBroker.processRules([namespaceAllCommandsDataObject, applicationCommandAliases], [biz.cobjectDeepMerge]);
                } else {
                  namespaceAllCommandsDataObject = applicationCommandAliases;
                }
              } // End-if (applicationCommandAliases)
            } else if (commandNamespaceTypeConfirmedEntity === wrd.cPlugins) {
              if (await configurator.getConfigurationSetting(wrd.csystem, cfg.cenablePluginLoader)) {
                // processing plugins commands
                await loggers.consoleLog(namespacePrefix + functionName, msg.cprocessingPluginsCommands);
                let pluginsCommandAliases = await commandBroker.getAllCommandAliasData(D[sys.cCommandsAliases][wrd.cPlugins]);
                if (pluginsCommandAliases) {
                  if (Object.keys(namespaceAllCommandsDataObject).length != 0) {
                    // pluginsCommandAliases is:
                    await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginsCommandAliasesIs + JSON.stringify(pluginsCommandAliases));
                    namespaceAllCommandsDataObject = await ruleBroker.processRules([namespaceAllCommandsDataObject, pluginsCommandAliases], [biz.cobjectDeepMerge]);
                  } else {
                    namespaceAllCommandsDataObject = pluginsCommandAliases;
                  }
                } // End-if (pluginsCommandAliases)
              } // End-if (await configurator.getConfigurationSetting(wrd.csystem, cfg.cenablePluginLoader))
            } else {
              // calling getCommandNamespaceDataObject() function,
              // because the user entered some namespace we should look for!
              // processing commands for:
              await loggers.consoleLog(namespacePrefix + functionName, msg.cprocessingCommandsFor + commandNamespaceTypeConfirmedEntity);
              let namespaceCommandsData = await commandBroker.getCommandNamespaceDataObject(undefined, commandNamespaceTypeConfirmedEntity);
              // namespaceCommandsData is:
              await loggers.consoleLog(namespacePrefix + functionName, msg.cnamespaceCommandsDataIs + JSON.stringify(namespaceCommandsData));
              if (namespaceCommandsData === false) {
                // ERROR: The command namespace was not found.
                // Please make sure you have entered the correct name and try again.
                errorMessage = msg.chelpCommandMessage01 + bas.cSpace + msg.chelpCommandMessage02;
                console.log(errorMessage);
              } else {
                // NOW call getAllCommandAliasData with the above found data!
                await loggers.consoleLog(namespacePrefix + functionName, msg.chelpCommandMessage03);
                let extraUserEnteredCommandAliases = await commandBroker.getAllCommandAliasData(namespaceCommandsData);
                if (extraUserEnteredCommandAliases) {
                  if (Object.keys(namespaceAllCommandsDataObject).length != 0) {
                    // extraUserEnteredCommandAliases is:
                    await loggers.consoleLog(namespacePrefix + functionName, msg.cextraUserEnteredCommandAliasesIs + JSON.stringify(extraUserEnteredCommandAliases));
                    namespaceAllCommandsDataObject = await ruleBroker.processRules([namespaceAllCommandsDataObject, extraUserEnteredCommandAliases], [biz.cobjectDeepMerge]);
                  } else {
                    namespaceAllCommandsDataObject = extraUserEnteredCommandAliases;
                  }
                } // End-if (extraUserEnteredCommandAliases)
              }
            }
            // namespaceAllCommandsDataObject is:
            await loggers.consoleLog(namespacePrefix + functionName, msg.cnamespaceAllCommandsDataObjectIs + JSON.stringify(namespaceAllCommandsDataObject));
          } // End-for (const commandNamespaceTypeConfirmedKey in commandNamespaceTypesConfirmedArray)
        } else {
          // Should never get here, but just in case we do, protect the system and get all the command alias data so the command can still finish successfully.
          namespaceAllCommandsDataObject = await commandBroker.getAllCommandAliasData(D[sys.cCommandsAliases]);
        }
        returnData[1] = await ruleBroker.processRules([namespaceAllCommandsDataObject, ''], [biz.carrayDeepClone]);
        // namespaceAllCommandsDataObject is:
        await loggers.consoleLog(namespacePrefix + functionName, msg.cnamespaceAllCommandsDataObjectIs + JSON.stringify(namespaceAllCommandsDataObject[0]));
        await loggers.consoleTableLog(baseFileName + bas.cDot + functionName, namespaceAllCommandsDataObject[0], [wrd.cName, wrd.cDescription]);
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function workflowHelp
     * @description Displays all the information about all the workflows in the system,
     * including both system defined workflows & client defined workflows.
     * @param {array<boolean|string|integer>} inputData An array that could possibly include the name of this command,
     * and a list of top-level workflow catagories data structures to be printed.
     * That way we can parameterize and optimize the workflow help to print workflows specific to the Haystacks platform,
     * the host application, loaded plugins, or various combinations of these.
     * inputData[0] = 'workflowHelp'
     * inputData[1] = Could be a coma-separated string list of command catagories to get workflow help for.
     * inputData[n] = Could be additional list of command catagories to get help for if the user entered a space-separated list.
     * Options are: Framework,Platform,Application,App,Plugins,Plugin
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,string|integer|boolean|object|array>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by the command output.
     * @author Seth Hollingsead
     * @date 2022/02/22
     */
    async function workflowHelp(inputData, inputMetaData) {
      let functionName = workflowHelp.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, []];
      let errorMessage = '';
      let workflowNamespaceTypesInputArray = []; // Use this to process any inputs the user may have entered.
      let workflowNamespaceTypesConfirmedArray = []; // Use this once we've confirmed vaid user entry for inputs given.
      let validUserEntry = false;
      let namespaceAllWorkflowsDataObject = [];
      let validWorkflowsNamespaceTypes = [wrd.cFramework, wrd.cApplication, wrd.cPlugins];

      // Process user input(s).
      if (Array.isArray(inputData) && inputData.length === 2) {
        // The user entered only 1 parameter namespace, but that could actually be a coma-separated list.
        // inputData.length is:
        await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataLengthIs + inputData.length);
        if (inputData[1].includes(bas.cComa)) {
          workflowNamespaceTypesInputArray = inputData[1].split(bas.cComa);
        } else if (inputData[1].includes(bas.cSemiColon)) {
          workflowNamespaceTypesInputArray = inputData[1].split(bas.cSemiColon);
        } else if (inputData[1].includes(bas.cForwardSlash)) {
          workflowNamespaceTypesInputArray = inputData[1].split(bas.cForwardSlash);
        } else if (inputData[1].includes(bas.cBackSlash)) {
          workflowNamespaceTypesInputArray = inputData[1].split(bas.cBackSlash);
        } else {
          // shift the data1!
          await loggers.consoleLog(namespacePrefix + functionName, msg.cshiftData1);
          inputData.shift();
          workflowNamespaceTypesInputArray = inputData;
        }
      } else if (Array.isArray(inputData) && inputData.length > 2) {
        // shift the data2!
        await loggers.consoleLog(namespacePrefix + functionName, msg.cshiftData2);
        inputData.shift();
        workflowNamespaceTypesInputArray = inputData;
      }

      // workflowNamespaceTypesInputArray is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cworkflowNamespaceTypesInputArrayIs + JSON.stringify(workflowNamespaceTypesInputArray));

      // This is where we need to process the array of inputs to normalize them to the workflow types available by the system,
      // or run a generic search to find the workflow data.
      // Available official types are: Framework,Platform,Application,App,Plugins,Plugin
      if (workflowNamespaceTypesInputArray.length > 0) {
        for (let workflowNamespaceEntityKey in workflowNamespaceTypesInputArray) {
          let workflowNamespaceEntity = workflowNamespaceTypesInputArray[workflowNamespaceEntityKey];
          if (workflowNamespaceEntity.toUpperCase().trim() === wrd.cFRAMEWORK || workflowNamespaceEntity.toUpperCase().trim() === wrd.cPLATFORM) {
            workflowNamespaceTypesConfirmedArray.push(wrd.cFramework);
          } else if (workflowNamespaceEntity.toUpperCase().trim() === wrd.cAPPLICATION || workflowNamespaceEntity.toUpperCase().trim() === wrd.cAPP) {
            workflowNamespaceTypesConfirmedArray.push(wrd.cApplication);
          } else if (workflowNamespaceEntity.toUpperCase().trim() === wrd.cPLUGINS || workflowNamespaceEntity.toUpperCase().trim() === wrd.cPLUGIN) {
            workflowNamespaceTypesConfirmedArray.push(wrd.cPlugins);
          } else if (workflowNamespaceEntity === '') {
            // Just ignore it!
          } else {
            // Not sure what the user may have entered here, but it could be some valid namespace, so just add it to the array, and assume best intent.
            // If its going to error out, then it will print an error some place else.
            workflowNamespaceTypesConfirmedArray.push(workflowNamespaceEntity);
          }
        } // End-for (let workflowNamespaceEntityKey in workflowNamespaceTypesInputArray)
        if (workflowNamespaceTypesConfirmedArray.length > 0) {
          validUserEntry = true;
        }
      } else {
        // User didn't enter any parameters at al....just gather them all!
        workflowNamespaceTypesConfirmedArray = validWorkflowsNamespaceTypes;
        validUserEntry = true;
      }

      // workflowNamespaceTypesConfirmedArray is:
      await loggers.consoleLog(namespacePrefix + functionName, msg.cworkflowNamespaceTypesConfirmedArrayIs + JSON.stringify(workflowNamespaceTypesConfirmedArray));

      if (validUserEntry) {
        if (workflowNamespaceTypesConfirmedArray.length > 0) {
          for (const workflowNamespaceTypeConfirmedKey in workflowNamespaceTypesConfirmedArray) {
            const workflowNamespaceTypeConfirmedEntity = workflowNamespaceTypesConfirmedArray[workflowNamespaceTypeConfirmedKey];
            if (workflowNamespaceTypeConfirmedEntity === wrd.cFramework) {
              // processing framework workflows
              await loggers.consoleLog(namespacePrefix + functionName, msg.cprocessingFrameworkWorkflows);
              let frameworkWorkflows = await workflowBroker.getAllWorkflows(D[sys.cCommandWorkflows][wrd.cFramework]);
              if (frameworkWorkflows) {
                if (namespaceAllWorkflowsDataObject.length != 0) {
                  // frameworkWorkflows is:
                  await loggers.consoleLog(namespacePrefix + functionName, msg.cframeworkWorkflowsIs + JSON.stringify(frameworkWorkflows));
                  namespaceAllWorkflowsDataObject.push(...frameworkWorkflows);
                } else {
                  namespaceAllWorkflowsDataObject = frameworkWorkflows;
                }
              } // End-if (frameworkWorkflows)
            } else if (workflowNamespaceTypeConfirmedEntity === wrd.cApplication) {
              // processing application workflows
              await loggers.consoleLog(namespacePrefix + functionName, msg.cprocessingApplicationWorkflows);
              let applicationWorkflows = await workflowBroker.getAllWorkflows(D[sys.cCommandWorkflows][wrd.cApplication]);
              if (applicationWorkflows) {
                if (namespaceAllWorkflowsDataObject.length != 0) {
                  // applicationWorkflows is:
                  await loggers.consoleLog(namespacePrefix + functionName, msg.capplicationWorkflowsIs + JSON.stringify(applicationWorkflows));
                  namespaceAllWorkflowsDataObject.push(...applicationWorkflows);
                } else {
                  namespaceAllWorkflowsDataObject = applicationWorkflows;
                }
              } // End-if (applicationWorkflows)
            } else if (workflowNamespaceTypeConfirmedEntity === wrd.cPlugins) {
              if (await configurator.getConfigurationSetting(wrd.csystem, cfg.cenablePluginLoader)) {
                // processing plugins workflows
                await loggers.consoleLog(namespacePrefix + functionName, msg.cprocessingPluginsWorkflows);
                let pluginWorkflows = await workflowBroker.getAllWorkflows(D[sys.cCommandWorkflows][wrd.cPlugins]);
                if (pluginWorkflows) {
                  if (namespaceAllWorkflowsDataObject.length != 0) {
                    // pluginWorkflows is:
                    await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginWorkflowsIs + JSON.stringify(pluginWorkflows));
                    namespaceAllWorkflowsDataObject.push(...pluginWorkflows);
                  } else {
                    namespaceAllWorkflowsDataObject = pluginWorkflows;
                  }
                } // End-if (pluginWorkflows)
              } // End-if (await configurator.getConfigurationSetting(wrd.csystem, cfg.cenablePluginLoader))
            } else {
              // calling getWorkflowNamespaceDataObject() function,
              // because the user entered some namespace we should look for!
              // processing workflows for:
              await loggers.consoleLog(namespacePrefix + functionName, msg.cprocessingWorkflowsFor + workflowNamespaceTypeConfirmedEntity);
              let namespaceWorkflowData = await workflowBroker.getWorkflowNamespaceDataObject(undefined, workflowNamespaceTypeConfirmedEntity);
              // namespaceWorkflowData is:
              await loggers.consoleLog(namespacePrefix + functionName, msg.cnamespaceWorkflowDataIs + JSON.stringify(namespaceWorkflowData));
              if (namespaceWorkflowData === false) {
                // ERROR: The workflow namespace was not found.
                // Please make sure you have entered the correct name and try again.
                errorMessage = msg.cworkflowHelpCommandMessage01 + bas.cSpace + msg.chelpCommandMessage02;
                console.log(errorMessage);
              } else {
                // NOW call getAllWorkflows with the above found data!
                await loggers.consoleLog(namespacePrefix + functionName, msg.cworkfowHelpMessage03);
                let extraUserEnteredWorkflows = await workflowBroker.getAllWorkflows(namespaceWorkflowData);
                if (extraUserEnteredWorkflows) {
                  if (namespaceAllWorkflowsDataObject.length != 0) {
                    // extraUserEnteredWorkflows is:
                    await loggers.consoleLog(namespacePrefix + functionName, msg.cextraUserEnteredWorkflowsIs + JSON.stringify(extraUserEnteredWorkflows));
                    // namespaceAllWorkflowsDataObject = await ruleBroker.processRules([namespaceAllWorkflowsDataObject, extraUserEnteredWorkflows], [biz.cobjectDeepMerge]);
                    namespaceAllWorkflowsDataObject.push(...extraUserEnteredWorkflows);
                  } else {
                    namespaceAllWorkflowsDataObject = extraUserEnteredWorkflows;
                  }
                } // End-if (extraUserEnteredWorkflows)
              }
            }
            // namespaceAllWorkflowsDataObject is:
            await loggers.consoleLog(namespacePrefix + functionName, msg.cnamespaceAllWorkflowsDataObjectIs + JSON.stringify(namespaceAllWorkflowsDataObject));
          } // End-for (const workflowNamespaceTypeConfirmedKey in workflowNamespaceTypesConfirmedArray)
        } else {
          // Should never get here, but just in case we do, protect the system and get all the workflows data so the command can still finish successfully.
          // just call getAllWorkflows functions with no input,
          // will return all and print all.
          namespaceAllWorkflowsDataObject = await workflowBroker.getAllWorkflows();
        }
        returnData[1] = await ruleBroker.processRules([namespaceAllWorkflowsDataObject, ''], [biz.carrayDeepClone]);
        // namespaceAllWorkflowsDataObject is:
        await loggers.consoleLog(namespacePrefix + functionName, msg.cnamespaceAllWorkflowsDataObjectIs + JSON.stringify(namespaceAllWorkflowsDataObject));
        await loggers.consoleTableLog(baseFileName + bas.cDot + functionName, namespaceAllWorkflowsDataObject);
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function printCommands
     * @description Prints out a RAW dump of the commands data structure, useful for debugging the commands in the live environment.
     * @param {string} inputData Not used for this command.
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,array<string>>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by an empty.
     * @author Seth Hollingsead
     * @date 2023/11/10
     */
    async function printCommands(inputData, inputMetaData) {
      let functionName = printCommands.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, []];
      console.log(msg.ccommandsAre, D[wrd.cCommands]);
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function printBusinessRules
     * @description Prints out a RAW dump of the business rules data structure, useful for debugging the business rules in the live environment.
     * @param {string} inputData Not used for this command.
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,array<string>>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by an empty.
     * @author Seth Hollingsead
     * @date 2023/11/10
     */
    async function printBusinessRules(inputData, inputMetaData) {
      let functionName = printBusinessRules.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, []];
      console.log(msg.cbusinessRulesAre, D[sys.cbusinessRules]);
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function printUserCommandsLog
     * @description Prints out the command log of all the commands the user has entered since the start of the application for this instance it was running.
     * @param {string} inputData Not used for this command.
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,array<string>>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by an array of strings that is the user entered commands.
     * @author Seth Hollingsead
     * @date 2023/02/14
     */
    async function printUserCommandsLog(inputData, inputMetaData) {
      let functionName = printUserCommandsLog.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, []];
      if (await configurator.getConfigurationSetting(wrd.csystem, cfg.clogUserEnteredCommands) === true) {
        if (await stack.isEmpty(sys.cUserEnteredCommandLog) === false) {
          let userEnteredCommandLogArray = await stack.getStackContents(sys.cUserEnteredCommandLog);
          let userEnteredCommandLog = userEnteredCommandLogArray.join(bas.cComa + bas.cNewLine + bas.cCarriageReturn);
          returnData[1] = userEnteredCommandLog;
          console.log(userEnteredCommandLog);
        } else {
          // User commands log is empty.
          console.log(msg.cUserCommandsLogIsEmpty);
          returnData[1] = '';
        }
      } else {
        // NOTE: The user entered command log setting is not enabled.
        // Change the setting logUserEnteredCommands to enable user entered command log data to be captured for printing.
        console.log(msg.cprintUserCommandLogMessage01 + bas.cSpace + msg.cprintUserCommandLogMessage02);
        returnData[1] = false;
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function printAllCommandsLog
     * @description Prints out a log of all the commands executed by the system since the start of the application for this instance it was running.
     * @param {string} inputData Not used for this command.
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,array<string>>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by an array of strings that is the user entered commands.
     * @author Seth Hollingsead
     * @date 2023/02/14
     */
    async function printAllCommandsLog(inputData, inputMetaData) {
      let functionName = printAllCommandsLog.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, []];
      if (await configurator.getConfigurationSetting(wrd.csystem, cfg.clogAllCommands) === true) {
        if (await stack.isEmpty(sys.cSystemCommandLog) === false) {
          let allCommandsLogArray = await stack.getStackContents(sys.cSystemCommandLog);
          let allCommandsLog = allCommandsLogArray.join(bas.cComa + bas.cNewLine + bas.cCarriageReturn);
          returnData[1] = allCommandsLog;
          console.log(allCommandsLog);
        } else {
          // All commands log is empty.
          console.log(msg.cAllCommandsLogIsEmpty);
          returnData[1] = '';
        }
      } else {
        // NOTE: The command log setting is not enabled.
        // Change the setting logAllCommands to enable command log data to be captured for printing.
        console.log(msg.cprintAllCommandLogMessage01 + bas.cSpace + msg.cprintAllCommandLogMessage02);
        returnData[1] = false;
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function clearUserCommandsLog
     * @description Wipes out the user command log, destroying all evidence of whatever commands the user has entered.
     * @param {string} inputData Not used for this command.
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,boolean>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by another boolean value to indicate if the operation was successful or not.
     * @author Seth Hollingsead
     * @date 2023/02/14
     */
    async function clearUserCommandsLog(inputData, inputMetaData) {
      let functionName = clearUserCommandsLog.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, []];
      if (await configurator.getConfigurationSetting(wrd.csystem, cfg.clogUserEnteredCommands) === true) {
        if (await configurator.getConfigurationSetting(wrd.csystem, cfg.cenableUserCommandsLogClearing) === true) {
          returnData[1] = await stack.clearStack(sys.cUserEnteredCommandLog);
        } else {
          // NOTE: User commands log clearing setting is not enabled.
          console.log(msg.cclearUserCommandsLogMessage01);
          returnData[1] = false;
        }
      } else {
        // NOTE: The user entered command log setting is not enabled.
        console.log(msg.cprintUserCommandLogMessage01);
        returnData[1] = false;
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    /**
     * @function clearAllCommandsLog
     * @description Wipes out the all commands log, destroying all evidence of whatever commands were executed by the system.
     * @param {string} inputData Not used for this command.
     * @param {string} inputMetaData Not used for this command.
     * @return {array<boolean,boolean>} An array with a boolean True or False value to
     * indicate if the application should exit or not exit, followed by another boolean value to indicate if the operation was successful or not.
     * @author Seth Hollingsead
     * @date 2023/02/14
     */
    async function clearAllCommandsLog(inputData, inputMetaData) {
      let functionName = clearAllCommandsLog.name;
      await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + JSON.stringify(inputData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
      let returnData = [true, []];
      if (await configurator.getConfigurationSetting(wrd.csystem, cfg.clogAllCommands) === true) {
        if (await configurator.getConfigurationSetting(wrd.csystem, cfg.cenableAllCommandsLogClearing) === true) {
          returnData[1] = await stack.clearStack(sys.cSystemCommandLog);
        } else {
          // NOTE: All commands log clearing setting is not enabled.
          console.log(msg.cclearAllCommandsLogMessage01);
          returnData[1] = false;
        }
      } else {
        // NOTE: The command log setting is not enabled.
        console.log(msg.cprintAllCommandLogMessage01);
        returnData[1] = false;
      }
      await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
      await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
      return returnData;
    }

    export default {
      echoCommand,
      exit,
      version,
      about,
      name,
      clearScreen,
      help,
      workflowHelp,
      printCommands,
      printBusinessRules,
      printUserCommandsLog,
      printAllCommandsLog,
      clearUserCommandsLog,
      clearAllCommandsLog
    };