"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 utils_exports = {};
__export(utils_exports, {
  addAssociations: () => addAssociations,
  assignLifelineIssueingCommand: () => assignLifelineIssueingCommand,
  checkAssociation: () => checkAssociation,
  configureLifelineAssociations: () => configureLifelineAssociations,
  doesAnyLifelineSendActuatorOrSensorReports: () => doesAnyLifelineSendActuatorOrSensorReports,
  getAllAssociationGroups: () => getAllAssociationGroups,
  getAllAssociations: () => getAllAssociations,
  getAssociationGroups: () => getAssociationGroups,
  getAssociations: () => getAssociations,
  getLifelineGroupIds: () => getLifelineGroupIds,
  removeAssociations: () => removeAssociations
});
module.exports = __toCommonJS(utils_exports);
var import_core = require("@zwave-js/core");
var import_shared = require("@zwave-js/shared");
var import_arrays = require("alcalzone-shared/arrays");
var import_AssociationCC = require("../cc/AssociationCC.js");
var import_AssociationGroupInfoCC = require("../cc/AssociationGroupInfoCC.js");
var import_MultiChannelAssociationCC = require("../cc/MultiChannelAssociationCC.js");
var import_API = require("./API.js");
var import_Types = require("./_Types.js");
function getAssociations(ctx, endpoint) {
  const ret = /* @__PURE__ */ new Map();
  if (endpoint.supportsCC(import_core.CommandClasses.Association)) {
    const destinations = import_AssociationCC.AssociationCC.getAllDestinationsCached(ctx, endpoint);
    for (const [groupId, assocs] of destinations) {
      ret.set(groupId, assocs);
    }
  } else {
    throw new import_core.ZWaveError(`Node ${endpoint.nodeId}${endpoint.index > 0 ? `, endpoint ${endpoint.index}` : ""} does not support associations!`, import_core.ZWaveErrorCodes.CC_NotSupported);
  }
  if (endpoint.supportsCC(import_core.CommandClasses["Multi Channel Association"])) {
    const destinations = import_MultiChannelAssociationCC.MultiChannelAssociationCC.getAllDestinationsCached(ctx, endpoint);
    for (const [groupId, assocs] of destinations) {
      if (ret.has(groupId)) {
        const normalAssociations = ret.get(groupId);
        ret.set(groupId, [
          ...normalAssociations,
          // Eliminate potential duplicates
          ...assocs.filter((a1) => !normalAssociations.some((a2) => a1.nodeId === a2.nodeId && a1.endpoint === a2.endpoint))
        ]);
      } else {
        ret.set(groupId, assocs);
      }
    }
  }
  return ret;
}
__name(getAssociations, "getAssociations");
function getAllAssociations(ctx, node) {
  const ret = new import_shared.ObjectKeyMap();
  for (const endpoint of node.getAllEndpoints()) {
    const address = {
      nodeId: node.id,
      endpoint: endpoint.index
    };
    if (endpoint.supportsCC(import_core.CommandClasses.Association)) {
      ret.set(address, getAssociations(ctx, endpoint));
    }
  }
  return ret;
}
__name(getAllAssociations, "getAllAssociations");
function checkAssociation(ctx, endpoint, group, destination) {
  const targetNode = ctx.getNodeOrThrow(destination.nodeId);
  const targetEndpoint = destination.nodeId === ctx.ownNodeId ? targetNode : targetNode.getEndpointOrThrow(destination.endpoint ?? 0);
  if (!endpoint.supportsCC(import_core.CommandClasses.Association) && !endpoint.supportsCC(import_core.CommandClasses["Multi Channel Association"])) {
    throw new import_core.ZWaveError(`Node ${endpoint.nodeId}${endpoint.index > 0 ? `, endpoint ${endpoint.index}` : ""} does not support associations!`, import_core.ZWaveErrorCodes.CC_NotSupported);
  }
  if ((0, import_core.isLongRangeNodeId)(destination.nodeId)) {
    return import_Types.AssociationCheckResult.Forbidden_DestinationIsLongRange;
  } else if ((0, import_core.isLongRangeNodeId)(endpoint.nodeId)) {
    if (group !== 1 || destination.nodeId !== ctx.ownNodeId) {
      return import_Types.AssociationCheckResult.Forbidden_SourceIsLongRange;
    }
  }
  if (destination.nodeId === ctx.ownNodeId) {
    return import_Types.AssociationCheckResult.OK;
  }
  if (destination.nodeId === endpoint.nodeId) {
    return import_Types.AssociationCheckResult.Forbidden_SelfAssociation;
  }
  const sourceNode = ctx.getNode(endpoint.nodeId);
  let securityClassMustMatch;
  if (destination.endpoint == void 0) {
    const sourceNodeCCVersion = endpoint.getCCVersion(import_core.CommandClasses.Association);
    securityClassMustMatch = sourceNodeCCVersion < 3;
  } else {
    const sourceNodeCCVersion = endpoint.getCCVersion(import_core.CommandClasses["Multi Channel Association"]);
    securityClassMustMatch = sourceNodeCCVersion < 4;
  }
  const sourceSecurityClass = sourceNode.getHighestSecurityClass();
  const targetSecurityClass = targetNode.getHighestSecurityClass();
  if (sourceSecurityClass != void 0 && targetSecurityClass != void 0) {
    if (securityClassMustMatch && sourceSecurityClass !== targetSecurityClass) {
      return import_Types.AssociationCheckResult.Forbidden_SecurityClassMismatch;
    } else if (
      // Commands to insecure nodes are allowed
      targetSecurityClass !== import_core.SecurityClass.None && !securityClassMustMatch && !sourceNode.hasSecurityClass(targetSecurityClass)
    ) {
      return import_Types.AssociationCheckResult.Forbidden_DestinationSecurityClassNotGranted;
    }
  }
  if (!endpoint.supportsCC(import_core.CommandClasses["Association Group Information"])) {
    return import_Types.AssociationCheckResult.OK;
  }
  const groupCommandList = import_AssociationGroupInfoCC.AssociationGroupInfoCC.getIssuedCommandsCached(ctx, endpoint, group);
  if (!groupCommandList || !groupCommandList.size) {
    return import_Types.AssociationCheckResult.OK;
  }
  const groupCCs = [...groupCommandList.keys()];
  if (groupCCs.includes(import_core.CommandClasses.Basic) && import_core.actuatorCCs.some((cc) => targetEndpoint?.supportsCC(cc))) {
    return import_Types.AssociationCheckResult.OK;
  }
  if (groupCCs.some((cc) => targetEndpoint?.supportsCC(cc))) {
    return import_Types.AssociationCheckResult.OK;
  } else {
    return import_Types.AssociationCheckResult.Forbidden_NoSupportedCCs;
  }
}
__name(checkAssociation, "checkAssociation");
function getAssociationGroups(ctx, endpoint) {
  let assocInstance;
  let mcInstance;
  if (endpoint.supportsCC(import_core.CommandClasses.Association)) {
    assocInstance = import_AssociationCC.AssociationCC;
  } else {
    throw new import_core.ZWaveError(`Node ${endpoint.nodeId}${endpoint.index > 0 ? `, endpoint ${endpoint.index}` : ""} does not support associations!`, import_core.ZWaveErrorCodes.CC_NotSupported);
  }
  if (endpoint.supportsCC(import_core.CommandClasses["Multi Channel Association"])) {
    mcInstance = import_MultiChannelAssociationCC.MultiChannelAssociationCC;
  }
  const assocGroupCount = assocInstance.getGroupCountCached(ctx, endpoint) ?? 0;
  const mcGroupCount = mcInstance?.getGroupCountCached(ctx, endpoint) ?? 0;
  const groupCount = Math.max(assocGroupCount, mcGroupCount);
  const deviceConfig = ctx.getDeviceConfig?.(endpoint.nodeId);
  const ret = /* @__PURE__ */ new Map();
  if (endpoint.supportsCC(import_core.CommandClasses["Association Group Information"])) {
    const agiInstance = import_AssociationGroupInfoCC.AssociationGroupInfoCC;
    for (let group = 1; group <= groupCount; group++) {
      const assocConfig = deviceConfig?.getAssociationConfigForEndpoint(endpoint.index, group);
      const multiChannel = !!mcInstance && group <= mcGroupCount;
      ret.set(group, {
        maxNodes: (multiChannel ? mcInstance : assocInstance).getMaxNodesCached(ctx, endpoint, group) || 1,
        // AGI implies Z-Wave+ where group 1 is the lifeline
        isLifeline: group === 1,
        label: (
          // prefer the configured label if we have one
          assocConfig?.label ?? agiInstance.getGroupNameCached(ctx, endpoint, group) ?? `Unnamed group ${group}`
        ),
        multiChannel,
        profile: agiInstance.getGroupProfileCached(ctx, endpoint, group),
        issuedCommands: agiInstance.getIssuedCommandsCached(ctx, endpoint, group)
      });
    }
  } else {
    for (let group = 1; group <= groupCount; group++) {
      const assocConfig = deviceConfig?.getAssociationConfigForEndpoint(endpoint.index, group);
      const multiChannel = !!mcInstance && group <= mcGroupCount;
      ret.set(group, {
        maxNodes: (multiChannel ? mcInstance : assocInstance).getMaxNodesCached(ctx, endpoint, group) || assocConfig?.maxNodes || 1,
        isLifeline: assocConfig?.isLifeline ?? group === 1,
        label: assocConfig?.label ?? `Unnamed group ${group}`,
        multiChannel
      });
    }
  }
  return ret;
}
__name(getAssociationGroups, "getAssociationGroups");
function getAllAssociationGroups(ctx, node) {
  const ret = /* @__PURE__ */ new Map();
  for (const endpoint of node.getAllEndpoints()) {
    if (endpoint.supportsCC(import_core.CommandClasses.Association)) {
      ret.set(endpoint.index, getAssociationGroups(ctx, endpoint));
    }
  }
  return ret;
}
__name(getAllAssociationGroups, "getAllAssociationGroups");
async function addAssociations(ctx, endpoint, group, destinations, options) {
  const nodeAndEndpointString = `${endpoint.nodeId}${endpoint.index > 0 ? `, endpoint ${endpoint.index}` : ""}`;
  let assocInstance;
  let mcInstance;
  const nodeAssociations = (0, import_arrays.distinct)(destinations.filter((a) => a.endpoint == void 0).map((a) => a.nodeId));
  const endpointAssociations = destinations.filter((a) => a.endpoint != void 0);
  if (endpoint.supportsCC(import_core.CommandClasses.Association)) {
    assocInstance = import_AssociationCC.AssociationCC;
  } else if (nodeAssociations.length > 0) {
    throw new import_core.ZWaveError(`Node ${nodeAndEndpointString} does not support associations!`, import_core.ZWaveErrorCodes.CC_NotSupported);
  }
  if (endpoint.supportsCC(import_core.CommandClasses["Multi Channel Association"])) {
    mcInstance = import_MultiChannelAssociationCC.MultiChannelAssociationCC;
  } else if (endpointAssociations.length > 0) {
    throw new import_core.ZWaveError(`Node ${nodeAndEndpointString} does not support multi channel associations!`, import_core.ZWaveErrorCodes.CC_NotSupported);
  }
  const selfAssociations = destinations.filter((d) => d.nodeId === endpoint.nodeId);
  if (selfAssociations.length > 0) {
    throw new import_core.ZWaveError(`Associating a node with itself is not allowed!`, import_core.ZWaveErrorCodes.AssociationCC_NotAllowed, selfAssociations.map((a) => ({
      ...a,
      checkResult: import_Types.AssociationCheckResult.Forbidden_SelfAssociation
    })));
  }
  const assocGroupCount = assocInstance?.getGroupCountCached(ctx, endpoint) ?? 0;
  const mcGroupCount = mcInstance?.getGroupCountCached(ctx, endpoint) ?? 0;
  const groupCount = Math.max(assocGroupCount, mcGroupCount);
  if (group > groupCount) {
    throw new import_core.ZWaveError(`Group ${group} does not exist on node ${nodeAndEndpointString}`, import_core.ZWaveErrorCodes.AssociationCC_InvalidGroup);
  }
  const deviceConfig = ctx.getDeviceConfig?.(endpoint.nodeId);
  const groupIsMultiChannel = !!mcInstance && group <= mcGroupCount && deviceConfig?.associations?.get(group)?.multiChannel !== false;
  if (groupIsMultiChannel) {
    if (!options?.force) {
      const disallowedAssociations = destinations.map((a) => ({
        ...a,
        checkResult: checkAssociation(ctx, endpoint, group, a)
      })).filter(({ checkResult }) => checkResult !== import_Types.AssociationCheckResult.OK);
      if (disallowedAssociations.length) {
        let message = `The following associations are not allowed:`;
        message += disallowedAssociations.map((a) => `
\xB7 Node ${a.nodeId}${a.endpoint ? `, endpoint ${a.endpoint}` : ""}: ${(0, import_shared.getEnumMemberName)(import_Types.AssociationCheckResult, a.checkResult).replace("Forbidden_", "")}`).join("");
        throw new import_core.ZWaveError(message, import_core.ZWaveErrorCodes.AssociationCC_NotAllowed, disallowedAssociations);
      }
    }
    const api = import_API.CCAPI.create(import_core.CommandClasses["Multi Channel Association"], ctx, endpoint);
    await api.addDestinations({
      groupId: group,
      nodeIds: nodeAssociations,
      endpoints: endpointAssociations
    });
    await api.getGroup(group);
  } else {
    if (destinations.some((a) => a.endpoint != void 0)) {
      throw new import_core.ZWaveError(`Node ${nodeAndEndpointString}, group ${group} does not support multi channel associations!`, import_core.ZWaveErrorCodes.CC_NotSupported);
    }
    if (!options?.force) {
      const disallowedAssociations = destinations.map((a) => ({
        ...a,
        checkResult: checkAssociation(ctx, endpoint, group, a)
      })).filter(({ checkResult }) => checkResult !== import_Types.AssociationCheckResult.OK);
      if (disallowedAssociations.length) {
        let message = `The associations to the following nodes are not allowed`;
        message += disallowedAssociations.map((a) => `
\xB7 Node ${a.nodeId}: ${(0, import_shared.getEnumMemberName)(import_Types.AssociationCheckResult, a.checkResult).replace("Forbidden_", "")}`).join("");
        throw new import_core.ZWaveError(message, import_core.ZWaveErrorCodes.AssociationCC_NotAllowed, disallowedAssociations);
      }
    }
    const api = import_API.CCAPI.create(import_core.CommandClasses.Association, ctx, endpoint);
    await api.addNodeIds(group, ...destinations.map((a) => a.nodeId));
    await api.getGroup(group);
  }
}
__name(addAssociations, "addAssociations");
async function removeAssociations(ctx, endpoint, group, destinations) {
  const nodeAndEndpointString = `${endpoint.nodeId}${endpoint.index > 0 ? `, endpoint ${endpoint.index}` : ""}`;
  const nodeAssociations = (0, import_arrays.distinct)(destinations.filter((a) => a.endpoint == void 0).map((a) => a.nodeId));
  const endpointAssociations = destinations.filter((a) => a.endpoint != void 0);
  let groupExistsAsMultiChannel = false;
  let groupExistsAsNodeAssociation = false;
  let mcInstance;
  let assocInstance;
  if (endpoint.supportsCC(import_core.CommandClasses["Multi Channel Association"])) {
    mcInstance = import_MultiChannelAssociationCC.MultiChannelAssociationCC;
    if (group <= mcInstance.getGroupCountCached(ctx, endpoint)) {
      groupExistsAsMultiChannel = true;
    }
  } else if (endpointAssociations.length > 0) {
    throw new import_core.ZWaveError(`Node ${nodeAndEndpointString} does not support multi channel associations!`, import_core.ZWaveErrorCodes.CC_NotSupported);
  }
  if (endpoint.supportsCC(import_core.CommandClasses.Association)) {
    assocInstance = import_AssociationCC.AssociationCC;
    if (group <= assocInstance.getGroupCountCached(ctx, endpoint)) {
      groupExistsAsNodeAssociation = true;
    }
  }
  if (!mcInstance && !assocInstance) {
    throw new import_core.ZWaveError(`Node ${nodeAndEndpointString} does not support associations!`, import_core.ZWaveErrorCodes.CC_NotSupported);
  }
  if (!groupExistsAsMultiChannel && !groupExistsAsNodeAssociation) {
    throw new import_core.ZWaveError(` Association group ${group} does not exist for node ${nodeAndEndpointString}`, import_core.ZWaveErrorCodes.AssociationCC_InvalidGroup);
  }
  if (endpointAssociations.length > 0 && !groupExistsAsMultiChannel) {
    throw new import_core.ZWaveError(`Node ${nodeAndEndpointString}, association group ${group} does not support multi channel associations!`, import_core.ZWaveErrorCodes.AssociationCC_InvalidGroup);
  }
  if (assocInstance && nodeAssociations.length > 0 && groupExistsAsNodeAssociation) {
    const api = import_API.CCAPI.create(import_core.CommandClasses.Association, ctx, endpoint);
    await api.removeNodeIds({
      groupId: group,
      nodeIds: nodeAssociations
    });
    await api.getGroup(group);
  }
  if (mcInstance && groupExistsAsMultiChannel) {
    const api = import_API.CCAPI.create(import_core.CommandClasses["Multi Channel Association"], ctx, endpoint);
    await api.removeDestinations({
      groupId: group,
      nodeIds: nodeAssociations,
      endpoints: endpointAssociations
    });
    await api.getGroup(group);
  }
}
__name(removeAssociations, "removeAssociations");
function getLifelineGroupIds(ctx, endpoint) {
  if (endpoint.index > 0)
    return [];
  const lifelineGroups = [];
  if (endpoint.supportsCC(import_core.CommandClasses["Z-Wave Plus Info"])) {
    lifelineGroups.push(1);
  }
  let associations;
  const deviceConfig = ctx.getDeviceConfig?.(endpoint.nodeId);
  if (endpoint.index === 0) {
    associations = deviceConfig?.associations ?? deviceConfig?.endpoints?.get(0)?.associations;
  } else {
    associations = deviceConfig?.endpoints?.get(endpoint.index)?.associations;
  }
  if (associations?.size) {
    lifelineGroups.push(...[...associations.values()].filter((a) => a.isLifeline).map((a) => a.groupId));
  }
  return (0, import_arrays.distinct)(lifelineGroups).toSorted((a, b) => a - b);
}
__name(getLifelineGroupIds, "getLifelineGroupIds");
async function configureLifelineAssociations(ctx, endpoint) {
  const ownNodeId = ctx.ownNodeId;
  const node = ctx.getNodeOrThrow(endpoint.nodeId);
  const valueDB = ctx.getValueDB(node.id);
  const deviceConfig = ctx.getDeviceConfig?.(node.id);
  const nodeSupportsMultiChannel = node.supportsCC(import_core.CommandClasses["Multi Channel"]);
  let assocInstance;
  const assocAPI = import_API.CCAPI.create(import_core.CommandClasses.Association, ctx, endpoint);
  if (endpoint.supportsCC(import_core.CommandClasses.Association)) {
    assocInstance = import_AssociationCC.AssociationCC;
  }
  let mcInstance;
  let mcGroupCount = 0;
  const mcAPI = import_API.CCAPI.create(import_core.CommandClasses["Multi Channel Association"], ctx, endpoint);
  if (endpoint.supportsCC(import_core.CommandClasses["Multi Channel Association"])) {
    mcInstance = import_MultiChannelAssociationCC.MultiChannelAssociationCC;
    mcGroupCount = mcInstance.getGroupCountCached(ctx, endpoint) ?? 0;
  }
  const lifelineGroups = getLifelineGroupIds(ctx, node);
  if (lifelineGroups.length === 0) {
    if (endpoint.supportsCC(import_core.CommandClasses["Association Group Information"])) {
      const agiAPI = import_API.CCAPI.create(import_core.CommandClasses["Association Group Information"], ctx, endpoint);
      const lifeline = await agiAPI.getGroupInfo(1, true).catch(() => void 0);
      if (lifeline?.profile === import_Types.AssociationGroupInfoProfile["General: Lifeline"]) {
        lifelineGroups.push(1);
      }
    }
  }
  if (lifelineGroups.length === 0) {
    ctx.logNode(node.id, {
      endpoint: endpoint.index,
      message: "No information about Lifeline associations, cannot assign ourselves!",
      level: "warn"
    });
    valueDB.setValue(import_AssociationCC.AssociationCCValues.hasLifeline.endpoint(endpoint.index), false);
    return;
  }
  ctx.logNode(node.id, {
    endpoint: endpoint.index,
    message: `Checking/assigning lifeline groups: ${lifelineGroups.join(", ")}
supports classic associations:       ${!!assocInstance}
supports multi channel associations: ${!!mcInstance}`
  });
  for (const group of lifelineGroups) {
    const groupSupportsMultiChannelAssociation = group <= mcGroupCount;
    const assocConfig = deviceConfig?.getAssociationConfigForEndpoint(endpoint.index, group);
    const mustUseNodeAssociation = !groupSupportsMultiChannelAssociation || !nodeSupportsMultiChannel || assocConfig?.multiChannel === false;
    let mustUseMultiChannelAssociation = false;
    if (groupSupportsMultiChannelAssociation && nodeSupportsMultiChannel) {
      if (assocConfig?.multiChannel === true) {
        mustUseMultiChannelAssociation = true;
      } else if (endpoint.index === 0) {
        const allEndpoints = node.getAllEndpoints();
        if (allEndpoints.length > 1 && allEndpoints.filter((e) => e.index !== endpoint.index).every((e) => !e.supportsCC(import_core.CommandClasses.Association) && !e.supportsCC(import_core.CommandClasses["Multi Channel Association"]))) {
          mustUseMultiChannelAssociation = true;
        }
      }
    }
    ctx.logNode(node.id, {
      endpoint: endpoint.index,
      message: `Configuring lifeline group #${group}:
group supports multi channel:  ${groupSupportsMultiChannelAssociation}
configured strategy:           ${assocConfig?.multiChannel ?? "auto"}
must use node association:     ${mustUseNodeAssociation}
must use endpoint association: ${mustUseMultiChannelAssociation}`
    });
    const isAssignedAsNodeAssociation = /* @__PURE__ */ __name((endpoint2) => {
      if (groupSupportsMultiChannelAssociation && mcInstance) {
        if (
          // Only consider a group if it doesn't share its associations with the root endpoint
          mcInstance.getMaxNodesCached(ctx, endpoint2, group) > 0 && !!mcInstance.getAllDestinationsCached(ctx, endpoint2).get(group)?.some((addr) => addr.nodeId === ownNodeId && addr.endpoint == void 0)
        ) {
          return true;
        }
      }
      if (assocInstance) {
        if (
          // Only consider a group if it doesn't share its associations with the root endpoint
          assocInstance.getMaxNodesCached(ctx, endpoint2, group) > 0 && !!assocInstance.getAllDestinationsCached(ctx, endpoint2).get(group)?.some((addr) => addr.nodeId === ownNodeId)
        ) {
          return true;
        }
      }
      return false;
    }, "isAssignedAsNodeAssociation");
    const isAssignedAsEndpointAssociation = /* @__PURE__ */ __name((endpoint2) => {
      if (mcInstance) {
        if (
          // Only consider a group if it doesn't share its associations with the root endpoint
          mcInstance.getMaxNodesCached(ctx, endpoint2, group) > 0 && mcInstance.getAllDestinationsCached(ctx, endpoint2).get(group)?.some((addr) => addr.nodeId === ownNodeId && addr.endpoint === 0)
        ) {
          return true;
        }
      }
      return false;
    }, "isAssignedAsEndpointAssociation");
    const invalidEndpointAssociations = mcInstance?.getAllDestinationsCached(ctx, endpoint).get(group)?.filter((addr) => addr.nodeId === ownNodeId && addr.endpoint != void 0 && addr.endpoint !== 0) ?? [];
    if (invalidEndpointAssociations.length > 0 && mcAPI.isSupported() && groupSupportsMultiChannelAssociation) {
      ctx.logNode(node.id, {
        endpoint: endpoint.index,
        message: `Found invalid lifeline associations in group #${group}, removing them...`,
        direction: "outbound"
      });
      await mcAPI.removeDestinations({
        groupId: group,
        endpoints: invalidEndpointAssociations
      });
      await mcAPI.getGroup(group);
    }
    let hasLifeline = false;
    if (!mustUseMultiChannelAssociation) {
      if (isAssignedAsNodeAssociation(endpoint)) {
        hasLifeline = true;
        ctx.logNode(node.id, {
          endpoint: endpoint.index,
          message: `Lifeline group #${group} is already assigned with a node association`,
          direction: "none"
        });
      } else if (assocAPI.isSupported() && assocInstance.getMaxNodesCached(ctx, endpoint, group) > 0) {
        ctx.logNode(node.id, {
          endpoint: endpoint.index,
          message: `Assigning lifeline group #${group} with a node association via Association CC...`,
          direction: "outbound"
        });
        if (isAssignedAsEndpointAssociation(endpoint) && mcAPI.isSupported()) {
          await mcAPI.removeDestinations({
            groupId: group,
            endpoints: [{ nodeId: ownNodeId, endpoint: 0 }]
          });
          await mcAPI.getGroup(group);
        }
        await assocAPI.addNodeIds(group, ownNodeId);
        const groupReport = await assocAPI.getGroup(group);
        hasLifeline = !!groupReport?.nodeIds.includes(ownNodeId);
        if (hasLifeline) {
          ctx.logNode(node.id, {
            endpoint: endpoint.index,
            message: `Lifeline group #${group} was assigned with a node association via Association CC`,
            direction: "none"
          });
        } else {
          ctx.logNode(node.id, {
            endpoint: endpoint.index,
            message: `Assigning lifeline group #${group} with a node association via Association CC did not work`,
            direction: "none"
          });
        }
      }
      if (!hasLifeline && mcAPI.isSupported() && mcInstance.getMaxNodesCached(ctx, endpoint, group) > 0) {
        ctx.logNode(node.id, {
          endpoint: endpoint.index,
          message: `Assigning lifeline group #${group} with a node association via Multi Channel Association CC...`,
          direction: "outbound"
        });
        if (isAssignedAsEndpointAssociation(endpoint)) {
          await mcAPI.removeDestinations({
            groupId: group,
            endpoints: [{ nodeId: ownNodeId, endpoint: 0 }]
          });
        }
        await mcAPI.addDestinations({
          groupId: group,
          nodeIds: [ownNodeId]
        });
        const groupReport = await mcAPI.getGroup(group);
        hasLifeline = !!groupReport?.nodeIds.includes(ownNodeId);
        if (hasLifeline) {
          ctx.logNode(node.id, {
            endpoint: endpoint.index,
            message: `Lifeline group #${group} was assigned with a node association via Multi Channel Association CC`,
            direction: "none"
          });
        } else {
          ctx.logNode(node.id, {
            endpoint: endpoint.index,
            message: `Assigning lifeline group #${group} with a node association via Multi Channel Association CC did not work`,
            direction: "none"
          });
        }
      }
    }
    if (!hasLifeline && !mustUseNodeAssociation) {
      if (isAssignedAsEndpointAssociation(endpoint)) {
        hasLifeline = true;
        ctx.logNode(node.id, {
          endpoint: endpoint.index,
          message: `Lifeline group #${group} is already assigned with an endpoint association`,
          direction: "none"
        });
      } else if (mcAPI.isSupported() && mcAPI.version >= 3 && mcInstance.getMaxNodesCached(ctx, endpoint, group) > 0) {
        ctx.logNode(node.id, {
          endpoint: endpoint.index,
          message: `Assigning lifeline group #${group} with a multi channel association...`,
          direction: "outbound"
        });
        if (isAssignedAsNodeAssociation(endpoint)) {
          await mcAPI.removeDestinations({
            groupId: group,
            nodeIds: [ownNodeId]
          });
          if (assocAPI.isSupported()) {
            await assocAPI.removeNodeIds({
              groupId: group,
              nodeIds: [ownNodeId]
            });
            await assocAPI.getGroup(group);
          }
        }
        await mcAPI.addDestinations({
          groupId: group,
          endpoints: [{ nodeId: ownNodeId, endpoint: 0 }]
        });
        const groupReport = await mcAPI.getGroup(group);
        hasLifeline = !!groupReport?.endpoints.some((a) => a.nodeId === ownNodeId && a.endpoint === 0);
        if (hasLifeline) {
          ctx.logNode(node.id, {
            endpoint: endpoint.index,
            message: `Lifeline group #${group} was assigned with a multi channel association`,
            direction: "none"
          });
        } else {
          ctx.logNode(node.id, {
            endpoint: endpoint.index,
            message: `Assigning lifeline group #${group} with a multi channel association did not work`,
            direction: "none"
          });
        }
      }
    }
    if (!hasLifeline && group === 1 && node.supportsCC(import_core.CommandClasses["Z-Wave Plus Info"]) && endpoint.index > 0) {
      const rootAssocConfig = deviceConfig?.getAssociationConfigForEndpoint(0, group);
      const rootMustUseNodeAssociation = !nodeSupportsMultiChannel || rootAssocConfig?.multiChannel === false;
      ctx.logNode(node.id, {
        endpoint: endpoint.index,
        message: `Checking root device for fallback assignment of lifeline group #${group}:
root supports multi channel:  ${nodeSupportsMultiChannel}
configured strategy:           ${rootAssocConfig?.multiChannel ?? "auto"}
must use node association:     ${rootMustUseNodeAssociation}`
      });
      if (!rootMustUseNodeAssociation) {
        if (isAssignedAsEndpointAssociation(node)) {
          hasLifeline = true;
          ctx.logNode(node.id, {
            endpoint: endpoint.index,
            message: `Lifeline group #${group} is already assigned with a multi channel association on the root device`,
            direction: "none"
          });
        } else {
          const rootMCAPI = import_API.CCAPI.create(import_core.CommandClasses["Multi Channel Association"], ctx, node);
          const rootAssocAPI = import_API.CCAPI.create(import_core.CommandClasses.Association, ctx, node);
          if (rootMCAPI.isSupported()) {
            ctx.logNode(node.id, {
              endpoint: endpoint.index,
              message: `Assigning lifeline group #${group} with a multi channel association on the root device...`,
              direction: "outbound"
            });
            if (isAssignedAsNodeAssociation(node)) {
              await rootMCAPI.removeDestinations({
                groupId: group,
                nodeIds: [ownNodeId]
              });
              if (rootAssocAPI.isSupported()) {
                await rootAssocAPI.removeNodeIds({
                  groupId: group,
                  nodeIds: [ownNodeId]
                });
                await rootAssocAPI.getGroup(group);
              }
            }
            await rootMCAPI.addDestinations({
              groupId: group,
              endpoints: [{ nodeId: ownNodeId, endpoint: 0 }]
            });
            const groupReport = await rootMCAPI.getGroup(group);
            hasLifeline = !!groupReport?.endpoints.some((a) => a.nodeId === ownNodeId && a.endpoint === 0);
          }
        }
      }
    }
    if (!hasLifeline) {
      ctx.logNode(node.id, {
        endpoint: endpoint.index,
        message: `All attempts to assign lifeline group #${group} failed, skipping...`,
        direction: "none",
        level: "warn"
      });
    }
  }
  valueDB.setValue(import_AssociationCC.AssociationCCValues.hasLifeline.endpoint(endpoint.index), true);
}
__name(configureLifelineAssociations, "configureLifelineAssociations");
async function assignLifelineIssueingCommand(ctx, endpoint, ccId, ccCommand) {
  const node = ctx.getNodeOrThrow(endpoint.nodeId);
  if (node.supportsCC(import_core.CommandClasses["Association Group Information"]) && (node.supportsCC(import_core.CommandClasses.Association) || node.supportsCC(import_core.CommandClasses["Multi Channel Association"]))) {
    const groupsIssueingNotifications = import_AssociationGroupInfoCC.AssociationGroupInfoCC.findGroupsForIssuedCommand(ctx, node, ccId, ccCommand);
    if (groupsIssueingNotifications.length > 0) {
      const groupId = groupsIssueingNotifications[0];
      const existingAssociations = getAssociations(ctx, node).get(groupId) ?? [];
      if (!existingAssociations.some((a) => a.nodeId === ctx.ownNodeId)) {
        ctx.logNode(node.id, {
          endpoint: endpoint.index,
          message: `Configuring associations to receive ${(0, import_core.getCCName)(ccId)} commands...`,
          direction: "outbound"
        });
        await addAssociations(ctx, node, groupId, [
          { nodeId: ctx.ownNodeId }
        ]);
      }
    }
  }
}
__name(assignLifelineIssueingCommand, "assignLifelineIssueingCommand");
function doesAnyLifelineSendActuatorOrSensorReports(ctx, node) {
  if (!node.supportsCC(import_core.CommandClasses.Association) && !node.supportsCC(import_core.CommandClasses["Multi Channel Association"])) {
    return false;
  }
  if (!node.supportsCC(import_core.CommandClasses["Association Group Information"])) {
    return import_core.NOT_KNOWN;
  }
  const lifelineGroupIds = getLifelineGroupIds(ctx, node);
  if (lifelineGroupIds.some((id) => import_AssociationGroupInfoCC.AssociationGroupInfoCC.getGroupProfileCached(ctx, node, id) === import_Types.AssociationGroupInfoProfile["General: Lifeline"])) {
    return true;
  }
  for (const groupId of lifelineGroupIds) {
    const issuedCommands = import_AssociationGroupInfoCC.AssociationGroupInfoCC.getIssuedCommandsCached(ctx, node, groupId);
    if (!issuedCommands)
      continue;
    const commands = [...issuedCommands.keys()];
    if (commands.some((c) => (0, import_core.isActuatorCC)(c) || (0, import_core.isSensorCC)(c))) {
      return true;
    }
  }
  return false;
}
__name(doesAnyLifelineSendActuatorOrSensorReports, "doesAnyLifelineSendActuatorOrSensorReports");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  addAssociations,
  assignLifelineIssueingCommand,
  checkAssociation,
  configureLifelineAssociations,
  doesAnyLifelineSendActuatorOrSensorReports,
  getAllAssociationGroups,
  getAllAssociations,
  getAssociationGroups,
  getAssociations,
  getLifelineGroupIds,
  removeAssociations
});
//# sourceMappingURL=utils.js.map
