"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 EventReadResponse_exports = {};
__export(EventReadResponse_exports, {
  EventReadResponse: () => EventReadResponse
});
module.exports = __toCommonJS(EventReadResponse_exports);
var import_AccessControl = require("#action/server/AccessControl.js");
var import_DataResponse = require("#action/server/DataResponse.js");
var import_general = require("#general");
var import_model = require("#model");
var import_types = require("#types");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("EventReadResponse");
class EventReadResponse extends import_DataResponse.DataResponse {
  // Normalized Event Filter to just our node-id
  #eventMinVersion;
  // The Fabric filtering is done when we read the data from OccurrenceManager, so we can determine the parameter once
  #filteredForFabricIndex;
  // The following state updates as data producers execute.  This serves both to convey state between functions and as
  // a cache between producers that touch the same endpoint and/or cluster
  #currentEndpoint;
  #currentCluster;
  // Collected allowed and existing event paths to consider when reading events
  #allowedEventPaths = /* @__PURE__ */ new Map();
  // Count how many events status (on error) and event values (on success) we have emitted
  #statusCount = 0;
  #valueCount = 0;
  constructor(node, session) {
    super(node, session);
  }
  async *process({
    eventFilters,
    eventRequests,
    isFabricFiltered
  }) {
    const nodeId = !this.session.fabric ? import_types.NodeId.UNSPECIFIED_NODE_ID : this.nodeId;
    if (eventFilters !== void 0) {
      for (const { nodeId: filterNodeId, eventMin } of eventFilters) {
        if (filterNodeId === void 0 || filterNodeId === nodeId) {
          this.#eventMinVersion = (0, import_types.EventNumber)(eventMin);
          break;
        }
      }
    }
    if (isFabricFiltered) {
      this.#filteredForFabricIndex = this.session.fabric ?? import_types.FabricIndex.NO_FABRIC;
    }
    for (const path of eventRequests) {
      if (path.endpointId === void 0 || path.clusterId === void 0 || path.eventId === void 0) {
        this.#addWildcard(path);
      } else {
        const status = this.#addConcrete(path);
        if (status !== void 0) {
          yield [status];
          this.#statusCount++;
        }
      }
    }
    for await (const data of this.#readAllowedEvents()) {
      yield [data];
      this.#valueCount++;
    }
  }
  get counts() {
    return {
      status: this.#statusCount,
      success: this.#valueCount,
      existent: this.#allowedEventPaths.size
    };
  }
  /** Guarded accessor for this.#currentEndpoint.  This should never be undefined */
  get #guardedCurrentEndpoint() {
    if (this.#currentEndpoint === void 0) {
      throw new import_general.InternalError("currentEndpoint is not set. Should never happen");
    }
    return this.#currentEndpoint;
  }
  /** Guarded accessor for this.#currentCluster.  This should never be undefined */
  get #guardedCurrentCluster() {
    if (this.#currentCluster === void 0) {
      throw new import_general.InternalError("currentCluster is not set. Should never happen");
    }
    return this.#currentCluster;
  }
  /**
   * Validate a wildcard path and update the internal state.
   */
  #addWildcard(path) {
    const { nodeId, clusterId, endpointId, eventId } = path;
    if (clusterId === void 0 && eventId !== void 0) {
      throw new import_types.StatusResponseError("Illegal read request with wildcard cluster ID", import_types.StatusCode.InvalidAction);
    }
    if (nodeId !== void 0 && nodeId !== this.nodeId) {
      return;
    }
    if (endpointId === void 0) {
      for (const endpoint2 of this.node) {
        this.#addEndpointForWildcard(endpoint2, path);
      }
      return;
    }
    const endpoint = this.node[endpointId];
    if (endpoint) {
      return this.#addEndpointForWildcard(endpoint, path);
    }
  }
  /**
   * Validate a concrete path and update the internal state.
   */
  #addConcrete(path) {
    const { nodeId, endpointId, clusterId, eventId } = path;
    if (nodeId !== void 0 && this.nodeId !== nodeId) {
      return this.#asStatus(path, import_types.Status.UnsupportedNode);
    }
    const endpoint = this.node[endpointId];
    const cluster = endpoint?.[clusterId];
    const event = cluster?.type.events[eventId];
    let limits;
    if (event === void 0) {
      const modelEvent = this.node.matter.member(path.clusterId, [import_model.ElementTag.Cluster])?.member(path.eventId, [import_model.ElementTag.Event]);
      if (modelEvent) {
        limits = (0, import_AccessControl.AccessControl)(modelEvent).limits;
      } else {
        limits = import_DataResponse.FallbackLimits;
      }
    } else {
      limits = event.limits;
    }
    if ((0, import_AccessControl.hasRemoteActor)(this.session)) {
      const location = {
        ...cluster?.location ?? {
          path: import_model.DataModelPath.none,
          endpoint: endpointId,
          cluster: clusterId
        },
        owningFabric: this.session.fabric
      };
      const permission = this.session.authorityAt(limits.readLevel, location);
      switch (permission) {
        case import_AccessControl.AccessControl.Authority.Granted:
          break;
        case import_AccessControl.AccessControl.Authority.Unauthorized:
          return this.#asStatus(path, import_types.Status.UnsupportedAccess);
        case import_AccessControl.AccessControl.Authority.Restricted:
          return this.#asStatus(path, import_types.Status.AccessRestricted);
        default:
          throw new import_general.InternalError(`Unsupported authorization state ${permission}`);
      }
    }
    if (endpoint === void 0) {
      return this.#asStatus(path, import_types.Status.UnsupportedEndpoint);
    }
    if (cluster === void 0) {
      return this.#asStatus(path, import_types.Status.UnsupportedCluster);
    }
    if (event === void 0 || !cluster.type.events[event.id]) {
      return this.#asStatus(path, import_types.Status.UnsupportedEvent);
    }
    if (this.#currentEndpoint !== endpoint) {
      this.#currentEndpoint = endpoint;
      this.#currentCluster = cluster;
    } else if (this.#currentCluster !== cluster) {
      this.#currentCluster = cluster;
    }
    this.#registerEventPath(path);
  }
  /**
   * Starts new chunk or adds to current chunk all values from {@link endpoint} selected by {@link path}.
   *
   * Emits previous chunk if it exists and was not for this endpoint.  This means that our chunk size is one endpoint
   * worth of data, except for the initial error chunk if there are path errors.
   *
   * TODO - skip endpoints for which subject is unauthorized
   */
  #addEndpointForWildcard(endpoint, path) {
    if (this.#currentEndpoint !== endpoint) {
      this.#currentEndpoint = endpoint;
      this.#currentCluster = void 0;
    }
    const { clusterId } = path;
    if (clusterId === void 0) {
      for (const cluster of endpoint) {
        this.#addClusterForWildcard(cluster, { ...path, endpointId: endpoint.id });
      }
    } else {
      const cluster = endpoint[clusterId];
      if (cluster !== void 0) {
        this.#addClusterForWildcard(cluster, { ...path, endpointId: endpoint.id });
      }
    }
  }
  /**
   * Read values from a specific {@link cluster} for a wildcard path.
   *
   * Depends on state initialized by {@link #addEndpointForWildcard}.
   *
   * TODO - skip endpoints for which subject is unauthorized as optimization
   */
  #addClusterForWildcard(cluster, path) {
    if (this.#currentCluster !== cluster) {
      this.#currentCluster = cluster;
    }
    const { eventId } = path;
    if (eventId === void 0) {
      for (const event of cluster.type.events) {
        this.#addEventForWildcard(event, {
          ...path,
          clusterId: cluster.type.id
        });
      }
    } else {
      const event = cluster.type.events[eventId];
      if (event !== void 0) {
        this.#addEventForWildcard(event, {
          ...path,
          clusterId: cluster.type.id
        });
      }
    }
  }
  /**
   * Read values from a specific {@link event} for a wildcard path.
   *
   * Depends on state initialized by {@link #addClusterForWildcard}.
   */
  #addEventForWildcard(event, path) {
    if (!this.#guardedCurrentCluster.type.events[event.id]) {
      return;
    }
    if ((0, import_AccessControl.hasRemoteActor)(this.session) && this.session.authorityAt(event.limits.readLevel, this.#guardedCurrentCluster.location) !== import_AccessControl.AccessControl.Authority.Granted) {
      return;
    }
    this.#registerEventPath({ ...path, eventId: event.id });
  }
  #createEventKey(path) {
    const { endpointId, clusterId, eventId } = path;
    return `${endpointId}:${clusterId}:${eventId}`;
  }
  #registerEventPath(path) {
    const { eventId } = path;
    this.#allowedEventPaths.set(
      this.#createEventKey({
        ...path,
        endpointId: this.#guardedCurrentEndpoint.id,
        clusterId: this.#guardedCurrentCluster.type.id,
        eventId
      }),
      this.#guardedCurrentCluster.type.events[eventId].tlv
    );
  }
  async *#readAllowedEvents() {
    for await (const event of this.node.eventHandler.get(this.#eventMinVersion)) {
      const tlv = this.#allowedEventPaths.get(this.#createEventKey(event));
      if (tlv === void 0) {
        continue;
      }
      if (this.#filteredForFabricIndex !== void 0 && tlv instanceof import_types.ObjectSchema && tlv.isFabricScoped) {
        const { payload } = event;
        if (!(0, import_general.isObject)(payload)) {
          throw new import_general.InternalError("Fabric sensitive event payload is not an object. Should never happen.");
        }
        const { fabricIndex } = payload;
        if (fabricIndex !== void 0 && fabricIndex !== this.#filteredForFabricIndex) {
          continue;
        }
      }
      yield this.#asValue(event, tlv);
    }
  }
  /**
   * Add a status value.
   */
  #asStatus(path, status) {
    logger.debug(`Error reading event ${this.node.inspectPath(path)}: Status=${import_types.StatusCode[status]}(${status})`);
    const report = {
      kind: "event-status",
      path,
      status
    };
    return report;
  }
  /**
   * Add event values as separate entries to the result.
   */
  #asValue(event, tlv) {
    const { number, epochTimestamp: timestamp, priority, payload: value, endpointId, clusterId, eventId } = event;
    const report = {
      kind: "event-value",
      path: { endpointId, clusterId, eventId },
      number,
      priority,
      timestamp,
      value,
      tlv
    };
    return report;
  }
}
//# sourceMappingURL=EventReadResponse.js.map
