/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { ImplementationError } from "@matter/general";
import { FabricIndex as FabricIndexElement } from "@matter/model";
import { NoAssociatedFabricError } from "@matter/protocol";
import { FabricIndex, TlvOfModel } from "@matter/types";
function createAttributeClient(attribute, name, endpointId, clusterId, interactionClient, present = false, unknown = false) {
  if (unknown) {
    return new UnknownSupportedAttributeClient(attribute, name, endpointId, clusterId, interactionClient);
  }
  if (present) {
    return new SupportedAttributeClient(attribute, name, endpointId, clusterId, interactionClient);
  }
  return new AttributeClient(attribute, name, endpointId, clusterId, interactionClient);
}
class AttributeClient {
  constructor(attribute, name, endpointId, clusterId, interactionClient) {
    this.attribute = attribute;
    this.name = name;
    this.endpointId = endpointId;
    this.clusterId = clusterId;
    const model = attribute.schema;
    this.schema = TlvOfModel(model);
    this.id = attribute.id;
    this.#isWritable = model.writable;
    this.#isFabricScoped = model.fabricScoped;
    this.#updatedBySubscriptions = !(model.effectiveQuality.changesOmitted ?? false);
    this.#interactionClient = interactionClient;
  }
  #isWritable;
  #isFabricScoped;
  #updatedBySubscriptions;
  schema;
  #listeners = new Array();
  id;
  #interactionClient;
  /**
   * Set the value of the attribute. When dataVersion parameter is provided the value is only set when the
   * cluster dataVersion of the server matches. If it does not match it is rejected with an Error.
   */
  async set(value, dataVersion) {
    if (!this.#isWritable) throw new ImplementationError(`Attribute ${this.name} is not writable`);
    value = this.schema.injectField(
      value,
      FabricIndexElement.id,
      FabricIndex.OMIT_FABRIC,
      (existingFieldIndex) => existingFieldIndex === void 0
    );
    this.schema.validate(value);
    if (this.#isFabricScoped) {
      value = this.schema.removeField(
        value,
        FabricIndexElement.id,
        (existingFieldIndex) => existingFieldIndex === FabricIndex.OMIT_FABRIC
      );
      if (this.#interactionClient.maybeAddress) {
        const fabricIndex = this.#interactionClient.maybeAddress.fabricIndex;
        try {
          value = this.schema.removeField(
            value,
            FabricIndexElement.id,
            (existingFieldIndex) => existingFieldIndex.index === fabricIndex
          );
        } catch (e) {
          NoAssociatedFabricError.accept(e);
        }
      }
    }
    return await this.#interactionClient.setAttribute({
      attributeData: {
        endpointId: this.endpointId,
        clusterId: this.clusterId,
        attribute: this.attribute,
        value,
        dataVersion
      }
    });
  }
  get fabricScoped() {
    return this.#isFabricScoped;
  }
  getLocal() {
    if (this.endpointId === void 0) {
      throw new ImplementationError(`Cannot read attribute ${this.name} without endpointId.`);
    }
    return this.#interactionClient.getStoredAttribute({
      endpointId: this.endpointId,
      clusterId: this.clusterId,
      attribute: this.attribute
    });
  }
  /**
   * Get the value of the attribute. Fabric scoped reads are always done with the remote.
   * The `requestFromRemote` parameter allowed to force or prevent remote reads:
   * - `true` forces a remote read
   * - `false` forces a local read, return undefined if no value is available
   * - `undefined` returns local values if available or if the read is fabric filtered, otherwise remote read
   */
  async get(requestFromRemote, isFabricFiltered = true) {
    if (this.endpointId === void 0) {
      throw new ImplementationError(`Cannot read attribute ${this.name} without endpointId.`);
    }
    if (requestFromRemote === void 0) {
      requestFromRemote = this.#isFabricScoped || !this.#updatedBySubscriptions ? true : void 0;
    } else if (!requestFromRemote && this.#isFabricScoped) {
      requestFromRemote = true;
    }
    return await this.#interactionClient.getAttribute({
      endpointId: this.endpointId,
      clusterId: this.clusterId,
      attribute: this.attribute,
      isFabricFiltered,
      requestFromRemote
    });
  }
  /** Subscribe to the attribute. */
  async subscribe(minIntervalFloorSeconds, maxIntervalCeilingSeconds, knownDataVersion, isFabricFiltered = true) {
    if (this.endpointId === void 0) {
      throw new ImplementationError(`Cannot read attribute ${this.name} without endpointId.`);
    }
    if (!this.#updatedBySubscriptions) {
      throw new ImplementationError(`Attribute ${this.name} is not updated by subscriptions.`);
    }
    return this.#interactionClient.subscribeAttribute({
      endpointId: this.endpointId,
      clusterId: this.clusterId,
      attribute: this.attribute,
      minIntervalFloorSeconds,
      maxIntervalCeilingSeconds,
      isFabricFiltered,
      knownDataVersion,
      listener: (value) => this.update(value)
    });
  }
  /**
   * Update the value of the attribute. Just internally used!
   * @private
   */
  update(value) {
    this.#listeners.forEach((listener) => listener(value));
  }
  /** Add a listener to the attribute. */
  addListener(listener) {
    this.#listeners.push(listener);
  }
  /** Remove a listener from the attribute. */
  removeListener(listener) {
    const entryIndex = this.#listeners.indexOf(listener);
    if (entryIndex !== -1) {
      this.#listeners.splice(entryIndex, 1);
    }
  }
}
class SupportedAttributeClient extends AttributeClient {
}
class UnknownSupportedAttributeClient extends SupportedAttributeClient {
}
export {
  AttributeClient,
  SupportedAttributeClient,
  UnknownSupportedAttributeClient,
  createAttributeClient
};
//# sourceMappingURL=AttributeClient.js.map
