"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var VirtualNode_exports = {};
__export(VirtualNode_exports, {
  VirtualNode: () => VirtualNode
});
module.exports = __toCommonJS(VirtualNode_exports);
var import_cc = require("@zwave-js/cc");
var import_core = require("@zwave-js/core");
var import_arrays = require("alcalzone-shared/arrays");
var import_VirtualEndpoint = require("./VirtualEndpoint.js");
function groupNodesBySecurityClass(nodes) {
  const ret = /* @__PURE__ */ new Map();
  for (const node of nodes) {
    const secClass = node.getHighestSecurityClass();
    if (secClass === import_core.SecurityClass.Temporary || secClass == void 0) {
      continue;
    }
    if (!ret.has(secClass)) {
      ret.set(secClass, []);
    }
    ret.get(secClass).push(node);
  }
  return ret;
}
__name(groupNodesBySecurityClass, "groupNodesBySecurityClass");
class VirtualNode extends import_VirtualEndpoint.VirtualEndpoint {
  static {
    __name(this, "VirtualNode");
  }
  id;
  constructor(id, driver, physicalNodes) {
    super(void 0, driver, 0);
    this.id = id;
    super.setNode(this);
    this.physicalNodes = [...physicalNodes].filter((n) => (
      // And avoid including the controller node in the support checks
      n.id !== driver.controller.ownNodeId && n.getHighestSecurityClass() !== import_core.SecurityClass.S0_Legacy
    ));
    this.nodesBySecurityClass = groupNodesBySecurityClass(this.physicalNodes);
    if (this.hasMixedSecurityClasses)
      this.id = void 0;
  }
  physicalNodes;
  nodesBySecurityClass;
  get hasMixedSecurityClasses() {
    return this.nodesBySecurityClass.size > 1;
  }
  /**
   * Updates a value for a given property of a given CommandClass.
   * This will communicate with the physical node(s) this virtual node represents!
   */
  async setValue(valueId, value, options) {
    valueId = (0, import_core.normalizeValueID)(valueId);
    try {
      const endpointInstance = this.getEndpoint(valueId.endpoint || 0);
      if (!endpointInstance) {
        return {
          status: import_cc.SetValueStatus.EndpointNotFound,
          message: `Endpoint ${valueId.endpoint} does not exist on virtual node ${this.id ?? "??"}`
        };
      }
      let api = endpointInstance.commandClasses[valueId.commandClass];
      if (!api.setValue) {
        return {
          status: import_cc.SetValueStatus.NotImplemented,
          message: `The ${(0, import_core.getCCName)(valueId.commandClass)} CC does not support setting values`
        };
      }
      const valueIdProps = {
        property: valueId.property,
        propertyKey: valueId.propertyKey
      };
      const hooks = api.setValueHooks?.(valueIdProps, value, options);
      if (hooks?.supervisionDelayedUpdates) {
        api = api.withOptions({
          requestStatusUpdates: true,
          onUpdate: /* @__PURE__ */ __name(async (update) => {
            try {
              if (update.status === import_core.SupervisionStatus.Success) {
                await hooks.supervisionOnSuccess();
              } else if (update.status === import_core.SupervisionStatus.Fail) {
                await hooks.supervisionOnFailure();
              }
            } catch {
            }
          }, "onUpdate")
        });
      }
      if (typeof options?.onProgress === "function") {
        api = api.withOptions({
          onProgress: options.onProgress
        });
      }
      const result = await api.setValue.call(api, valueIdProps, value, options);
      if (api.isSetValueOptimistic(valueId)) {
        const affectedNodes = this.physicalNodes.filter((node) => node.getEndpoint(endpointInstance.index)?.supportsCC(valueId.commandClass));
        for (const node of affectedNodes) {
          node.valueDB.setValue(valueId, value);
        }
      }
      if (hooks) {
        const supervisedAndSuccessful = (0, import_core.isSupervisionResult)(result) && result.status === import_core.SupervisionStatus.Success;
        const shouldUpdateOptimistically = api.isSetValueOptimistic(valueId) && (supervisedAndSuccessful || !this.driver.options.disableOptimisticValueUpdate && result == void 0);
        if (shouldUpdateOptimistically) {
          hooks.optimisticallyUpdateRelatedValues?.(supervisedAndSuccessful);
        }
        if (!(0, import_core.supervisedCommandSucceeded)(result) || hooks.forceVerifyChanges?.()) {
          await hooks.verifyChanges?.(result);
        }
      }
      return (0, import_cc.supervisionResultToSetValueResult)(result);
    } catch (e) {
      if ((0, import_core.isZWaveError)(e)) {
        let result;
        switch (e.code) {
          // This CC or API is not implemented
          case import_core.ZWaveErrorCodes.CC_NotImplemented:
          case import_core.ZWaveErrorCodes.CC_NoAPI:
            result = {
              status: import_cc.SetValueStatus.NotImplemented,
              message: e.message
            };
            break;
          // A user tried to set an invalid value
          case import_core.ZWaveErrorCodes.Argument_Invalid:
            result = {
              status: import_cc.SetValueStatus.InvalidValue,
              message: e.message
            };
            break;
        }
        if (result)
          return result;
      }
      throw e;
    }
  }
  /**
   * Returns a list of all value IDs and their metadata that can be used to
   * control the physical node(s) this virtual node represents.
   */
  getDefinedValueIDs() {
    const ret = /* @__PURE__ */ new Map();
    for (const pNode of this.physicalNodes) {
      const valueIDs = pNode.getDefinedValueIDs().filter((v) => import_core.actuatorCCs.includes(v.commandClass));
      for (const valueId of valueIDs) {
        const mapKey = (0, import_core.valueIdToString)(valueId);
        const ccVersion = pNode.getCCVersion(valueId.commandClass);
        const metadata = pNode.getValueMetadata(valueId);
        if (!metadata.writeable)
          continue;
        const needsUpdate = !ret.has(mapKey) || ret.get(mapKey).ccVersion < ccVersion;
        if (needsUpdate) {
          ret.set(mapKey, {
            ...valueId,
            ccVersion,
            metadata: {
              ...metadata,
              // Metadata of virtual nodes is only writable
              readable: false
            }
          });
        }
      }
    }
    const exposedEndpoints = (0, import_arrays.distinct)([...ret.values()].map((v) => v.endpoint).filter((e) => e !== void 0));
    for (const endpoint of exposedEndpoints) {
      const valueId = {
        ...import_cc.BasicCCValues.targetValue.endpoint(endpoint),
        commandClassName: "Basic",
        propertyName: "Target value"
      };
      const ccVersion = 1;
      const metadata = {
        ...import_cc.BasicCCValues.targetValue.meta,
        readable: false
      };
      ret.set((0, import_core.valueIdToString)(valueId), {
        ...valueId,
        ccVersion,
        metadata
      });
    }
    return [...ret.values()];
  }
  /** Cache for this node's endpoint instances */
  _endpointInstances = /* @__PURE__ */ new Map();
  getEndpoint(index) {
    if (index < 0) {
      throw new import_core.ZWaveError("The endpoint index must be positive!", import_core.ZWaveErrorCodes.Argument_Invalid);
    }
    if (!index)
      return this;
    if (!this.isMultiChannelInterviewComplete) {
      this.driver.driverLog.print(`Virtual node ${this.id ?? "??"}, Endpoint ${index}: Trying to access endpoint instance before the Multi Channel interview of all nodes was completed!`, "error");
      return void 0;
    }
    if (index > this.getEndpointCount())
      return void 0;
    if (!this._endpointInstances.has(index)) {
      this._endpointInstances.set(index, new import_VirtualEndpoint.VirtualEndpoint(this, this.driver, index));
    }
    return this._endpointInstances.get(index);
  }
  getEndpointOrThrow(index) {
    const ret = this.getEndpoint(index);
    if (!ret) {
      throw new import_core.ZWaveError(`Endpoint ${index} does not exist on virtual node ${this.id ?? "??"}`, import_core.ZWaveErrorCodes.Controller_EndpointNotFound);
    }
    return ret;
  }
  /** Returns the current endpoint count of this virtual node (the maximum in the list of physical nodes) */
  getEndpointCount() {
    let ret = 0;
    for (const node of this.physicalNodes) {
      const count = node.getEndpointCount();
      ret = Math.max(ret, count);
    }
    return ret;
  }
  get isMultiChannelInterviewComplete() {
    for (const node of this.physicalNodes) {
      if (!node["isMultiChannelInterviewComplete"])
        return false;
    }
    return true;
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  VirtualNode
});
//# sourceMappingURL=VirtualNode.js.map
