"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 Endpoint_exports = {};
__export(Endpoint_exports, {
  Endpoint: () => Endpoint
});
module.exports = __toCommonJS(Endpoint_exports);
var import_cc = require("@zwave-js/cc");
var import_ZWavePlusCC = require("@zwave-js/cc/ZWavePlusCC");
var import_core = require("@zwave-js/core");
var import_shared = require("@zwave-js/shared");
var import_NetworkCache = require("../driver/NetworkCache.js");
class Endpoint {
  static {
    __name(this, "Endpoint");
  }
  nodeId;
  driver;
  index;
  constructor(nodeId, driver, index, deviceClass, supportedCCs) {
    this.nodeId = nodeId;
    this.driver = driver;
    this.index = index;
    this._implementedCommandClasses = new import_core.CacheBackedMap(this.driver.networkCache, {
      prefix: import_NetworkCache.cacheKeys.node(this.nodeId).endpoint(this.index)._ccBaseKey,
      suffixSerializer: /* @__PURE__ */ __name((cc) => (0, import_shared.num2hex)(cc), "suffixSerializer"),
      suffixDeserializer: /* @__PURE__ */ __name((key) => {
        const ccId = parseInt(key, 16);
        if (ccId in import_core.CommandClasses)
          return ccId;
      }, "suffixDeserializer")
    });
    if (deviceClass)
      this.deviceClass = deviceClass;
    if (supportedCCs != void 0) {
      for (const cc of supportedCCs) {
        if (cc === import_core.CommandClasses.Basic) {
          continue;
        }
        this.addCC(cc, { isSupported: true });
      }
    }
  }
  /** Required by {@link IZWaveEndpoint} */
  virtual = false;
  /**
   * Only used for endpoints which store their device class differently than nodes.
   * DO NOT ACCESS directly!
   */
  _deviceClass;
  get deviceClass() {
    if (this.index > 0) {
      return this._deviceClass;
    } else {
      return this.driver.cacheGet(import_NetworkCache.cacheKeys.node(this.nodeId).deviceClass);
    }
  }
  set deviceClass(deviceClass) {
    if (this.index > 0) {
      this._deviceClass = deviceClass;
    } else {
      this.driver.cacheSet(import_NetworkCache.cacheKeys.node(this.nodeId).deviceClass, deviceClass);
    }
  }
  /** Can be used to distinguish multiple endpoints of a node */
  get endpointLabel() {
    return this.tryGetNode()?.deviceConfig?.endpoints?.get(this.index)?.label;
  }
  /** Resets all stored information of this endpoint */
  reset() {
    this._implementedCommandClasses.clear();
    this._commandClassAPIs.clear();
  }
  _implementedCommandClasses;
  /**
   * @internal
   * Information about the implemented Command Classes of this endpoint.
   */
  get implementedCommandClasses() {
    return this._implementedCommandClasses;
  }
  getCCs() {
    return this._implementedCommandClasses.entries();
  }
  /**
   * Adds a CC to the list of command classes implemented by the endpoint or updates the information.
   * You shouldn't need to call this yourself.
   * @param info The information about the command class. This is merged with existing information.
   */
  addCC(cc, info) {
    if (this.index > 0 && cc === import_core.CommandClasses["Multi Channel"])
      return;
    const original = this._implementedCommandClasses.get(cc);
    const updated = Object.assign({}, original ?? {
      isSupported: false,
      isControlled: false,
      secure: false,
      version: 0
    }, info);
    if (original == void 0 || !(0, import_core.isCCInfoEqual)(original, updated)) {
      this._implementedCommandClasses.set(cc, updated);
    }
  }
  /** Removes a CC from the list of command classes implemented by the endpoint */
  removeCC(cc) {
    this._implementedCommandClasses.delete(cc);
  }
  /** Tests if this endpoint supports the given CommandClass */
  supportsCC(cc) {
    return !!this._implementedCommandClasses.get(cc)?.isSupported;
  }
  /** Tests if this endpoint supports or controls the given CC only securely */
  isCCSecure(cc) {
    return !!this._implementedCommandClasses.get(cc)?.secure;
  }
  /** Tests if this endpoint controls the given CommandClass */
  controlsCC(cc) {
    return !!this._implementedCommandClasses.get(cc)?.isControlled;
  }
  /**
   * Checks if this endpoint is allowed to support Basic CC per the specification.
   * This depends on the device type and the other supported CCs
   */
  maySupportBasicCC() {
    if (import_core.actuatorCCs.some((cc) => this.supportsCC(cc))) {
      return false;
    }
    return this.deviceClass?.specific.maySupportBasicCC ?? this.deviceClass?.generic.maySupportBasicCC ?? true;
  }
  /** Determines if support for a CC was force-removed via config file */
  wasCCRemovedViaConfig(cc) {
    if (this.supportsCC(cc))
      return false;
    const compatConfig = this.tryGetNode()?.deviceConfig?.compat;
    if (!compatConfig)
      return false;
    const removedEndpoints = compatConfig.removeCCs?.get(cc);
    if (!removedEndpoints)
      return false;
    return removedEndpoints == "*" || removedEndpoints.includes(this.index);
  }
  /**
   * Determines if support for a CC was force-added via config file. */
  wasCCSupportAddedViaConfig(cc) {
    const compatConfig = this.tryGetNode()?.deviceConfig?.compat;
    if (!compatConfig)
      return false;
    const addedCC = compatConfig.addCCs?.get(cc);
    if (!addedCC)
      return false;
    const endpointInfo = addedCC.endpoints.get(this.index);
    if (!endpointInfo)
      return false;
    return endpointInfo.isSupported === true;
  }
  /**
   * Retrieves the version of the given CommandClass this endpoint implements.
   * Returns 0 if the CC is not supported.
   */
  getCCVersion(cc) {
    const ccInfo = this._implementedCommandClasses.get(cc);
    const ret = ccInfo?.version ?? 0;
    if (ret === 0 && this.index > 0) {
      return this.tryGetNode().getCCVersion(cc);
    }
    return ret;
  }
  /**
   * Creates an instance of the given CC and links it to this endpoint.
   * Throws if the CC is neither supported nor controlled by the endpoint.
   */
  createCCInstance(cc) {
    const ccId = typeof cc === "number" ? cc : (0, import_cc.getCommandClassStatic)(cc);
    if (!this.supportsCC(ccId) && !this.controlsCC(ccId)) {
      throw new import_core.ZWaveError(`Cannot create an instance of the unsupported CC ${import_core.CommandClasses[ccId]} (${(0, import_shared.num2hex)(ccId)})`, import_core.ZWaveErrorCodes.CC_NotSupported);
    }
    return import_cc.CommandClass.createInstanceUnchecked(this, cc);
  }
  /**
   * Creates an instance of the given CC and links it to this endpoint.
   * Returns `undefined` if the CC is neither supported nor controlled by the endpoint.
   */
  createCCInstanceUnsafe(cc) {
    const ccId = typeof cc === "number" ? cc : (0, import_cc.getCommandClassStatic)(cc);
    if (this.supportsCC(ccId) || this.controlsCC(ccId)) {
      return import_cc.CommandClass.createInstanceUnchecked(this, cc);
    }
  }
  /** Returns instances for all CCs this endpoint supports, that should be interviewed, and that are implemented in this library */
  getSupportedCCInstances() {
    let supportedCCInstances = [...this.implementedCommandClasses.keys()].filter((cc) => this.supportsCC(cc)).map((cc) => this.createCCInstance(cc)).filter((instance) => !!instance);
    if (this.index > 0) {
      supportedCCInstances = supportedCCInstances.filter((instance) => !instance.skipEndpointInterview());
    }
    return supportedCCInstances;
  }
  /** Builds the dependency graph used to automatically determine the order of CC interviews */
  buildCCInterviewGraph(skipCCs) {
    const supportedCCs = this.getSupportedCCInstances().map((instance) => instance.ccId).filter((ccId) => !skipCCs.includes(ccId));
    const ret = supportedCCs.map((cc) => new import_core.GraphNode(cc));
    for (const node of ret) {
      const instance = this.createCCInstance(node.value);
      for (const requiredCCId of instance.determineRequiredCCInterviews()) {
        const requiredCC = ret.find((instance2) => instance2.value === requiredCCId);
        if (requiredCC)
          node.edges.add(requiredCC);
      }
    }
    return ret;
  }
  /**
   * @internal
   * Creates an API instance for a given command class. Throws if no API is defined.
   * @param ccId The command class to create an API instance for
   * @param requireSupport Whether accessing the API should throw if it is not supported by the node.
   */
  createAPI(ccId, requireSupport = true) {
    return import_cc.CCAPI.create(ccId, this.driver, this, requireSupport);
  }
  _commandClassAPIs = /* @__PURE__ */ new Map();
  _commandClassAPIsProxy = new Proxy(this._commandClassAPIs, {
    get: /* @__PURE__ */ __name((target, ccNameOrId) => {
      if (process.env.NODE_ENV === "test" && typeof ccNameOrId === "string" && (ccNameOrId === "$$typeof" || ccNameOrId === "constructor" || ccNameOrId.includes("@@__IMMUTABLE"))) {
        return void 0;
      }
      if (typeof ccNameOrId === "symbol") {
        if (ccNameOrId === Symbol.iterator) {
          return this.commandClassesIterator;
        } else if (ccNameOrId === Symbol.toStringTag) {
          return "[object Object]";
        }
        return void 0;
      } else {
        const ccId = (0, import_cc.normalizeCCNameOrId)(ccNameOrId);
        if (ccId == void 0) {
          throw new import_core.ZWaveError(`Command Class ${ccNameOrId} is not implemented!`, import_core.ZWaveErrorCodes.CC_NotImplemented);
        }
        if (!target.has(ccId)) {
          const api = import_cc.CCAPI.create(ccId, this.driver, this);
          target.set(ccId, api);
        }
        return target.get(ccId);
      }
    }, "get")
  });
  /**
   * Used to iterate over the commandClasses API without throwing errors by accessing unsupported CCs
   */
  commandClassesIterator = function* () {
    for (const cc of this.implementedCommandClasses.keys()) {
      if (this.supportsCC(cc))
        yield this.commandClasses[cc];
    }
  }.bind(this);
  /**
   * Provides access to simplified APIs that are tailored to specific CCs.
   * Make sure to check support of each API using `API.isSupported()` since
   * all other API calls will throw if the API is not supported
   */
  get commandClasses() {
    return this._commandClassAPIsProxy;
  }
  /** Allows checking whether a CC API is supported before calling it with {@link Endpoint.invokeCCAPI} */
  supportsCCAPI(cc) {
    return this.commandClasses[cc].isSupported();
  }
  /**
   * Allows dynamically calling any CC API method on this endpoint by CC ID and method name.
   * Use {@link Endpoint.supportsCCAPI} to check support first.
   */
  invokeCCAPI(cc, method, ...args) {
    const CCAPI2 = this.commandClasses[cc];
    const ccId = (0, import_cc.normalizeCCNameOrId)(cc);
    const ccName = (0, import_core.getCCName)(ccId);
    if (!CCAPI2) {
      throw new import_core.ZWaveError(`The API for the ${ccName} CC does not exist or is not implemented!`, import_core.ZWaveErrorCodes.CC_NoAPI);
    }
    const apiMethod = CCAPI2[method];
    if (typeof apiMethod !== "function") {
      throw new import_core.ZWaveError(`Method "${method}" does not exist on the API for the ${ccName} CC!`, import_core.ZWaveErrorCodes.CC_NotImplemented);
    }
    return apiMethod.apply(CCAPI2, args);
  }
  /**
   * Returns the node this endpoint belongs to (or undefined if the node doesn't exist)
   */
  tryGetNode() {
    return this.driver.controller.nodes.get(this.nodeId);
  }
  /** Z-Wave+ Icon (for management) */
  get installerIcon() {
    return this.tryGetNode()?.getValue(import_ZWavePlusCC.ZWavePlusCCValues.installerIcon.endpoint(this.index));
  }
  /** Z-Wave+ Icon (for end users) */
  get userIcon() {
    return this.tryGetNode()?.getValue(import_ZWavePlusCC.ZWavePlusCCValues.userIcon.endpoint(this.index));
  }
  /**
   * @internal
   * Returns a dump of this endpoint's information for debugging purposes
   */
  createEndpointDump() {
    const ret = {
      index: this.index,
      deviceClass: "unknown",
      commandClasses: {},
      maySupportBasicCC: this.maySupportBasicCC()
    };
    if (this.deviceClass) {
      ret.deviceClass = {
        basic: {
          key: this.deviceClass.basic,
          label: (0, import_shared.getEnumMemberName)(import_core.BasicDeviceClass, this.deviceClass.basic)
        },
        generic: {
          key: this.deviceClass.generic.key,
          label: this.deviceClass.generic.label
        },
        specific: {
          key: this.deviceClass.specific.key,
          label: this.deviceClass.specific.label
        }
      };
    }
    for (const [ccId, info] of this._implementedCommandClasses) {
      ret.commandClasses[(0, import_core.getCCName)(ccId)] = { ...info, values: [] };
    }
    for (const [prop, value] of Object.entries(ret)) {
      if (value === void 0)
        delete ret[prop];
    }
    return ret;
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  Endpoint
});
//# sourceMappingURL=Endpoint.js.map
