text: /* === This is a file from Node-Red being used as-is. === */
  /**
   * Copyright JS Foundation and other contributors, http://js.foundation
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   **/
  
  const clone = require("clone");
  const Flow = require("./Flow").Flow;
  const context = require("../nodes/context");
  const util = require("util");
  const redUtil = require("@node-red/util").util;
  const events = require("@node-red/util").events;
  const flowUtil = require("./util");
  const credentials = require("../nodes/credentials");
  
  /**
   * Create deep copy of object
   */
  function deepCopy(obj) {
    return JSON.parse(JSON.stringify(obj));
  }
  
  /**
   * Evaluate Input Value
   */
  function evaluateInputValue(value, type, node) {
    if (type === "bool") {
      return value === "true" || value === true;
    }
    if (type === "cred") {
      return value;
    }
    return redUtil.evaluateNodeProperty(value, type, node, null, null);
  }
  
  /**
   * This class represents a subflow - which is handled as a special type of Flow
   */
  class Subflow extends Flow {
    /**
     * Create a Subflow object.
     * This takes a subflow definition and instance node, creates a clone of the
     * definition with unique ids applied and passes to the super class.
     * @param {[type]} parent          [description]
     * @param {[type]} globalFlow      [description]
     * @param {[type]} subflowDef      [description]
     * @param {[type]} subflowInstance [description]
     */
    constructor(parent, globalFlow, subflowDef, subflowInstance) {
      // console.log("CREATE SUBFLOW",subflowDef.id,subflowInstance.id,"alias?",subflowInstance._alias);
      // console.log("SubflowInstance\n"+JSON.stringify(subflowInstance," ",2));
      // console.log("SubflowDef\n"+JSON.stringify(subflowDef," ",2));
      var subflows = parent.flow.subflows;
      var globalSubflows = parent.global.subflows;
  
      var node_map = {};
      var node;
      var wires;
      var i;
  
      var subflowInternalFlowConfig = {
        id: subflowInstance.id,
        configs: {},
        nodes: {},
        subflows: {},
      };
  
      if (subflowDef.configs) {
        // Clone all of the subflow config node definitions and give them new IDs
        for (i in subflowDef.configs) {
          if (subflowDef.configs.hasOwnProperty(i)) {
            node = createNodeInSubflow(subflowInstance.id, subflowDef.configs[i]);
            node_map[node._alias] = node;
            subflowInternalFlowConfig.configs[node.id] = node;
          }
        }
      }
      if (subflowDef.nodes) {
        // Clone all of the subflow node definitions and give them new IDs
        for (i in subflowDef.nodes) {
          if (subflowDef.nodes.hasOwnProperty(i)) {
            node = createNodeInSubflow(subflowInstance.id, subflowDef.nodes[i]);
            node_map[node._alias] = node;
            subflowInternalFlowConfig.nodes[node.id] = node;
          }
        }
      }
  
      subflowInternalFlowConfig.subflows = clone(subflowDef.subflows || {});
  
      remapSubflowNodes(subflowInternalFlowConfig.configs, node_map);
      remapSubflowNodes(subflowInternalFlowConfig.nodes, node_map);
  
      // console.log("Instance config\n",JSON.stringify(subflowInternalFlowConfig,"",2));
  
      super(parent, globalFlow, subflowInternalFlowConfig);
  
      this.TYPE = "subflow";
      this.subflowDef = subflowDef;
      this.subflowInstance = subflowInstance;
      this.node_map = node_map;
      this.path = parent.path + "/" + (subflowInstance._alias || subflowInstance.id);
  
      let id = subflowInstance.id;
      if (subflowInstance._alias) {
        id = subflowInstance._alias;
      }
      this.templateCredentials = credentials.get(subflowDef.id) || {};
      this.instanceCredentials = credentials.get(id) || {};
  
      var env = [];
      if (this.subflowDef.env) {
        this.subflowDef.env.forEach((e) => {
          env[e.name] = e;
          if (e.type === "cred") {
            e.value = this.templateCredentials[e.name];
          }
        });
      }
      if (this.subflowInstance.env) {
        this.subflowInstance.env.forEach((e) => {
          var old = env[e.name];
          var ui = old ? old.ui : null;
          env[e.name] = e;
          if (ui) {
            env[e.name].ui = ui;
          }
          if (e.type === "cred") {
            if (!old || this.instanceCredentials.hasOwnProperty(e.name)) {
              e.value = this.instanceCredentials[e.name];
            } else if (old) {
              e.value = this.templateCredentials[e.name];
            }
          }
        });
      }
      this.env = env;
    }
  
    /**
     * Start the subflow.
     * This creates a subflow instance node to handle the inbound messages. It also
     * rewires an subflow internal node that is connected to an output so it is connected
     * to the parent flow nodes the subflow instance is wired to.
     * @param  {[type]} diff [description]
     * @return {[type]}      [description]
     */
    start(diff) {
      var self = this;
      // Create a subflow node to accept inbound messages and route appropriately
      var Node = require("../nodes/Node");
  
      if (this.subflowDef.status) {
        var subflowStatusConfig = {
          id: this.subflowInstance.id + ":status",
          type: "subflow-status",
          z: this.subflowInstance.id,
          _flow: this.parent,
        };
        this.statusNode = new Node(subflowStatusConfig);
        this.statusNode.on("input", function (msg) {
          if (msg.payload !== undefined) {
            if (typeof msg.payload === "string") {
              // if msg.payload is a String, use it as status text
              self.node.status({ text: msg.payload });
              return;
            } else if (Object.prototype.toString.call(msg.payload) === "[object Object]") {
              if (
                msg.payload.hasOwnProperty("text") ||
                msg.payload.hasOwnProperty("fill") ||
                msg.payload.hasOwnProperty("shape") ||
                Object.keys(msg.payload).length === 0
              ) {
                // msg.payload is an object that looks like a status object
                self.node.status(msg.payload);
                return;
              }
            }
            // Anything else - inspect it and use as status text
            var text = util.inspect(msg.payload);
            if (text.length > 32) {
              text = text.substr(0, 32) + "...";
            }
            self.node.status({ text: text });
          } else if (msg.status !== undefined) {
            // if msg.status exists
            if (msg.status.hasOwnProperty("text") && msg.status.text.indexOf("common.") === 0) {
              msg.status.text = "node-red:" + msg.status.text;
            }
            self.node.status(msg.status);
          }
        });
      }
  
      var subflowInstanceConfig = {
        id: this.subflowInstance.id,
        type: this.subflowInstance.type,
        z: this.subflowInstance.z,
        name: this.subflowInstance.name,
        wires: [],
        _flow: this,
      };
      if (this.subflowDef.in) {
        subflowInstanceConfig.wires = this.subflowDef.in.map(function (n) {
          return n.wires.map(function (w) {
            return self.node_map[w.id].id;
          });
        });
        subflowInstanceConfig._originalWires = clone(subflowInstanceConfig.wires);
      }
  
      this.node = new Node(subflowInstanceConfig);
      this.node.on("input", function (msg) {
        this.send(msg);
      });
      // Called when the subflow instance node is being stopped
      this.node.on("close", function (done) {
        this.status({});
        // Stop the complete subflow
        self.stop().finally(done);
      });
      this.node.status = (status) => this.parent.handleStatus(this.node, status);
      // Create a context instance
      // console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z)
      this._context = context.get(this._alias || this.id, this.z);
  
      this.node._updateWires = this.node.updateWires;
  
      this.node.updateWires = function (newWires) {
        // Wire the subflow outputs
        if (self.subflowDef.out) {
          var node, wires, i, j;
          // Restore the original wiring to the internal nodes
          subflowInstanceConfig.wires = clone(subflowInstanceConfig._originalWires);
          for (i = 0; i < self.subflowDef.out.length; i++) {
            wires = self.subflowDef.out[i].wires;
            for (j = 0; j < wires.length; j++) {
              if (wires[j].id != self.subflowDef.id) {
                node = self.node_map[wires[j].id];
                if (node._originalWires) {
                  node.wires = clone(node._originalWires);
                }
              }
            }
          }
  
          var modifiedNodes = {};
          var subflowInstanceModified = false;
          for (i = 0; i < self.subflowDef.out.length; i++) {
            wires = self.subflowDef.out[i].wires;
            for (j = 0; j < wires.length; j++) {
              if (wires[j].id === self.subflowDef.id) {
                subflowInstanceConfig.wires[wires[j].port] = subflowInstanceConfig.wires[
                  wires[j].port
                ].concat(newWires[i]);
                subflowInstanceModified = true;
              } else {
                node = self.node_map[wires[j].id];
                node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]);
                modifiedNodes[node.id] = node;
              }
            }
          }
          Object.keys(modifiedNodes).forEach(function (id) {
            var node = modifiedNodes[id];
            self.activeNodes[id].updateWires(node.wires);
          });
  
          if (subflowInstanceModified) {
            self.node._updateWires(subflowInstanceConfig.wires);
          }
        }
      };
  
      // Wire the subflow outputs
      if (this.subflowDef.out) {
        for (var i = 0; i < this.subflowDef.out.length; i++) {
          // i: the output index
          // This is what this Output is wired to
          var wires = this.subflowDef.out[i].wires;
          for (var j = 0; j < wires.length; j++) {
            if (wires[j].id === this.subflowDef.id) {
              // A subflow input wired straight to a subflow output
              subflowInstanceConfig.wires[wires[j].port] = subflowInstanceConfig.wires[
                wires[j].port
              ].concat(this.subflowInstance.wires[i]);
              this.node._updateWires(subflowInstanceConfig.wires);
            } else {
              var node = self.node_map[wires[j].id];
              if (!node._originalWires) {
                node._originalWires = clone(node.wires);
              }
              node.wires[wires[j].port] = (node.wires[wires[j].port] || []).concat(
                this.subflowInstance.wires[i],
              );
            }
          }
        }
      }
  
      if (this.subflowDef.status) {
        var subflowStatusId = this.statusNode.id;
        wires = this.subflowDef.status.wires;
        for (var j = 0; j < wires.length; j++) {
          if (wires[j].id === this.subflowDef.id) {
            // A subflow input wired straight to a subflow output
            subflowInstanceConfig.wires[wires[j].port].push(subflowStatusId);
            this.node._updateWires(subflowInstanceConfig.wires);
          } else {
            var node = self.node_map[wires[j].id];
            if (!node._originalWires) {
              node._originalWires = clone(node.wires);
            }
            node.wires[wires[j].port] = node.wires[wires[j].port] || [];
            node.wires[wires[j].port].push(subflowStatusId);
          }
        }
      }
      super.start(diff);
    }
  
    /**
     * Stop this subflow.
     * The `stopList` argument helps define what needs to be stopped in the case
     * of a modified-nodes/flows type deploy.
     * @param  {[type]} stopList    [description]
     * @param  {[type]} removedList [description]
     * @return {[type]}             [description]
     */
    stop(stopList, removedList) {
      const nodes = Object.keys(this.activeNodes);
      return super.stop(stopList, removedList).then((res) => {
        nodes.forEach((id) => {
          events.emit("node-status", {
            id: id,
          });
        });
        return res;
      });
    }
    /**
     * Get environment variable of subflow
     * @param {String}   name   name of env var
     * @return {Object}  val    value of env var
     */
    getSetting(name) {
      if (!/^\$parent\./.test(name)) {
        var env = this.env;
        if (env && env.hasOwnProperty(name)) {
          var val = env[name];
          // If this is an env type property we need to be careful not
          // to get into lookup loops.
          // 1. if the value to lookup is the same as this one, go straight to parent
          // 2. otherwise, check if it is a compound env var ("foo $(bar)")
          //    and if so, substitute any instances of `name` with $parent.name
          // See https://github.com/node-red/node-red/issues/2099
          if (val.type !== "env" || val.value !== name) {
            let value = val.value;
            var type = val.type;
            if (type === "env") {
              value = value.replace(new RegExp("\\${" + name + "}", "g"), "${$parent." + name + "}");
            }
            try {
              return evaluateInputValue(value, type, this.node);
            } catch (e) {
              this.error(e);
              return undefined;
            }
          } else {
            // This _is_ an env property pointing at itself - go to parent
          }
        }
      } else {
        // name starts $parent. ... so delegate to parent automatically
        name = name.substring(8);
      }
      var parent = this.parent;
      if (parent) {
        var val = parent.getSetting(name);
        return val;
      }
      return undefined;
    }
  
    /**
     * Get a node instance from this subflow.
     * If the subflow has a status node, check for that, otherwise use
     * the super-class function
     * @param  {String} id [description]
     * @param  {Boolean} cancelBubble    if true, prevents the flow from passing the request to the parent
     *                                   This stops infinite loops when the parent asked this Flow for the
     *                                   node to begin with.
     * @return {[type]}    [description]
     */
    getNode(id, cancelBubble) {
      if (this.statusNode && this.statusNode.id === id) {
        return this.statusNode;
      }
      return super.getNode(id, cancelBubble);
    }
  
    /**
     * Handle a status event from a node within this flow.
     * @param  {Node}    node          The original node that triggered the event
     * @param  {Object}  statusMessage The status object
     * @param  {Node}    reportingNode The node emitting the status event.
     *                                 This could be a subflow instance node when the status
     *                                 is being delegated up.
     * @param  {boolean} muteStatus    Whether to emit the status event
     * @return {[type]}               [description]
     */
    handleStatus(node, statusMessage, reportingNode, muteStatus) {
      let handled = super.handleStatus(node, statusMessage, reportingNode, muteStatus);
      if (!handled) {
        if (!this.statusNode || node === this.node) {
          // No status node on this subflow caught the status message.
          // AND there is no Subflow Status node - so the user isn't
          // wanting to manage status messages themselves
          // Pass up to the parent with this subflow's instance as the
          // reporting node
          handled = this.parent.handleStatus(node, statusMessage, this.node, true);
        }
      }
      return handled;
    }
  
    /**
     * Handle an error event from a node within this flow. If there are no Catch
     * nodes within this flow, pass the event to the parent flow.
     * @param  {[type]} node          [description]
     * @param  {[type]} logMessage    [description]
     * @param  {[type]} msg           [description]
     * @param  {[type]} reportingNode [description]
     * @return {[type]}               [description]
     */
    handleError(node, logMessage, msg, reportingNode) {
      let handled = super.handleError(node, logMessage, msg, reportingNode);
      if (!handled) {
        // No catch node on this subflow caught the error message.
        // Pass up to the parent with the subflow's instance as the
        // reporting node.
        handled = this.parent.handleError(node, logMessage, msg, this.node);
      }
      return handled;
    }
  }
  
  /**
   * Clone a node definition for use within a subflow instance.
   * Give the node a new id and set its _alias property to record
   * its association with the original node definition.
   * @param  {[type]} subflowInstanceId [description]
   * @param  {[type]} def               [description]
   * @return {[type]}                   [description]
   */
  function createNodeInSubflow(subflowInstanceId, def) {
    let node = clone(def);
    let nid = redUtil.generateId();
    // console.log("Create Node In subflow",node._alias, "--->",nid, "(",node.type,")")
    // node_map[node.id] = node;
    node._alias = node.id;
    node.id = nid;
    node.z = subflowInstanceId;
    return node;
  }
  
  /**
   * Given an object of {id:nodes} and a map of {old-id:node}, modifiy all
   * properties in the nodes object to reference the new node ids.
   * This handles:
   *  - node.wires,
   *  - node.scope of Complete, Catch and Status nodes,
   *  - node.XYZ for any property where XYZ is recognised as an old property
   * @param  {[type]} nodes   [description]
   * @param  {[type]} nodeMap [description]
   * @return {[type]}         [description]
   */
  function remapSubflowNodes(nodes, nodeMap) {
    for (var id in nodes) {
      if (nodes.hasOwnProperty(id)) {
        var node = nodes[id];
        if (node.wires) {
          var outputs = node.wires;
          for (var j = 0; j < outputs.length; j++) {
            var wires = outputs[j];
            for (var k = 0; k < wires.length; k++) {
              if (nodeMap[outputs[j][k]]) {
                outputs[j][k] = nodeMap[outputs[j][k]].id;
              } else {
                outputs[j][k] = null;
              }
            }
          }
        }
        if (
          (node.type === "complete" || node.type === "catch" || node.type === "status") &&
          node.scope
        ) {
          node.scope = node.scope.map(function (id) {
            return nodeMap[id] ? nodeMap[id].id : "";
          });
        } else {
          for (var prop in node) {
            if (node.hasOwnProperty(prop) && prop !== "_alias") {
              if (nodeMap[node[prop]]) {
                node[prop] = nodeMap[node[prop]].id;
              }
            }
          }
        }
      }
    }
  }
  
  class SubflowModule extends Subflow {
    /**
     * Create a Subflow Module object.
     * This is a node that has been published as a subflow.
     * @param {[type]} parent          [description]
     * @param {[type]} globalFlow      [description]
     * @param {[type]} subflowDef      [description]
     * @param {[type]} subflowInstance [description]
     */
    constructor(type, parent, globalFlow, subflowDef, subflowInstance) {
      super(parent, globalFlow, subflowDef, subflowInstance);
      this.TYPE = `module:${type}`;
      this.subflowType = type;
    }
  
    /**
     * [log description]
     * @param  {[type]} msg [description]
     * @return {[type]}     [description]
     */
    log(msg) {
      if (msg.id) {
        msg.id = this.id;
      }
      if (msg.type) {
        msg.type = this.subflowType;
      }
      super.log(msg);
    }
  }
  
  function createSubflow(parent, globalFlow, subflowDef, subflowInstance) {
    return new Subflow(parent, globalFlow, subflowDef, subflowInstance);
  }
  
  function createModuleInstance(type, parent, globalFlow, subflowDef, subflowInstance) {
    return new SubflowModule(type, parent, globalFlow, subflowDef, subflowInstance);
  }
  
  module.exports = {
    init: function (runtime) {},
    create: createSubflow,
    createModuleInstance: createModuleInstance,
  };
  