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, hasRemoteActor } from "#action/server/AccessControl.js";
import { DataResponse, FallbackLimits } from "#action/server/DataResponse.js";
import { Diagnostic, InternalError, Logger, serialize, toHex } from "@matter/general";
import { AccessLevel, DataModelPath, ElementTag, FabricIndex as FabricIndexField } from "@matter/model";
import {
  ArraySchema,
  FabricIndex,
  Status,
  StatusResponseError
} from "@matter/types";
import { Subject } from "./Subject.js";
const logger = Logger.get("AttributeWriteResponse");
class AttributeWriteResponse extends DataResponse {
  #fabricIndex;
  // 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;
  // Count how many attribute status (on error) and attribute values (on success) we have emitted
  #statusCount = 0;
  #successCount = 0;
  #errorCount = 0;
  constructor(node, session) {
    super(node, session);
    this.#fabricIndex = session.fabric ?? FabricIndex.NO_FABRIC;
  }
  async process({ writeRequests, suppressResponse }) {
    var _stack = [];
    try {
      const _writing = __using(_stack, this.join("writing"));
      const writeResponses = new Array();
      for (const { path, data, dataVersion } of writeRequests) {
        if (path.wildcardPathFlags) {
          throw new StatusResponseError(
            "WildcardPathFlags are not allowed in write interactions",
            Status.InvalidAction
          );
        }
        if (path.endpointId === void 0 || path.clusterId === void 0 || path.attributeId === void 0) {
          const responses = await this.#processWildcard(path, data);
          if (responses !== void 0) {
            writeResponses.push(...responses);
          }
        } else {
          if (Subject.isGroup(this.session.subject)) {
            throw new StatusResponseError("Group writes can not be concrete paths", Status.InvalidAction);
          }
          writeResponses.push(
            await this.#writeConcrete(path, data, dataVersion)
          );
        }
      }
      if (!suppressResponse) {
        return writeResponses;
      }
      return void 0;
    } 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.#successCount,
      existent: this.#successCount + this.#errorCount
    };
  }
  /**
   * Validate a wildcard path and update internal state.
   */
  async #processWildcard(path, value) {
    const { nodeId, endpointId } = path;
    if (nodeId !== void 0 && nodeId !== this.nodeId) {
      return;
    }
    const isGroupPath = Subject.isGroup(this.session.subject);
    if (endpointId === void 0) {
      let groupEndpoints;
      if (isGroupPath) {
        if (this.session.subject.endpoints?.length) {
          groupEndpoints = this.session.subject.endpoints;
        } else {
          logger.debug(`No endpoints mapped to group ${this.session.subject.id}, skipping wildcard invoke`);
          return;
        }
      }
      const responses = new Array();
      for (const endpoint2 of this.node) {
        if (groupEndpoints !== void 0 && !groupEndpoints.includes(endpoint2.id)) {
          continue;
        }
        const response = await this.#writeEndpointForWildcard(endpoint2, path, value);
        if (response !== void 0) {
          responses.push(response);
        }
      }
      return responses;
    }
    if (isGroupPath) {
      throw new StatusResponseError("Illegal write request with group ID and endpoint ID", Status.InvalidAction);
    }
    const endpoint = this.node[endpointId];
    if (endpoint) {
      const response = await this.#writeEndpointForWildcard(endpoint, path, value);
      if (response !== void 0) {
        return [response];
      }
    }
  }
  /**
   * Write to a concrete path and update internal state.
   */
  async #writeConcrete(path, value, version) {
    const { nodeId, endpointId, clusterId, attributeId } = path;
    if (nodeId !== void 0 && this.nodeId !== nodeId) {
      return this.#asStatus(path, Status.UnsupportedNode);
    }
    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;
    }
    let access;
    if (hasRemoteActor(this.session)) {
      const location = {
        ...cluster?.location ?? {
          path: DataModelPath.none,
          endpoint: endpointId,
          cluster: clusterId
        },
        owningFabric: this.session.fabric
      };
      access = { session: this.session, location };
      const denial = this.#authorize(access.session, AccessLevel.View, location);
      if (denial !== void 0) {
        return this.#asStatus(path, denial);
      }
    }
    if (endpoint === void 0) {
      return this.#asStatus(path, Status.UnsupportedEndpoint);
    }
    if (cluster === void 0) {
      return this.#asStatus(path, Status.UnsupportedCluster);
    }
    if (attribute === void 0 || !cluster.type.attributes[attribute.id]) {
      return this.#asStatus(path, Status.UnsupportedAttribute);
    }
    if (!limits.writable) {
      this.#errorCount++;
      return this.#asStatus(path, Status.UnsupportedWrite);
    }
    if (access !== void 0) {
      const denial = this.#authorize(access.session, limits.writeLevel, access.location);
      if (denial !== void 0) {
        return this.#asStatus(path, denial);
      }
    }
    if (hasRemoteActor(this.session)) {
      if (limits.timed && !this.session.timed) {
        this.#errorCount++;
        return this.#asStatus(path, Status.NeedsTimedInteraction);
      }
      if (limits.fabricScoped && !this.session.fabric) {
        this.#errorCount++;
        return this.#asStatus(path, Status.UnsupportedAccess);
      }
    }
    if (version !== void 0 && version !== cluster.version) {
      this.#errorCount++;
      return this.#asStatus(path, Status.DataVersionMismatch);
    }
    if (this.#currentEndpoint !== endpoint) {
      this.#currentEndpoint = endpoint;
      this.#currentCluster = cluster;
    } else if (this.#currentCluster !== cluster) {
      this.#currentCluster = cluster;
    }
    return await this.writeValue(attribute, path, value);
  }
  /**
   * 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 as optimization
   */
  #writeEndpointForWildcard(endpoint, path, value) {
    const { clusterId, attributeId } = path;
    if (clusterId === void 0 || attributeId === void 0) {
      throw new StatusResponseError(
        "Wildcard path write must specify a clusterId and attributeId",
        Status.InvalidAction
      );
    }
    if (this.#currentEndpoint !== endpoint) {
      this.#currentEndpoint = endpoint;
      this.#currentCluster = void 0;
    }
    const cluster = endpoint[clusterId];
    if (cluster !== void 0) {
      return this.#writeClusterForWildcard(cluster, path, value);
    }
  }
  /**
   * Write values for a specific {@link cluster} for a wildcard path.
   *
   * Depends on state initialized by {@link #writeEndpointForWildcard}.
   *
   * TODO - skip clusters for which subject is unauthorized
   */
  #writeClusterForWildcard(cluster, path, value) {
    if (this.#currentCluster !== cluster) {
      this.#currentCluster = cluster;
    }
    const { attributeId } = path;
    if (attributeId === void 0) {
      throw new StatusResponseError("Wildcard path write must specify an attributeId", Status.InvalidAction);
    } else {
      const attribute = cluster.type.attributes[attributeId];
      if (attribute !== void 0) {
        return this.#writeAttributeForWildcard(attribute, path, value);
      }
    }
  }
  /**
   * Write values to a specific {@link attribute} for a wildcard path.
   *
   * Depends on state initialized by {@link #writeClusterForWildcard}.
   */
  #writeAttributeForWildcard(attribute, path, value) {
    if (!this.#guardedCurrentCluster.type.attributes[attribute.id]) {
      return;
    }
    if (!attribute.limits.writable) {
      return;
    }
    if (hasRemoteActor(this.session)) {
      if (this.session.authorityAt(attribute.limits.writeLevel, this.#guardedCurrentCluster.location) !== AccessControl.Authority.Granted) {
        return;
      }
      if (attribute.limits.timed && !this.session.timed) {
        return;
      }
    }
    return this.writeValue(
      attribute,
      {
        ...path,
        endpointId: this.#guardedCurrentEndpoint.id,
        clusterId: this.#guardedCurrentCluster.type.id,
        attributeId: attribute.id
      },
      value
    );
  }
  /**
   * Validate access at {@link level}.  Returns undefined if granted; otherwise the status to report.
   */
  #authorize(session, level, location) {
    const permission = session.authorityAt(level, location);
    switch (permission) {
      case AccessControl.Authority.Granted:
        return void 0;
      case AccessControl.Authority.Unauthorized:
        return Status.UnsupportedAccess;
      case AccessControl.Authority.Restricted:
        return Status.AccessRestricted;
      default:
        throw new InternalError(`Unsupported authorization state ${permission}`);
    }
  }
  /**
   * Add a status value.
   */
  #asStatus(path, status, clusterStatus) {
    if (clusterStatus !== void 0 && status !== Status.Success) {
      status = Status.Failure;
    }
    if (status !== Status.Success) {
      logger.debug(
        () => `Error writing attribute ${this.node.inspectPath(path)}: Status=${Status[status]}(${toHex(status)}), ClusterStatus=${clusterStatus !== void 0 ? toHex(clusterStatus) : void 0}`
      );
    }
    const report = {
      kind: "attr-status",
      path,
      status,
      clusterStatus
    };
    if (status !== Status.Success) {
      this.#statusCount++;
    }
    return report;
  }
  async writeValue(attribute, path, value) {
    const { attributeId, listIndex } = path;
    if (listIndex !== void 0 && listIndex !== null) {
      throw new StatusResponseError(
        `Unsupported Write path provided: listIndex === ${listIndex}`,
        Status.InvalidAction
      );
    }
    try {
      const { tlv } = attribute;
      if (listIndex === void 0) {
        const decoded = this.#decodeWithSchema(tlv, value);
        logger.debug(() => `Writing attribute ${this.node.inspectPath(path)}=${Diagnostic.json(decoded)}`);
        const writeState = await this.#guardedCurrentCluster.openForWrite(this.session);
        writeState[attributeId] = decoded;
        await this.session.transaction?.commit();
      } else if (listIndex === null) {
        if (!(tlv instanceof ArraySchema)) {
          throw new StatusResponseError(
            `Unsupported Write path provided: listIndex === ${listIndex} but attribute is not a list`,
            Status.InvalidAction
          );
        }
        const writeState = await this.#guardedCurrentCluster.openForWrite(this.session);
        const decoded = this.#decodeWithSchema(tlv.elementSchema, value);
        logger.debug(
          () => `Writing attribute chunk ${this.node.inspectPath(path)} adding ${serialize(decoded)}`
        );
        writeState[attributeId].push(decoded);
        await this.session.transaction?.commit();
      }
    } catch (error) {
      await this.session.transaction?.rollback();
      const sre = StatusResponseError.of(error);
      if (sre) {
        this.#errorCount++;
        return this.#asStatus(path, sre.code, sre.clusterCode);
      }
      throw error;
    }
    this.#successCount++;
    return this.#asStatus(path, Status.Success);
  }
  #decodeWithSchema(tlv, value) {
    return tlv.injectField(
      tlv.decodeTlv(value),
      FabricIndexField.id,
      this.#fabricIndex,
      () => true
      // We always inject the current fabricIndex for writes
    );
  }
}
export {
  AttributeWriteResponse
};
//# sourceMappingURL=AttributeWriteResponse.js.map
