"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 AtomicWriteHandler_exports = {};
__export(AtomicWriteHandler_exports, {
  AtomicWriteHandler: () => AtomicWriteHandler
});
module.exports = __toCommonJS(AtomicWriteHandler_exports);
var import_ClusterBehavior = require("#behavior/cluster/ClusterBehavior.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");
var import_thermostat = require("@matter/types/clusters/thermostat");
var import_AtomicWriteState = require("./AtomicWriteState.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("AtomicWriteHandler");
class AtomicWriteHandler {
  #observers = new import_general.ObserverGroup();
  #pendingWrites = new import_general.BasicSet();
  constructor(fabricManager) {
    this.#observers.on(fabricManager.events.deleting, (fabric) => this.#handleFabricRemoval(fabric));
  }
  static [import_general.Environmental.create](env) {
    const instance = new AtomicWriteHandler(env.get(import_protocol.FabricManager));
    env.set(AtomicWriteHandler, instance);
    return instance;
  }
  close() {
    this.#observers.close();
    for (const writeState of Array.from(this.#pendingWrites)) {
      writeState.close();
    }
  }
  /**
   * Initializes an AtomicWrite state for the given request, context, endpoint and cluster.
   * It also implements all relevant validation according to the Matter spec.
   */
  #initializeState({ requestType, attributeRequests, timeout }, context, endpoint, cluster) {
    if (!import_ClusterBehavior.ClusterBehavior.is(cluster)) {
      throw new import_general.InternalError("Cluster behavior expected for atomic write handler");
    }
    const peerAddress = this.#assertValidPeer(context);
    if (attributeRequests.length === 0) {
      throw new import_types.StatusResponse.InvalidCommandError("No attribute requests provided");
    }
    const attributes = /* @__PURE__ */ new Map();
    for (const attr of attributeRequests) {
      const [attributeName, _] = Object.entries(cluster.cluster.attributes ?? {}).find(([_2, { id }]) => id === attr) ?? [];
      if (attributeName === void 0 || endpoint.stateOf(cluster.id)[attr] === void 0) {
        throw new import_types.StatusResponse.InvalidCommandError(`Attribute ${attr} not supported by cluster`);
      }
      if (attributes.has(attr)) {
        throw new import_types.StatusResponse.InvalidCommandError("Duplicate attribute in attribute requests");
      }
      attributes.set(attr, attributeName);
    }
    const existingState = this.#pendingWrites.find(
      (s) => import_protocol.PeerAddress.is(s.peerAddress, peerAddress) && s.endpoint.number == endpoint.number && s.clusterId === cluster.cluster.id
    );
    if (requestType === import_thermostat.Thermostat.RequestType.BeginWrite) {
      if (timeout === void 0) {
        throw new import_types.StatusResponse.InvalidCommandError("Timeout missing for BeginWrite request");
      }
      if (existingState !== void 0 && existingState.attributeRequests.some((attr) => attributeRequests.includes(attr))) {
        throw new import_types.StatusResponse.InvalidCommandError(
          "An atomic write for at least one of the attributes is already in progress for this peer"
        );
      }
      const initialValues = {};
      for (const attr of attributeRequests) {
        initialValues[attr] = endpoint.stateOf(cluster.id)[attr];
      }
      const state = new import_AtomicWriteState.AtomicWriteState(
        peerAddress,
        endpoint,
        cluster.cluster.id,
        attributeRequests,
        timeout,
        attributes,
        initialValues
      );
      this.#pendingWrites.add(state);
      state.closed.on(() => void this.#pendingWrites.delete(state));
      logger.debug("Added atomic write state:", state);
      return state;
    }
    if (existingState === void 0) {
      throw new import_types.StatusResponse.InvalidInStateError("No atomic write in progress for this peer");
    }
    if (existingState.attributeRequests.length !== attributeRequests.length || !existingState.attributeRequests.every((attr) => attributeRequests.includes(attr))) {
      throw new import_types.StatusResponse.InvalidInStateError("Attribute requests do not match existing atomic write");
    }
    return existingState;
  }
  /**
   * Implements the begin write logic for an atomic write.
   */
  beginWrite(request, context, endpoint, cluster) {
    if (!(0, import_protocol.hasRemoteActor)(context)) {
      throw new import_types.StatusResponse.InvalidCommandError("AtomicRequest requires a remote actor");
    }
    if (!import_ClusterBehavior.ClusterBehavior.is(cluster)) {
      throw new import_general.InternalError("Cluster behavior expected for atomic write handler");
    }
    let commandStatusCode = import_types.Status.Success;
    const attributeStatus = request.attributeRequests.map((attr) => {
      let statusCode = import_types.Status.Success;
      const attributeModel = cluster.schema.conformant.attributes(attr);
      if (!attributeModel?.quality.atomic) {
        statusCode = import_types.Status.InvalidAction;
      } else if (this.#pendingWriteStateForAttribute(endpoint, cluster, attr) !== void 0) {
        statusCode = import_types.Status.Busy;
      } else {
        const { writeLevel } = cluster.supervisor.get(attributeModel).access.limits;
        const location = {
          path: import_model.DataModelPath.none,
          endpoint: endpoint.number,
          cluster: cluster.cluster.id,
          owningFabric: context.fabric
        };
        if (context.authorityAt(writeLevel, location) !== import_protocol.AccessControl.Authority.Granted) {
          statusCode = import_types.Status.UnsupportedAccess;
        }
      }
      if (statusCode !== import_types.Status.Success) {
        commandStatusCode = import_types.Status.Failure;
      }
      return {
        attributeId: attr,
        statusCode
      };
    });
    let timeout;
    if (commandStatusCode === import_types.Status.Success) {
      const state = this.#initializeState(request, context, endpoint, cluster);
      timeout = state.timeout;
    }
    return {
      statusCode: commandStatusCode,
      attributeStatus,
      timeout
    };
  }
  /**
   * Handles writing a value for an attribute as part of an ongoing atomic write.
   * It uses the *$AtomicChanging* event to trigger validation of the partial write.
   */
  writeAttribute(context, endpoint, cluster, attribute, value) {
    const state = this.#assertPendingWriteForAttributeAndPeer(context, endpoint, cluster, attribute);
    const attributeName = state.attributeNames.get(attribute);
    logger.debug(`Writing pending value for attribute ${attributeName}, ${attribute} in atomic write`, value);
    endpoint.eventsOf(cluster.id)[`${attributeName}$AtomicChanging`]?.emit(value, state.pendingAttributeValues[attribute] !== void 0 ? state.pendingAttributeValues[attribute] : state.initialValues[attribute], context);
    state.pendingAttributeValues[attribute] = value;
    logger.debug("Atomic write state after current write:", state);
  }
  /**
   * Implements the commit logic for an atomic write.
   */
  async commitWrite(request, context, endpoint, cluster, clusterState) {
    const state = this.#initializeState(request, context, endpoint, cluster);
    let commandStatusCode = import_types.Status.Success;
    const attributeStatus = [];
    for (const [attr, value] of Object.entries(state.pendingAttributeValues)) {
      let statusCode = import_types.Status.Success;
      try {
        const attributeName = state.attributeNames.get((0, import_types.AttributeId)(Number(attr)));
        endpoint.eventsOf(cluster.id)[`${attributeName}$AtomicChanging`]?.emit(value, endpoint.stateOf(cluster.id)[attr], context);
        endpoint.eventsOf(cluster.id)[`${attributeName}$AtomicChanged`]?.emit(value, endpoint.stateOf(cluster.id)[attr], context);
        clusterState[attr] = value;
        await context.transaction?.commit();
      } catch (error) {
        await context.transaction?.rollback();
        logger.info(
          `Failed to write attribute ${attr} during atomic write commit:`,
          import_general.Diagnostic.errorMessage((0, import_general.asError)(error))
        );
        statusCode = import_types.StatusResponseError.of(error)?.code ?? import_types.Status.Failure;
        commandStatusCode = commandStatusCode === import_types.Status.Failure ? import_types.Status.Failure : commandStatusCode === import_types.Status.ConstraintError ? import_types.Status.ConstraintError : import_types.Status.Failure;
      }
      attributeStatus.push({
        attributeId: (0, import_types.AttributeId)(Number(attr)),
        statusCode
      });
    }
    state.close();
    return {
      statusCode: commandStatusCode,
      attributeStatus
    };
  }
  /**
   * Implements the rollback logic for an atomic write.
   */
  rollbackWrite(request, context, endpoint, cluster) {
    const state = this.#initializeState(request, context, endpoint, cluster);
    state.close();
    return {
      statusCode: import_types.Status.Success,
      attributeStatus: state.attributeRequests.map((attr) => ({
        attributeId: attr,
        statusCode: import_types.Status.Success
      }))
    };
  }
  /**
   * Handles fabric removal by closing all pending atomic write states for peers on the removed fabric.
   */
  #handleFabricRemoval(fabric) {
    const fabricIndex = fabric.fabricIndex;
    for (const writeState of Array.from(this.#pendingWrites)) {
      if (writeState.peerAddress.fabricIndex === fabricIndex) {
        logger.debug(
          `Closing atomic write state for peer ${writeState.peerAddress.toString()} on endpoint ${writeState.endpoint.id} due to fabric removal`
        );
        writeState.close();
      }
    }
  }
  /**
   * Returns the pending write state for the given attribute, if any.
   */
  #pendingWriteStateForAttribute(endpoint, cluster, attribute) {
    const writeStates = this.#pendingWrites.filter(
      (s) => s.endpoint.number === endpoint.number && s.clusterId === cluster.cluster.id
    );
    if (writeStates.length === 0) {
      return void 0;
    }
    const attrWriteStates = writeStates.filter(({ attributeRequests }) => attributeRequests.includes(attribute));
    if (attrWriteStates.length === 0) {
      return void 0;
    }
    if (attrWriteStates.length > 1) {
      throw new import_general.InternalError("Multiple atomic write states found for the same attribute. Should never happen");
    }
    return attrWriteStates[0];
  }
  /**
   * Returns the pending value for the given attribute and peer, if any.
   */
  pendingValueForAttributeAndPeer(context, endpoint, cluster, attribute) {
    const peerAddress = this.#derivePeerAddress(context);
    if (peerAddress === void 0) {
      return void 0;
    }
    const attrWriteState = this.#pendingWriteStateForAttribute(endpoint, cluster, attribute);
    if (attrWriteState === void 0) {
      return void 0;
    }
    if (!import_protocol.PeerAddress.is(attrWriteState.peerAddress, peerAddress)) {
      return void 0;
    }
    logger.debug(
      `Found pending value for attribute ${attribute} for peer ${peerAddress.nodeId}`,
      (0, import_general.serialize)(attrWriteState.pendingAttributeValues[attribute])
    );
    return attrWriteState.pendingAttributeValues[attribute];
  }
  #assertPendingWriteForAttributeAndPeer(session, endpoint, cluster, attribute) {
    const attrWriteState = this.#pendingWriteStateForAttribute(endpoint, cluster, attribute);
    if (attrWriteState === void 0) {
      throw new import_types.StatusResponse.InvalidInStateError("There is no atomic write in progress for this attribute");
    }
    const peerAddress = this.#derivePeerAddress(session);
    if (peerAddress === void 0) {
      throw new import_types.StatusResponse.InvalidInStateError("There is no atomic write in progress for this peer");
    }
    if (!import_protocol.PeerAddress.is(attrWriteState.peerAddress, peerAddress)) {
      throw new import_types.StatusResponse.BusyError("Attribute is part of an atomic write in progress for a different peer");
    }
    return attrWriteState;
  }
  #derivePeerAddress(session) {
    if ((0, import_protocol.hasRemoteActor)(session) && import_protocol.Subject.isNode(session.subject) && import_types.NodeId.isOperationalNodeId(session.subject.id)) {
      return (0, import_protocol.PeerAddress)({ fabricIndex: session.fabric, nodeId: (0, import_types.NodeId)(session.subject.id) });
    }
  }
  #assertValidPeer(context) {
    (0, import_protocol.assertRemoteActor)(context);
    const peerAddress = this.#derivePeerAddress(context);
    if (!context.session.associatedFabric || peerAddress === void 0) {
      throw new import_types.StatusResponse.InvalidCommandError("AtomicRequest requires an operational session");
    }
    return peerAddress;
  }
}
//# sourceMappingURL=AtomicWriteHandler.js.map
