"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
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 ProtocolService_exports = {};
__export(ProtocolService_exports, {
  ProtocolService: () => ProtocolService
});
module.exports = __toCommonJS(ProtocolService_exports);
var import_cluster_behavior_utils = require("#behavior/cluster/cluster-behavior-utils.js");
var import_LocalActorContext = require("#behavior/context/server/LocalActorContext.js");
var import_SupervisionConfig = require("#behavior/supervision/SupervisionConfig.js");
var import_general = require("@matter/general");
var import_model = require("@matter/model");
var import_protocol = require("@matter/protocol");
var import_types = require("@matter/types");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("ProtocolService");
class ProtocolService {
  #state;
  constructor(node) {
    this.#state = new NodeState(node);
  }
  /**
   * Invoked by a backing when initialized.
   */
  addCluster(backing) {
    const { schema } = backing.type;
    if (schema?.tag !== import_model.ElementTag.Cluster || schema.id === void 0) {
      return;
    }
    if ((0, import_cluster_behavior_utils.isClientBehavior)(backing.type)) {
      return;
    }
    this.#state.stateFor(backing.endpoint).addCluster(backing);
  }
  /**
   * Invoked by a backing when closed.
   */
  deleteCluster(backing) {
    if (this.#state.hasEndpoint(backing.endpoint)) {
      this.#state.stateFor(backing.endpoint).deleteCluster(backing);
    }
  }
  /**
   * Invoked by a backing when there is a state change.
   *
   * This optimized path allows us to broadcast state changes without registering observers for every change.
   */
  handleChange(backing, props) {
    const clusterId = backing.type.schema.id;
    if (clusterId === void 0) {
      return;
    }
    const namesToIds = backing.type.supervisor.propertyNamesAndIds;
    const attrs = props.map((name) => namesToIds.get(name)).filter((id) => id !== void 0);
    this.protocol.attrsChanged.emit(backing.endpoint.number, clusterId, attrs, backing.datasource.version);
  }
  /**
   * The {@link NodeProtocol}.
   */
  get protocol() {
    return this.#state.protocol;
  }
}
const WildcardPathFlags = {
  skipRootNode: 1 << import_types.WildcardPathFlags.wildcardSkipRootNode.offset,
  skipGlobalAttributes: 1 << import_types.WildcardPathFlags.wildcardSkipGlobalAttributes.offset,
  skipAttributeList: 1 << import_types.WildcardPathFlags.wildcardSkipAttributeList.offset,
  skipCommandLists: 1 << import_types.WildcardPathFlags.wildcardSkipCommandLists.offset,
  skipCustomElements: 1 << import_types.WildcardPathFlags.wildcardSkipCustomElements.offset,
  skipFixedAttributes: 1 << import_types.WildcardPathFlags.wildcardSkipFixedAttributes.offset,
  skipChangesOmittedAttributes: 1 << import_types.WildcardPathFlags.wildcardSkipChangesOmittedAttributes.offset,
  skipDiagnosticsClusters: 1 << import_types.WildcardPathFlags.wildcardSkipDiagnosticsClusters.offset
};
class NodeState {
  protocol;
  #endpoints = /* @__PURE__ */ new Set();
  #endpointStates = {};
  constructor(node) {
    let fabrics;
    let eventHandler;
    this.protocol = {
      matter: import_model.Matter,
      nodeIdFor(index) {
        if (!fabrics) {
          fabrics = node.env.get(import_protocol.FabricManager);
        }
        return fabrics.maybeFor(index)?.nodeId;
      },
      get eventHandler() {
        if (eventHandler === void 0) {
          eventHandler = node.env.get(import_protocol.OccurrenceManager);
        }
        return eventHandler;
      },
      [Symbol.iterator]: this.#endpoints[Symbol.iterator].bind(this.#endpoints),
      attrsChanged: new import_general.Observable(),
      toString() {
        return `node-proto#${node.id}`;
      },
      inspect() {
        return this.toString();
      },
      inspectPath(path) {
        return resolvePathForNode(this, path);
      }
    };
  }
  stateFor(endpoint) {
    const { number } = endpoint;
    let state = this.#endpointStates[number];
    if (state !== void 0) {
      return state;
    }
    state = new EndpointState(this, endpoint);
    this.protocol[number] = state.protocol;
    this.#endpoints.add(state.protocol);
    this.#endpointStates[number] = state;
    return state;
  }
  hasEndpoint(endpoint) {
    return endpoint.number in this.#endpointStates;
  }
  deleteEndpoint(endpoint) {
    delete this.protocol[endpoint.id];
    this.#endpoints.delete(endpoint);
    delete this.#endpointStates[endpoint.id];
  }
}
class EndpointState {
  protocol;
  #node;
  #activeClusters = /* @__PURE__ */ new Set();
  #clusters = /* @__PURE__ */ new Set();
  constructor(node, endpoint) {
    this.#node = node;
    const number = endpoint.number;
    this.protocol = {
      id: number,
      wildcardPathFlags: number === 0 ? WildcardPathFlags.skipRootNode : 0,
      path: endpoint.path,
      name: endpoint.type.name,
      deviceTypes: [],
      toString() {
        return `endpoint-proto#${this.id}`;
      },
      inspect() {
        return this.toString();
      },
      [Symbol.iterator]: this.#clusters[Symbol.iterator].bind(this.#clusters)
    };
  }
  addCluster(backing) {
    const type = clusterTypeProtocolOf(backing);
    if (!type) {
      return;
    }
    const cluster = new ClusterState(type, backing);
    if (backing.type.id === "descriptor") {
      this.#updateDeviceTypes(backing.endpoint.state.descriptor.deviceTypeList);
      backing.events.deviceTypeList$Changed.on(
        this.#updateDeviceTypes.bind(this)
      );
    }
    this.protocol[cluster.type.id] = cluster;
    this.#activeClusters.add(cluster.type.id);
    this.#clusters.add(cluster);
    const attrs = [...cluster.type.attributes].filter((attr) => attr.limits.readable && !attr.changesOmitted).map((attr) => attr.id);
    if (attrs.length) {
      this.#node.protocol.attrsChanged.emit(this.protocol.id, cluster.type.id, attrs, cluster.version);
    }
  }
  deleteCluster(backing) {
    const { schema } = backing.type;
    if (schema === void 0) {
      return;
    }
    const { tag, id } = schema;
    if (tag !== import_model.ElementTag.Cluster || id === void 0) {
      return;
    }
    const protocol = this.protocol[id];
    if (protocol) {
      this.#clusters.delete(protocol);
      delete this.protocol[id];
    }
    this.#activeClusters.delete(id);
    if (!this.#activeClusters.size) {
      this.#node.deleteEndpoint(this.protocol);
    }
  }
  #updateDeviceTypes(deviceTypeList) {
    this.protocol.deviceTypes = deviceTypeList.map((dt) => dt.deviceType);
  }
}
class ClusterState {
  type;
  #datasource;
  #endpointId;
  commands = {};
  skipCommandValidation;
  constructor(type, backing) {
    this.type = type;
    this.#datasource = backing.datasource;
    this.#endpointId = backing.endpoint.number;
    for (const cmd of type.commands) {
      const commandModels = this.#commandModels(backing, cmd);
      this.commands[cmd.id] = (args, session) => invokeCommand(backing, cmd, args, session, commandModels);
    }
    for (const cmd of type.commands) {
      if ((0, import_SupervisionConfig.maybeConfigOfConstructor)(backing.type.prototype, (0, import_general.camelize)(cmd.name))) {
        this.skipCommandValidation = true;
        break;
      }
    }
  }
  #commandModels(backing, cmd) {
    const { schema } = backing.type;
    if (!(schema instanceof import_model.ClusterModel)) {
      return;
    }
    const requestModel = schema.commands.find((c) => c.id === cmd.id && c.isRequest);
    if (!requestModel) {
      logger.warn(`No schema for command ${cmd.name} (${cmd.id})`);
      return;
    }
    return {
      supervisor: backing.type.supervisor,
      requestModel,
      responseModel: requestModel.responseModel
    };
  }
  get version() {
    return this.#datasource.version;
  }
  get location() {
    return this.#datasource.location;
  }
  readState(session) {
    const { interactionComplete: _ic, ...readOnly } = import_LocalActorContext.LocalActorContext.ReadOnly;
    const supervisorSession = {
      ...readOnly,
      ...session
    };
    return this.#datasource.reference(supervisorSession);
  }
  async openForWrite(session) {
    if (session.transaction === void 0) {
      throw new import_general.ImplementationError("Cluster protocol must be opened with a supervisor session");
    }
    await session.transaction.addResources(this.#datasource);
    await session.transaction.begin();
    return this.#datasource.reference(session);
  }
  toString() {
    return `cluster-proto#${this.#endpointId}:${this.type.id}`;
  }
  inspect() {
    return this.toString();
  }
}
const behaviorCache = /* @__PURE__ */ new WeakMap();
function clusterTypeProtocolOf(backing) {
  const behavior = backing.type;
  const { cluster, schema } = behavior;
  if (cluster === void 0 || schema?.id === void 0) {
    return;
  }
  const supportedElements = backing.endpoint.behaviors.elementsOf(behavior);
  const nonMandatorySupportedAttributes = /* @__PURE__ */ new Set();
  const nonMandatorySupportedEvents = /* @__PURE__ */ new Set();
  const nonMandatorySupportedCommands = /* @__PURE__ */ new Set();
  let wildcardPathFlags = schema.effectiveQuality.diagnostics ? WildcardPathFlags.skipDiagnosticsClusters : 0;
  if (schema.id & 4294901760) {
    wildcardPathFlags |= WildcardPathFlags.skipCustomElements;
  }
  const attrList = Array();
  const attributes = {
    [Symbol.iterator]: attrList[Symbol.iterator].bind(attrList)
  };
  const eventList = Array();
  const events = {
    [Symbol.iterator]: eventList[Symbol.iterator].bind(eventList)
  };
  const commandList = Array();
  const commands = {
    [Symbol.iterator]: commandList[Symbol.iterator].bind(commandList)
  };
  for (const member of behavior.supervisor.membersOf(schema, {
    tags: [import_model.ElementTag.Attribute, import_model.ElementTag.Event, import_model.ElementTag.Command]
  })) {
    const { id, tag, effectiveQuality: quality } = member;
    if (id === void 0) {
      continue;
    }
    if (member.isDeprecated || member.isDisallowed) {
      continue;
    }
    const name = member.propertyName;
    switch (tag) {
      case "attribute": {
        if (!member.effectiveConformance.isMandatory && !supportedElements.attributes.has(name)) {
          continue;
        }
        const tlv = (0, import_types.TlvOfModel)(member);
        let wildcardPathFlags2;
        switch (id) {
          case import_model.GeneratedCommandList.id:
          case import_model.AcceptedCommandList.id:
            wildcardPathFlags2 = WildcardPathFlags.skipGlobalAttributes | WildcardPathFlags.skipCommandLists;
            break;
          case import_model.AttributeList.id:
            wildcardPathFlags2 = WildcardPathFlags.skipGlobalAttributes | WildcardPathFlags.skipAttributeList;
            break;
          default:
            wildcardPathFlags2 = 0;
            break;
        }
        if (id & 4294901760) {
          wildcardPathFlags2 |= WildcardPathFlags.skipGlobalAttributes;
        }
        if (quality.fixed) {
          wildcardPathFlags2 |= WildcardPathFlags.skipFixedAttributes;
        }
        if (quality.changesOmitted) {
          wildcardPathFlags2 |= WildcardPathFlags.skipChangesOmittedAttributes;
        }
        const {
          access: { limits }
        } = behavior.supervisor.get(member);
        const {
          changesOmitted,
          effectiveQuality: { quieter }
        } = member;
        const attr = {
          id,
          tlv,
          wildcardPathFlags: wildcardPathFlags2,
          limits,
          name
        };
        if (changesOmitted) {
          attr.changesOmitted = true;
        }
        if (quieter) {
          attr.quieter = true;
        }
        attrList.push(attr);
        attributes[id] = attr;
        if (!member.effectiveConformance.isMandatory) {
          nonMandatorySupportedAttributes.add(id);
        }
        break;
      }
      case "event": {
        if (!member.effectiveConformance.isMandatory && !supportedElements.events.has(name)) {
          continue;
        }
        const tlv = (0, import_types.TlvOfModel)(member);
        const {
          access: { limits }
        } = behavior.supervisor.get(member);
        const event = { id, tlv, limits, name };
        eventList.push(event);
        events[id] = event;
        if (!member.effectiveConformance.isMandatory) {
          nonMandatorySupportedEvents.add(id);
        }
        break;
      }
      case "command": {
        if (id === import_types.CommandId.NONE || !member.effectiveConformance.isMandatory && !supportedElements.commands.has(name) || !member.isRequest) {
          continue;
        }
        const requestTlv = (0, import_types.TlvOfModel)(member);
        const responseModel = member.responseModel;
        const responseTlv = responseModel ? (0, import_types.TlvOfModel)(responseModel) : import_types.TlvVoid;
        const responseId = responseModel?.id ?? id;
        const {
          access: { limits }
        } = behavior.supervisor.get(member);
        const command = { id, responseId, requestTlv, responseTlv, limits, name };
        commandList.push(command);
        commands[id] = command;
        if (!member.effectiveConformance.isMandatory) {
          nonMandatorySupportedCommands.add(id);
        }
        break;
      }
    }
  }
  const elementsCacheKey = `a:${[...nonMandatorySupportedAttributes.values()].sort().join(",")},e:${[...nonMandatorySupportedEvents.values()].sort().join(",")},c:${[...nonMandatorySupportedCommands.values()].sort().join(",")}`;
  const existingCache = behaviorCache.get(behavior)?.get(elementsCacheKey);
  if (existingCache) {
    return existingCache;
  }
  const descriptor = {
    id: schema.id,
    name: schema.name,
    attributes,
    events,
    commands,
    wildcardPathFlags
  };
  const elementCache = behaviorCache.get(behavior) ?? /* @__PURE__ */ new Map();
  elementCache.set(elementsCacheKey, descriptor);
  behaviorCache.set(behavior, elementCache);
  return descriptor;
}
function validateCommandPayload(data, model, supervisor, session, outerResolve, config) {
  if (data === void 0) {
    return;
  }
  const validate = supervisor.get(model).validate;
  if (!validate) {
    return;
  }
  validate(data, session, {
    path: new import_model.DataModelPath(model.path),
    outerResolve,
    config
  });
}
function invokeCommand(backing, command, request, session, commandModels) {
  if (session.transaction === void 0) {
    throw new import_general.ImplementationError("Cluster protocol must be opened with a supervisor session");
  }
  let requestDiagnostic;
  if ((0, import_general.isObject)(request)) {
    requestDiagnostic = import_general.Diagnostic.dict(request);
  } else if (request !== void 0) {
    requestDiagnostic = request;
  } else {
    requestDiagnostic = import_general.Diagnostic.weak("(no payload)");
  }
  const { path, endpoint } = backing;
  const context = session;
  logger.info(
    "Invoke",
    import_protocol.Mark.INBOUND,
    import_general.Diagnostic.strong(path.at((0, import_general.camelize)(command.name))),
    session.transaction.via,
    requestDiagnostic
  );
  const commandInputConfig = (0, import_SupervisionConfig.maybeConfigOfConstructor)(backing.type.prototype, (0, import_general.camelize)(command.name));
  const onValidationError = commandInputConfig?.supervision?.onValidationError;
  if (commandModels) {
    try {
      validateCommandPayload(
        request,
        commandModels.requestModel,
        commandModels.supervisor,
        session,
        void 0,
        commandInputConfig
      );
    } catch (e) {
      if (onValidationError) {
        onValidationError((0, import_general.asError)(e));
      } else {
        logger.warn("Request validation error for", command.name, import_general.Diagnostic.errorMessage((0, import_general.asError)(e)));
      }
    }
  }
  const outerResolve = commandModels && request ? (name2) => name2 === (0, import_general.camelize)(commandModels.requestModel.name) ? request : void 0 : void 0;
  const agent = endpoint.agentFor(context);
  const behavior = agent.get(backing.type);
  let isAsync = false;
  let activity;
  let result;
  const { name } = command;
  try {
    activity = (0, import_protocol.hasRemoteActor)(context) ? context.activity?.frame(`invoke ${name}`) : void 0;
    const invoke = behavior[(0, import_general.camelize)(name)].bind(
      behavior
    );
    if (behavior.constructor.lockOnInvoke) {
      const tx = session.transaction;
      if (import_general.Transaction.Resource.isLocked(behavior)) {
        result = (async function invokeAsync() {
          await tx.addResources(behavior);
          await tx.begin();
          return invoke(request);
        })();
      } else {
        tx.addResourcesSync(behavior);
        tx.beginSync();
        result = invoke(request);
      }
    } else {
      result = invoke(request);
    }
    if (import_general.MaybePromise.is(result)) {
      isAsync = true;
      result = Promise.resolve(result).then((result2) => {
        if (commandModels?.responseModel) {
          try {
            validateCommandPayload(
              result2,
              commandModels.responseModel,
              commandModels.supervisor,
              session,
              outerResolve
            );
          } catch (e) {
            const error = new import_types.StatusResponse.FailureError(
              `Response validation failed for ${command.name}: ${(0, import_general.asError)(e).message}`
            );
            error.cause = e;
            throw error;
          }
        }
        if ((0, import_general.isObject)(result2)) {
          logger.info(
            "Invoke",
            import_protocol.Mark.OUTBOUND,
            import_general.Diagnostic.strong(path.at((0, import_general.camelize)(command.name))),
            session.transaction.via,
            import_general.Diagnostic.dict(result2)
          );
        }
        return result2;
      }).finally(() => activity?.[Symbol.dispose]());
    } else {
      if (commandModels?.responseModel) {
        try {
          validateCommandPayload(
            result,
            commandModels.responseModel,
            commandModels.supervisor,
            session,
            outerResolve
          );
        } catch (e) {
          const error = new import_types.StatusResponse.FailureError(
            `Response validation failed for ${command.name}: ${(0, import_general.asError)(e).message}`
          );
          error.cause = e;
          throw error;
        }
      }
      if ((0, import_general.isObject)(result)) {
        logger.info(
          "Invoke",
          import_protocol.Mark.OUTBOUND,
          import_general.Diagnostic.strong(path.at((0, import_general.camelize)(command.name))),
          session.transaction.via,
          import_general.Diagnostic.dict(result)
        );
      }
    }
  } finally {
    if (!isAsync) {
      activity?.[Symbol.dispose]();
    }
  }
  return result;
}
function resolvePathForNode(node, path) {
  const { endpointId, clusterId } = path;
  const endpoint = endpointId !== void 0 ? node[endpointId] : void 0;
  const cluster = endpoint && clusterId !== void 0 ? endpoint[clusterId] : void 0;
  let dmPath = new import_model.DataModelPath(endpointId ?? "*", "endpoint");
  dmPath = dmPath.at(nameOf(cluster?.type.name, clusterId), "cluster");
  if ("attributeId" in path) {
    const attribute = cluster && path.attributeId !== void 0 ? cluster.type.attributes[path.attributeId] : void 0;
    dmPath = dmPath.at("state").at(nameOf(attribute?.name, path.attributeId));
    if (path.listIndex === null) {
      dmPath = dmPath.at("[ADD]", "marker");
    }
    return dmPath;
  }
  if ("eventId" in path) {
    const event = cluster && path.eventId !== void 0 ? cluster.type.events[path.eventId] : void 0;
    dmPath = dmPath.at("events").at(nameOf(event?.name, path.eventId));
    if ("isUrgent" in path && path.isUrgent) {
      dmPath = dmPath.at("!", "marker");
    }
    return dmPath;
  }
  if ("commandId" in path) {
    const command = cluster && path.commandId !== void 0 ? cluster.type.commands[path.commandId] : void 0;
    return dmPath.at(nameOf(command?.name, path.commandId));
  }
  return dmPath.at("*", "element");
  function nameOf(name, id) {
    if (name !== void 0) {
      return (0, import_general.camelize)(name);
    }
    if (id === void 0) {
      return "*";
    }
    return `0x${id.toString(16)}`;
  }
}
//# sourceMappingURL=ProtocolService.js.map
