var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
var __using = (stack, value, async) => {
  if (value != null) {
    if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
    var dispose, inner;
    if (async) dispose = value[__knownSymbol("asyncDispose")];
    if (dispose === void 0) {
      dispose = value[__knownSymbol("dispose")];
      if (async) inner = dispose;
    }
    if (typeof dispose !== "function") __typeError("Object not disposable");
    if (inner) dispose = function() {
      try {
        inner.call(this);
      } catch (e) {
        return Promise.reject(e);
      }
    };
    stack.push([async, dispose, value]);
  } else if (async) {
    stack.push([async]);
  }
  return value;
};
var __callDispose = (stack, error, hasError) => {
  var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
    return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
  };
  var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
  var next = (it) => {
    while (it = stack.pop()) {
      try {
        var result = it[1] && it[1].call(it[2]);
        if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
      } catch (e) {
        fail(e);
      }
    }
    if (hasError) throw error;
  };
  return next();
};
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { AccessControl, hasLocalActor, hasRemoteActor } from "#action/server/AccessControl.js";
import { DataResponse, FallbackLimits, WildcardPathFlagsCodec } from "#action/server/DataResponse.js";
import { InternalError, Logger, serialize } from "#general";
import { DataModelPath, ElementTag } from "#model";
import {
  GlobalAttributes,
  NodeId,
  Status,
  StatusCode,
  StatusResponseError
} from "#types";
const logger = Logger.get("AttributeReadResponse");
const GlobalAttrIds = new Set(Object.values(GlobalAttributes({})).map((attr) => attr.id));
class AttributeReadResponse extends DataResponse {
  #versions;
  // Each input AttributePathIB that does not have an error installs a producer.  Producers run after validation and
  // generate actual attribute data
  #dataProducers;
  // The initial "chunk" may be a list of errors.  As producers execute it is a set of records associated with the
  // most recently touched endpoint.  When the endpoint changes the previous chunk emits
  #chunk;
  // 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;
  #currentState;
  #wildcardPathFlags = 0;
  // Count how many attribute status (on error) and attribute values (on success) we have emitted
  #statusCount = 0;
  #valueCount = 0;
  #filteredCount = 0;
  constructor(node, session) {
    super(node, session);
  }
  *process({ dataVersionFilters, attributeRequests }) {
    var _stack = [];
    try {
      const _reading = __using(_stack, this.join("reading attributes"));
      const nodeId = hasLocalActor(this.session) ? NodeId.UNSPECIFIED_NODE_ID : this.nodeId;
      if (dataVersionFilters?.length) {
        this.#versions = {};
        for (const {
          path: { nodeId: filterNodeId, endpointId, clusterId },
          dataVersion
        } of dataVersionFilters) {
          if (filterNodeId !== void 0 && filterNodeId !== nodeId) {
            continue;
          }
          if (typeof endpointId !== "number") {
            continue;
          }
          (this.#versions[endpointId] ?? (this.#versions[endpointId] = {}))[clusterId] = dataVersion;
        }
      }
      for (const path of attributeRequests) {
        if (path.endpointId === void 0 || path.clusterId === void 0 || path.attributeId === void 0) {
          this.addWildcard(path);
        } else {
          this.addConcrete(path);
        }
      }
      if (this.#dataProducers) {
        for (const producer of this.#dataProducers) {
          yield* producer.apply(this);
        }
      }
      if (this.#chunk !== void 0) {
        yield this.#chunk;
      }
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  /** Guarded accessor for this.#currentEndpoint.  This should never be undefined */
  get #guardedCurrentEndpoint() {
    if (this.#currentEndpoint === void 0) {
      throw new 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 InternalError("currentCluster is not set. Should never happen");
    }
    return this.#currentCluster;
  }
  get counts() {
    return {
      status: this.#statusCount,
      success: this.#valueCount,
      existent: this.#valueCount + this.#filteredCount
    };
  }
  /**
   * Validate a wildcard path and update internal state.
   */
  addWildcard(path) {
    const { nodeId, endpointId, clusterId, attributeId, wildcardPathFlags } = path;
    if (clusterId === void 0 && attributeId !== void 0 && !GlobalAttrIds.has(attributeId)) {
      throw new StatusResponseError(
        `Illegal read of wildcard cluster with non-global attribute #${attributeId}`,
        Status.InvalidAction
      );
    }
    if (nodeId !== void 0 && nodeId !== this.nodeId) {
      return;
    }
    const wpf = wildcardPathFlags ? WildcardPathFlagsCodec.encode(wildcardPathFlags) : 0;
    if (endpointId === void 0) {
      this.#addProducer(function* () {
        this.#wildcardPathFlags = wpf;
        for (const endpoint2 of this.node) {
          yield* this.readEndpointForWildcard(endpoint2, path);
        }
      });
      return;
    }
    const endpoint = this.node[endpointId];
    if (endpoint) {
      this.#addProducer(function() {
        this.#wildcardPathFlags = wpf;
        return this.readEndpointForWildcard(endpoint, path);
      });
    }
  }
  /**
   * Validate a concrete path and update internal state.
   */
  addConcrete(path) {
    const { nodeId, endpointId, clusterId, attributeId } = path;
    if (nodeId !== void 0 && this.nodeId !== nodeId) {
      this.addStatus(path, Status.UnsupportedNode);
      return;
    }
    const endpoint = this.node[endpointId];
    const cluster = endpoint?.[clusterId];
    const attribute = cluster?.type.attributes[attributeId];
    let limits;
    if (attribute === void 0) {
      const modelAttr = this.node.matter.member(path.clusterId, [ElementTag.Cluster])?.member(path.attributeId, [ElementTag.Attribute]);
      if (modelAttr) {
        limits = AccessControl(modelAttr).limits;
      } else {
        limits = FallbackLimits;
      }
    } else {
      limits = attribute.limits;
    }
    if (hasRemoteActor(this.session)) {
      const location = {
        ...cluster?.location ?? {
          path: DataModelPath.none,
          endpoint: endpointId,
          cluster: clusterId
        },
        owningFabric: this.session.fabric
      };
      const permission = this.session.authorityAt(limits.readLevel, location);
      switch (permission) {
        case AccessControl.Authority.Granted:
          break;
        case AccessControl.Authority.Unauthorized:
          this.addStatus(path, Status.UnsupportedAccess);
          return;
        case AccessControl.Authority.Restricted:
          this.addStatus(path, Status.AccessRestricted);
          return;
        default:
          throw new InternalError(`Unsupported authorization state ${permission}`);
      }
    }
    if (endpoint === void 0) {
      this.addStatus(path, Status.UnsupportedEndpoint);
      return;
    }
    if (cluster === void 0) {
      this.addStatus(path, Status.UnsupportedCluster);
      return;
    }
    if (attribute === void 0 || !cluster.type.attributes[attribute.id]) {
      this.addStatus(path, Status.UnsupportedAttribute);
      return;
    }
    if (!limits.readable) {
      this.addStatus(path, Status.UnsupportedRead);
      return;
    }
    const skipVersion = this.#versions?.[path.endpointId]?.[path.clusterId];
    if (skipVersion !== void 0 && skipVersion === cluster.version) {
      this.#filteredCount++;
      return;
    }
    this.#addProducer(function* () {
      if (this.#currentEndpoint !== endpoint) {
        if (this.#chunk) {
          yield this.#chunk;
          this.#chunk = void 0;
        }
        this.#currentEndpoint = endpoint;
        this.#currentCluster = cluster;
        this.#currentState = cluster.readState(this.session);
      } else if (this.#currentCluster !== cluster) {
        this.#currentCluster = cluster;
        this.#currentState = cluster.readState(this.session);
      } else if (this.#currentState === void 0) {
        this.#currentState = cluster.readState(this.session);
      }
      const value = this.#currentState[attributeId];
      const version = cluster.version;
      logger.debug(
        () => `Reading attribute ${this.node.inspectPath(path)}=${serialize(value)} (version=${version})`
      );
      this.#addValue(path, value, version, this.#currentCluster.type.attributes[attributeId].tlv);
    });
  }
  /**
   * 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.
   *
   * {@link this.#wildcardPathFlags} to numeric bitmap must be set prior to invocation.
   *
   * TODO - skip endpoints for which subject is unauthorized as optimization
   */
  *readEndpointForWildcard(endpoint, path) {
    if (endpoint.wildcardPathFlags & this.#wildcardPathFlags) {
      return;
    }
    if (this.#currentEndpoint !== endpoint) {
      if (this.#chunk) {
        yield this.#chunk;
        this.#chunk = void 0;
      }
      this.#currentEndpoint = endpoint;
      this.#currentCluster = void 0;
    }
    const { clusterId } = path;
    if (clusterId === void 0) {
      for (const cluster of endpoint) {
        this.readClusterForWildcard(cluster, path);
      }
    } else {
      const cluster = endpoint[clusterId];
      if (cluster !== void 0) {
        this.readClusterForWildcard(cluster, path);
      }
    }
  }
  /**
   * Read values from a specific {@link cluster} for a wildcard path.
   *
   * Depends on state initialized by {@link #readEndpointForWildcard}.
   *
   * TODO - skip clusters for which subject is unauthorized
   */
  readClusterForWildcard(cluster, path) {
    if (cluster.type.wildcardPathFlags & this.#wildcardPathFlags) {
      return;
    }
    if (this.#currentCluster !== cluster) {
      this.#currentCluster = cluster;
      this.#currentState = void 0;
    }
    const { attributeId } = path;
    const skipVersion = this.#versions?.[this.#guardedCurrentEndpoint.id]?.[cluster.type.id];
    const filteredByVersion = skipVersion !== void 0 && skipVersion === cluster.version;
    if (attributeId === void 0) {
      if (filteredByVersion) {
        for (const attribute of cluster.type.attributes) {
          if (attribute.limits.readable) {
            this.#filteredCount++;
          }
        }
        return;
      }
      for (const attribute of cluster.type.attributes) {
        this.readAttributeForWildcard(attribute, path);
      }
    } else {
      if (filteredByVersion) {
        this.#filteredCount++;
        return;
      }
      const attribute = cluster.type.attributes[attributeId];
      if (attribute !== void 0) {
        this.readAttributeForWildcard(attribute, path);
      }
    }
  }
  /**
   * Read values from a specific {@link attribute} for a wildcard path.
   *
   * Depends on state initialized by {@link #readClusterForWildcard}.
   */
  readAttributeForWildcard(attribute, path) {
    if (!this.#guardedCurrentCluster.type.attributes[attribute.id]) {
      return;
    }
    if (attribute.wildcardPathFlags & this.#wildcardPathFlags) {
      return;
    }
    if (!attribute.limits.readable || hasRemoteActor(this.session) && this.session.authorityAt(attribute.limits.readLevel, this.#guardedCurrentCluster.location) !== AccessControl.Authority.Granted) {
      return;
    }
    if (this.#currentState === void 0) {
      this.#currentState = this.#guardedCurrentCluster.readState(this.session);
    }
    const value = this.#currentState[attribute.id];
    if (value === void 0) {
      logger.warn(`Attribute ${this.node.inspectPath(path)} defined and enabled but has no value.`);
      return;
    }
    this.#addValue(
      {
        ...path,
        endpointId: this.#guardedCurrentEndpoint.id,
        clusterId: this.#guardedCurrentCluster.type.id,
        attributeId: attribute.id
      },
      this.#currentState[attribute.id],
      this.#guardedCurrentCluster.version,
      attribute.tlv
    );
  }
  /**
   * Add a function that produces data.  These functions are run after validation of input paths.
   */
  #addProducer(producer) {
    if (this.#dataProducers) {
      this.#dataProducers.push(producer);
    } else {
      this.#dataProducers = [producer];
    }
  }
  #addReportData(report) {
    if (this.#chunk) {
      this.#chunk.push(report);
    } else {
      this.#chunk = [report];
    }
  }
  /**
   * Add a status value.
   */
  addStatus(path, status) {
    logger.debug(
      () => `Error reading attribute ${this.node.inspectPath(path)}: Status=${StatusCode[status]}(${status})`
    );
    const report = {
      kind: "attr-status",
      path,
      status
    };
    this.#addReportData(report);
    this.#statusCount++;
  }
  /**
   * Add an attribute value.
   */
  #addValue(path, value, version, tlv) {
    const report = {
      kind: "attr-value",
      path,
      value,
      version,
      tlv
    };
    this.#addReportData(report);
    this.#valueCount++;
  }
}
export {
  AttributeReadResponse,
  GlobalAttrIds
};
//# sourceMappingURL=AttributeReadResponse.js.map
