"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var NetworkCache_exports = {};
__export(NetworkCache_exports, {
  cacheKeyUtils: () => cacheKeyUtils,
  cacheKeys: () => cacheKeys,
  deserializeNetworkCacheValue: () => deserializeNetworkCacheValue,
  migrateLegacyNetworkCache: () => migrateLegacyNetworkCache,
  serializeNetworkCacheValue: () => serializeNetworkCacheValue
});
module.exports = __toCommonJS(NetworkCache_exports);
var import_core = require("@zwave-js/core");
var import_shared = require("@zwave-js/shared");
var import_typeguards = require("alcalzone-shared/typeguards");
var import_pathe = __toESM(require("pathe"), 1);
var import_Inclusion = require("../controller/Inclusion.js");
var import_DeviceClass = require("../node/DeviceClass.js");
var import_Types = require("../node/_Types.js");
const cacheKeys = {
  controller: {
    provisioningList: "controller.provisioningList",
    associations: /* @__PURE__ */ __name((groupId) => `controller.associations.${groupId}`, "associations"),
    securityKeys: /* @__PURE__ */ __name((secClass) => `controller.securityKeys.${(0, import_shared.getEnumMemberName)(import_core.SecurityClass, secClass)}`, "securityKeys"),
    securityKeysLongRange: /* @__PURE__ */ __name((secClass) => `controller.securityKeyLongRange.${(0, import_shared.getEnumMemberName)(import_core.SecurityClass, secClass)}`, "securityKeysLongRange"),
    privateKey: "controller.privateKey"
  },
  // TODO: somehow these functions should be combined with the pattern matching below
  node: /* @__PURE__ */ __name((nodeId) => {
    const nodeBaseKey = `node.${nodeId}.`;
    return {
      _baseKey: nodeBaseKey,
      _securityClassBaseKey: `${nodeBaseKey}securityClasses`,
      _priorityReturnRouteBaseKey: `${nodeBaseKey}priorityReturnRoute`,
      interviewStage: `${nodeBaseKey}interviewStage`,
      deviceClass: `${nodeBaseKey}deviceClass`,
      isListening: `${nodeBaseKey}isListening`,
      isFrequentListening: `${nodeBaseKey}isFrequentListening`,
      isRouting: `${nodeBaseKey}isRouting`,
      supportedDataRates: `${nodeBaseKey}supportedDataRates`,
      protocolVersion: `${nodeBaseKey}protocolVersion`,
      nodeType: `${nodeBaseKey}nodeType`,
      supportsSecurity: `${nodeBaseKey}supportsSecurity`,
      supportsBeaming: `${nodeBaseKey}supportsBeaming`,
      securityClass: /* @__PURE__ */ __name((secClass) => `${nodeBaseKey}securityClasses.${(0, import_shared.getEnumMemberName)(import_core.SecurityClass, secClass)}`, "securityClass"),
      dsk: `${nodeBaseKey}dsk`,
      endpoint: /* @__PURE__ */ __name((index) => {
        const endpointBaseKey = `${nodeBaseKey}endpoint.${index}.`;
        const ccBaseKey = `${endpointBaseKey}commandClass.`;
        return {
          _baseKey: endpointBaseKey,
          _ccBaseKey: ccBaseKey,
          commandClass: /* @__PURE__ */ __name((ccId) => {
            const ccAsHex = (0, import_shared.num2hex)(ccId);
            return `${ccBaseKey}${ccAsHex}`;
          }, "commandClass")
        };
      }, "endpoint"),
      hasSUCReturnRoute: `${nodeBaseKey}hasSUCReturnRoute`,
      priorityReturnRoute: /* @__PURE__ */ __name((destinationNodeId) => `${nodeBaseKey}priorityReturnRoute.${destinationNodeId}`, "priorityReturnRoute"),
      prioritySUCReturnRoute: `${nodeBaseKey}priorityReturnRoute.SUC`,
      customReturnRoutes: /* @__PURE__ */ __name((destinationNodeId) => `${nodeBaseKey}customReturnRoutes.${destinationNodeId}`, "customReturnRoutes"),
      customSUCReturnRoutes: `${nodeBaseKey}customReturnRoutes.SUC`,
      defaultTransitionDuration: `${nodeBaseKey}defaultTransitionDuration`,
      defaultVolume: `${nodeBaseKey}defaultVolume`,
      lastSeen: `${nodeBaseKey}lastSeen`,
      deviceConfigHash: `${nodeBaseKey}deviceConfigHash`
    };
  }, "node")
};
const cacheKeyUtils = {
  nodeIdFromKey: /* @__PURE__ */ __name((key) => {
    const match = /^node\.(?<nodeId>\d+)\./.exec(key);
    if (match) {
      return parseInt(match.groups.nodeId, 10);
    }
  }, "nodeIdFromKey"),
  nodePropertyFromKey: /* @__PURE__ */ __name((key) => {
    const match = /^node\.\d+\.(?<property>[^\.]+)$/.exec(key);
    return match?.groups?.property;
  }, "nodePropertyFromKey"),
  isEndpointKey: /* @__PURE__ */ __name((key) => {
    return /endpoints\.(?<index>\d+)$/.test(key);
  }, "isEndpointKey"),
  endpointIndexFromKey: /* @__PURE__ */ __name((key) => {
    const match = /endpoints\.(?<index>\d+)$/.exec(key);
    if (match) {
      return parseInt(match.groups.index, 10);
    }
  }, "endpointIndexFromKey"),
  destinationFromPriorityReturnRouteKey: /* @__PURE__ */ __name((key) => {
    const match = /\.priorityReturnRoute\.(?<nodeId>\d+)$/.exec(key);
    if (match) {
      return parseInt(match.groups.nodeId, 10);
    }
  }, "destinationFromPriorityReturnRouteKey")
};
function tryParseInterviewStage(value) {
  if ((typeof value === "string" || typeof value === "number") && value in import_Types.InterviewStage) {
    return typeof value === "number" ? value : import_Types.InterviewStage[value];
  }
}
__name(tryParseInterviewStage, "tryParseInterviewStage");
function tryParseDeviceClass(value) {
  if ((0, import_typeguards.isObject)(value)) {
    const { basic, generic, specific } = value;
    if (typeof basic === "number" && typeof generic === "number" && typeof specific === "number") {
      return new import_DeviceClass.DeviceClass(basic, generic, specific);
    }
  }
}
__name(tryParseDeviceClass, "tryParseDeviceClass");
function tryParseSecurityClasses(value) {
  if ((0, import_typeguards.isObject)(value)) {
    const ret = /* @__PURE__ */ new Map();
    for (const [key, val] of Object.entries(value)) {
      if (key in import_core.SecurityClass && typeof import_core.SecurityClass[key] === "number" && typeof val === "boolean") {
        ret.set(import_core.SecurityClass[key], val);
      }
    }
    return ret;
  }
}
__name(tryParseSecurityClasses, "tryParseSecurityClasses");
function tryParseNodeType(value) {
  if (typeof value === "string" && value in import_core.NodeType) {
    return import_core.NodeType[value];
  }
}
__name(tryParseNodeType, "tryParseNodeType");
function tryParseProvisioningList(value) {
  const ret = [];
  if (!(0, import_typeguards.isArray)(value))
    return;
  for (const entry of value) {
    if ((0, import_typeguards.isObject)(entry) && typeof entry.dsk === "string" && (0, import_typeguards.isArray)(entry.securityClasses) && entry.securityClasses.every((s) => isSerializedSecurityClass(s)) && (entry.requestedSecurityClasses == void 0 || (0, import_typeguards.isArray)(entry.requestedSecurityClasses) && entry.requestedSecurityClasses.every((s) => isSerializedSecurityClass(s))) && (entry.protocol == void 0 || isSerializedProtocol(entry.protocol)) && (entry.supportedProtocols == void 0 || (0, import_typeguards.isArray)(entry.supportedProtocols) && entry.supportedProtocols.every((s) => isSerializedProtocol(s))) && (entry.status == void 0 || isSerializedProvisioningEntryStatus(entry.status))) {
      if ("nodeId" in entry && typeof entry.nodeId !== "number") {
        return;
      }
      const parsed = {
        ...entry
      };
      parsed.securityClasses = entry.securityClasses.map((s) => tryParseSerializedSecurityClass(s)).filter((s) => s !== void 0);
      if (entry.requestedSecurityClasses) {
        parsed.requestedSecurityClasses = entry.requestedSecurityClasses.map((s) => tryParseSerializedSecurityClass(s)).filter((s) => s !== void 0);
      }
      if (entry.status != void 0) {
        parsed.status = import_Inclusion.ProvisioningEntryStatus[entry.status];
      }
      if (entry.protocol != void 0) {
        parsed.protocol = tryParseSerializedProtocol(entry.protocol);
      }
      if (entry.supportedProtocols) {
        parsed.supportedProtocols = entry.supportedProtocols.map((s) => tryParseSerializedProtocol(s)).filter((s) => s !== void 0);
      }
      ret.push(parsed);
    } else {
      return;
    }
  }
  return ret;
}
__name(tryParseProvisioningList, "tryParseProvisioningList");
function isSerializedSecurityClass(value) {
  if (typeof value === "number" && value in import_core.SecurityClass)
    return true;
  if (typeof value === "string") {
    if (value.startsWith("unknown (0x") && value.endsWith(")")) {
      value = value.slice(11, -1);
    }
    if (value in import_core.SecurityClass && typeof import_core.SecurityClass[value] === "number") {
      return true;
    }
  }
  return false;
}
__name(isSerializedSecurityClass, "isSerializedSecurityClass");
function tryParseSerializedSecurityClass(value) {
  if (typeof value === "number" && value in import_core.SecurityClass)
    return value;
  if (typeof value === "string") {
    if (value.startsWith("unknown (0x") && value.endsWith(")")) {
      value = value.slice(11, -1);
    }
    if (value in import_core.SecurityClass && typeof import_core.SecurityClass[value] === "number") {
      return import_core.SecurityClass[value];
    }
  }
}
__name(tryParseSerializedSecurityClass, "tryParseSerializedSecurityClass");
function isSerializedProvisioningEntryStatus(s) {
  return typeof s === "string" && s in import_Inclusion.ProvisioningEntryStatus && typeof import_Inclusion.ProvisioningEntryStatus[s] === "number";
}
__name(isSerializedProvisioningEntryStatus, "isSerializedProvisioningEntryStatus");
function isSerializedProtocol(s) {
  if (typeof s === "number" && s in import_core.Protocols)
    return true;
  return typeof s === "string" && s in import_core.Protocols && typeof import_core.Protocols[s] === "number";
}
__name(isSerializedProtocol, "isSerializedProtocol");
function tryParseSerializedProtocol(value) {
  if (typeof value === "number" && value in import_core.Protocols)
    return value;
  if (typeof value === "string") {
    if (value in import_core.Protocols && typeof import_core.Protocols[value] === "number") {
      return import_core.Protocols[value];
    }
  }
}
__name(tryParseSerializedProtocol, "tryParseSerializedProtocol");
function tryParseDate(value) {
  if (typeof value === "number") {
    const ret = new Date(value);
    if (!isNaN(ret.getTime()))
      return ret;
  }
}
__name(tryParseDate, "tryParseDate");
function tryParseAssociationAddress(value) {
  if ((0, import_typeguards.isObject)(value)) {
    const { nodeId, endpoint } = value;
    if (typeof nodeId !== "number")
      return;
    if (endpoint !== void 0 && typeof endpoint !== "number")
      return;
    return { nodeId, endpoint };
  }
}
__name(tryParseAssociationAddress, "tryParseAssociationAddress");
function tryParseBuffer(value) {
  if (typeof value === "string") {
    try {
      return import_shared.Bytes.from(value, "hex");
    } catch {
    }
  }
}
__name(tryParseBuffer, "tryParseBuffer");
function tryParseBufferBase64(value) {
  if (typeof value === "string") {
    try {
      return import_shared.Bytes.from(value, "base64");
    } catch {
    }
  }
}
__name(tryParseBufferBase64, "tryParseBufferBase64");
function deserializeNetworkCacheValue(key, value) {
  function ensureType(value2, type) {
    if (typeof value2 === type)
      return value2;
    throw new import_core.ZWaveError(`Incorrect type ${typeof value2} for property "${key}"`, import_core.ZWaveErrorCodes.Driver_InvalidCache);
  }
  __name(ensureType, "ensureType");
  function fail() {
    throw new import_core.ZWaveError(`Failed to deserialize property "${key}"`, import_core.ZWaveErrorCodes.Driver_InvalidCache);
  }
  __name(fail, "fail");
  switch (cacheKeyUtils.nodePropertyFromKey(key)) {
    case "interviewStage": {
      value = tryParseInterviewStage(value);
      if (value)
        return value;
      fail();
    }
    case "deviceClass": {
      value = tryParseDeviceClass(value);
      if (value)
        return value;
      fail();
    }
    case "isListening":
    case "isRouting":
    case "hasSUCReturnRoute":
      return ensureType(value, "boolean");
    case "isFrequentListening": {
      switch (value) {
        case "1000ms":
        case true:
          return "1000ms";
        case "250ms":
          return "250ms";
        case false:
          return false;
      }
      fail();
    }
    case "dsk": {
      if (typeof value === "string") {
        return (0, import_core.dskFromString)(value);
      }
      fail();
    }
    case "supportsSecurity":
      return ensureType(value, "boolean");
    case "supportsBeaming":
      try {
        return ensureType(value, "boolean");
      } catch {
        return ensureType(value, "string");
      }
    case "protocolVersion":
      return ensureType(value, "number");
    case "nodeType": {
      value = tryParseNodeType(value);
      if (value)
        return value;
      fail();
    }
    case "supportedDataRates": {
      if ((0, import_typeguards.isArray)(value) && value.every((r) => typeof r === "number")) {
        return value;
      }
      fail();
    }
    case "lastSeen": {
      value = tryParseDate(value);
      if (value)
        return value;
      fail();
    }
    case "deviceConfigHash": {
      if (typeof value !== "string")
        fail();
      const versionMatch = value.match(/^\$v\d+\$/)?.[0];
      if (versionMatch) {
        value = tryParseBufferBase64(value.slice(versionMatch.length));
        if (value) {
          value = import_shared.Bytes.concat([
            import_shared.Bytes.from(versionMatch, "utf8"),
            value
          ]);
        }
      } else {
        value = tryParseBuffer(value);
      }
      if (value)
        return value;
      fail();
    }
  }
  if (key.startsWith("controller.associations.")) {
    value = tryParseAssociationAddress(value);
    if (value)
      return value;
    fail();
  } else if (key.startsWith("controller.securityKeys.")) {
    value = tryParseBuffer(value);
    if (value)
      return value;
    fail();
  }
  switch (key) {
    case cacheKeys.controller.provisioningList: {
      value = tryParseProvisioningList(value);
      if (value)
        return value;
      fail();
    }
    case cacheKeys.controller.privateKey: {
      value = tryParseBuffer(value);
      if (value)
        return value;
      fail();
    }
  }
  return value;
}
__name(deserializeNetworkCacheValue, "deserializeNetworkCacheValue");
function serializeNetworkCacheValue(key, value) {
  switch (cacheKeyUtils.nodePropertyFromKey(key)) {
    case "interviewStage": {
      return import_Types.InterviewStage[value];
    }
    case "deviceClass": {
      const deviceClass = value;
      return {
        basic: deviceClass.basic,
        generic: deviceClass.generic.key,
        specific: deviceClass.specific.key
      };
    }
    case "nodeType": {
      return import_core.NodeType[value];
    }
    case "securityClasses": {
      const ret = {};
      for (const secClass of import_core.securityClassOrder) {
        if (secClass in value) {
          ret[import_core.SecurityClass[secClass]] = value[secClass];
        }
      }
      return ret;
    }
    case "dsk": {
      return (0, import_core.dskToString)(value);
    }
    case "lastSeen": {
      return value.getTime();
    }
    case "deviceConfigHash": {
      const valueAsString = import_shared.Bytes.view(value).toString("utf8");
      const versionMatch = valueAsString.match(/^\$v\d+\$/)?.[0];
      if (versionMatch) {
        return versionMatch + import_shared.Bytes.view(value).subarray(versionMatch.length).toString("base64");
      } else {
        return import_shared.Bytes.view(value).toString("hex");
      }
    }
  }
  if (key.startsWith("controller.securityKeys.")) {
    return import_shared.Bytes.view(value).toString("hex");
  }
  switch (key) {
    case cacheKeys.controller.provisioningList: {
      const ret = [];
      for (const entry of value) {
        const serialized = { ...entry };
        serialized.securityClasses = entry.securityClasses.map((c) => (0, import_shared.getEnumMemberName)(import_core.SecurityClass, c));
        if (entry.requestedSecurityClasses) {
          serialized.requestedSecurityClasses = entry.requestedSecurityClasses.map((c) => (0, import_shared.getEnumMemberName)(import_core.SecurityClass, c));
        }
        if (entry.status != void 0) {
          serialized.status = (0, import_shared.getEnumMemberName)(import_Inclusion.ProvisioningEntryStatus, entry.status);
        }
        if (entry.protocol != void 0) {
          serialized.protocol = (0, import_shared.getEnumMemberName)(import_core.Protocols, entry.protocol);
        }
        if (entry.supportedProtocols != void 0) {
          serialized.supportedProtocols = entry.supportedProtocols.map((p) => (0, import_shared.getEnumMemberName)(import_core.Protocols, p));
        }
        ret.push(serialized);
      }
      return ret;
    }
    case cacheKeys.controller.privateKey: {
      return import_shared.Bytes.view(value).toString("hex");
    }
  }
  return value;
}
__name(serializeNetworkCacheValue, "serializeNetworkCacheValue");
const legacyPaths = {
  // These seem to duplicate the ones in cacheKeys, but this allows us to change
  // something in the future without breaking migration
  controller: {
    provisioningList: "controller.provisioningList"
  },
  node: {
    // These are relative to the node object
    interviewStage: `interviewStage`,
    deviceClass: `deviceClass`,
    isListening: `isListening`,
    isFrequentListening: `isFrequentListening`,
    isRouting: `isRouting`,
    supportedDataRates: `supportedDataRates`,
    protocolVersion: `protocolVersion`,
    nodeType: `nodeType`,
    supportsSecurity: `supportsSecurity`,
    supportsBeaming: `supportsBeaming`,
    securityClasses: `securityClasses`,
    dsk: `dsk`
  },
  commandClass: {
    // These are relative to the commandClasses object
    name: `name`,
    endpoint: /* @__PURE__ */ __name((index) => `endpoints.${index}`, "endpoint")
  }
};
async function migrateLegacyNetworkCache(homeId, networkCache, valueDB, fs, cacheDir) {
  const cacheFile = import_pathe.default.join(cacheDir, `${homeId.toString(16)}.json`);
  try {
    const stat = await fs.stat(cacheFile);
    if (!stat.isFile())
      return;
  } catch {
    return;
  }
  const legacyContents = await fs.readFile(cacheFile);
  const legacy = JSON.parse(import_shared.Bytes.view(legacyContents).toString("utf8"));
  const jsonl = networkCache;
  function tryMigrate(targetKey, source, sourcePath, converter) {
    let val = (0, import_shared.pickDeep)(source, sourcePath);
    if (val != void 0 && converter)
      val = converter(val);
    if (val != void 0)
      jsonl.set(targetKey, val);
  }
  __name(tryMigrate, "tryMigrate");
  tryMigrate(cacheKeys.controller.provisioningList, legacy, legacyPaths.controller.provisioningList, tryParseProvisioningList);
  if ((0, import_typeguards.isObject)(legacy.nodes)) {
    for (const node of Object.values(legacy.nodes)) {
      if (!(0, import_typeguards.isObject)(node) || typeof node.id !== "number")
        continue;
      const nodeCacheKeys = cacheKeys.node(node.id);
      tryMigrate(nodeCacheKeys.interviewStage, node, legacyPaths.node.interviewStage, tryParseInterviewStage);
      tryMigrate(nodeCacheKeys.deviceClass, node, legacyPaths.node.deviceClass, (v) => tryParseDeviceClass(v));
      tryMigrate(nodeCacheKeys.isListening, node, legacyPaths.node.isListening);
      tryMigrate(nodeCacheKeys.isFrequentListening, node, legacyPaths.node.isFrequentListening);
      tryMigrate(nodeCacheKeys.isRouting, node, legacyPaths.node.isRouting);
      tryMigrate(nodeCacheKeys.supportedDataRates, node, legacyPaths.node.supportedDataRates);
      tryMigrate(nodeCacheKeys.protocolVersion, node, legacyPaths.node.protocolVersion);
      tryMigrate(nodeCacheKeys.nodeType, node, legacyPaths.node.nodeType, tryParseNodeType);
      tryMigrate(nodeCacheKeys.supportsSecurity, node, legacyPaths.node.supportsSecurity);
      tryMigrate(nodeCacheKeys.supportsBeaming, node, legacyPaths.node.supportsBeaming);
      const securityClasses = tryParseSecurityClasses((0, import_shared.pickDeep)(node, legacyPaths.node.securityClasses));
      if (securityClasses) {
        for (const [secClass, val] of securityClasses) {
          jsonl.set(nodeCacheKeys.securityClass(secClass), val);
        }
      }
      tryMigrate(nodeCacheKeys.dsk, node, legacyPaths.node.dsk, import_core.dskFromString);
      if ((0, import_typeguards.isObject)(node.commandClasses)) {
        for (const [ccIdHex, cc] of Object.entries(node.commandClasses)) {
          const ccId = parseInt(ccIdHex, 16);
          if ((0, import_typeguards.isObject)(cc.endpoints)) {
            for (const endpointId of Object.keys(cc.endpoints)) {
              const endpointIndex = parseInt(endpointId, 10);
              const cacheKey = nodeCacheKeys.endpoint(endpointIndex).commandClass(ccId);
              tryMigrate(cacheKey, cc, legacyPaths.commandClass.endpoint(endpointIndex));
            }
          }
        }
      }
      const dbKey = JSON.stringify({
        nodeId: node.id,
        commandClass: -1,
        endpoint: 0,
        property: "hasSUCReturnRoute"
      });
      if (valueDB.has(dbKey)) {
        const hasSUCReturnRoute = valueDB.get(dbKey);
        valueDB.delete(dbKey);
        jsonl.set(nodeCacheKeys.hasSUCReturnRoute, hasSUCReturnRoute);
      }
    }
  }
}
__name(migrateLegacyNetworkCache, "migrateLegacyNetworkCache");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  cacheKeyUtils,
  cacheKeys,
  deserializeNetworkCacheValue,
  migrateLegacyNetworkCache,
  serializeNetworkCacheValue
});
//# sourceMappingURL=NetworkCache.js.map
