/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Mark } from "#common/Mark.js";
import { PeerUnresponsiveError } from "#peer/PeerCommunicationError.js";
import { UnexpectedMessageError } from "#protocol/errors.js";
import {
  causedBy,
  Diagnostic,
  InternalError,
  Logger,
  MatterFlowError,
  Millis,
  NoResponseTimeoutError,
  UnexpectedDataError
} from "@matter/general";
import { Specification } from "@matter/model";
import {
  ReceivedStatusResponseError,
  Status,
  StatusResponseError,
  TlvAny,
  TlvDataReport,
  TlvDataReportForSend,
  TlvDataVersionFilter,
  TlvInvokeRequest,
  TlvInvokeResponse,
  TlvInvokeResponseForSend,
  TlvReadRequest,
  TlvStatusResponse,
  TlvSubscribeRequest,
  TlvTimedRequest,
  TlvWriteRequest,
  TlvWriteResponse
} from "@matter/types";
import { SessionType } from "../codec/MessageCodec.js";
import {
  canAttributePayloadBeChunked,
  chunkAttributePayload,
  encodeAttributePayload,
  encodeAttributePayloadData,
  encodeEventPayload
} from "./AttributeDataEncoder.js";
import { Subscription } from "./Subscription.js";
var MessageType = /* @__PURE__ */ ((MessageType2) => {
  MessageType2[MessageType2["StatusResponse"] = 1] = "StatusResponse";
  MessageType2[MessageType2["ReadRequest"] = 2] = "ReadRequest";
  MessageType2[MessageType2["SubscribeRequest"] = 3] = "SubscribeRequest";
  MessageType2[MessageType2["SubscribeResponse"] = 4] = "SubscribeResponse";
  MessageType2[MessageType2["ReportData"] = 5] = "ReportData";
  MessageType2[MessageType2["WriteRequest"] = 6] = "WriteRequest";
  MessageType2[MessageType2["WriteResponse"] = 7] = "WriteResponse";
  MessageType2[MessageType2["InvokeRequest"] = 8] = "InvokeRequest";
  MessageType2[MessageType2["InvokeResponse"] = 9] = "InvokeResponse";
  MessageType2[MessageType2["TimedRequest"] = 10] = "TimedRequest";
  return MessageType2;
})(MessageType || {});
const logger = Logger.get("InteractionMessenger");
const DATA_REPORT_MAX_QUEUED_ATTRIBUTE_MESSAGES = 20;
const DATA_REPORT_MIN_AVAILABLE_BYTES_BEFORE_SENDING = 40;
class InteractionMessenger {
  #exchange;
  #endMessagePromise;
  constructor(exchange) {
    this.#exchange = exchange;
  }
  get exchange() {
    return this.#exchange;
  }
  set exchange(value) {
    this.#exchange = value;
  }
  send(messageType, payload, options) {
    return this.exchange.send(messageType, payload, options);
  }
  trackEndMessage(promise, errorMessage) {
    if (this.#endMessagePromise !== void 0) {
      throw new InternalError("An interaction can only have one end message");
    }
    this.#endMessagePromise = promise.catch((error) => logger.info(errorMessage, Diagnostic.errorMessage(error))).finally(() => this.#endMessagePromise = void 0);
  }
  sendStatus(status, options) {
    return this.send(
      1 /* StatusResponse */,
      TlvStatusResponse.encode({ status, interactionModelRevision: Specification.INTERACTION_MODEL_REVISION }),
      {
        ...options,
        logContext: {
          for: options?.logContext?.for ? `I/Status-${options?.logContext?.for}` : void 0,
          status: `${Status[status] ?? "unknown"}(${Diagnostic.hex(status)})`,
          ...options?.logContext
        }
      }
    );
  }
  async waitForSuccess(expectedMessageInfo, options) {
    await this.nextMessage(1 /* StatusResponse */, options, `Success-${expectedMessageInfo}`);
  }
  async nextMessage(expectedMessageType, options, expectedMessageInfo) {
    return await this.#nextMessage(expectedMessageType, options, expectedMessageInfo);
  }
  async anyNextMessage(expectedMessageInfo, options) {
    return this.#nextMessage(void 0, options, expectedMessageInfo);
  }
  async #nextMessage(expectedMessageType, options, expectedMessageInfo) {
    const message = await this.exchange.nextMessage(options);
    const messageType = message.payloadHeader.messageType;
    if (expectedMessageType !== void 0 && expectedMessageInfo === void 0) {
      expectedMessageInfo = MessageType[expectedMessageType];
    }
    this.throwIfErrorStatusMessage(message, expectedMessageInfo);
    if (expectedMessageType !== void 0 && messageType !== expectedMessageType) {
      throw new UnexpectedDataError(
        `Received unexpected message for ${expectedMessageInfo} type: ${messageType}, expected: ${expectedMessageType}`
      );
    }
    return message;
  }
  async close() {
    if (this.#endMessagePromise !== void 0) {
      await this.#endMessagePromise;
    }
    await this.exchange.close();
  }
  throwIfErrorStatusMessage(message, logHint) {
    const {
      payloadHeader: { messageType },
      payload
    } = message;
    if (messageType !== 1 /* StatusResponse */) return;
    const { status } = TlvStatusResponse.decode(payload);
    if (status !== Status.Success)
      throw new ReceivedStatusResponseError(
        `Received error status: ${Status[status]}(${status})${logHint ? ` (${logHint})` : ""}`,
        status
      );
  }
  get session() {
    return this.exchange.session;
  }
}
class InteractionServerMessenger extends InteractionMessenger {
  async handleRequest(recipient) {
    let continueExchange = true;
    let isGroupSession = false;
    try {
      while (continueExchange) {
        const message = await this.exchange.nextMessage();
        isGroupSession = message.packetHeader.sessionType === SessionType.Group;
        continueExchange = false;
        switch (message.payloadHeader.messageType) {
          case 2 /* ReadRequest */: {
            if (isGroupSession) {
              throw new StatusResponseError(
                `ReadRequest is not supported in group sessions`,
                Status.InvalidAction
              );
            }
            const readRequest = TlvReadRequest.decode(message.payload);
            const { dataReport, payload } = await recipient.handleReadRequest(
              this.exchange,
              readRequest,
              message
            );
            await this.sendDataReport({
              baseDataReport: dataReport,
              forFabricFilteredRead: readRequest.isFabricFiltered,
              payload
            });
            break;
          }
          case 6 /* WriteRequest */: {
            const writeRequest = TlvWriteRequest.decode(message.payload);
            await recipient.handleWriteRequest(this.exchange, writeRequest, this, message);
            break;
          }
          case 3 /* SubscribeRequest */: {
            if (isGroupSession) {
              throw new StatusResponseError(
                `SubscribeRequest is not supported in group sessions`,
                Status.InvalidAction
              );
            }
            const subscribeRequest = TlvSubscribeRequest.decode(message.payload);
            await recipient.handleSubscribeRequest(this.exchange, subscribeRequest, this, message);
            break;
          }
          case 8 /* InvokeRequest */: {
            const invokeRequest = TlvInvokeRequest.decode(message.payload);
            await recipient.handleInvokeRequest(this.exchange, invokeRequest, this, message);
            break;
          }
          case 10 /* TimedRequest */: {
            if (isGroupSession) {
              throw new StatusResponseError(
                `TimedRequest is not supported in group sessions`,
                Status.InvalidAction
              );
            }
            const timedRequest = TlvTimedRequest.decode(message.payload);
            recipient.handleTimedRequest(this.exchange, timedRequest, message);
            await this.sendStatus(Status.Success, {
              logContext: { for: "TimedRequest" }
            });
            continueExchange = true;
            break;
          }
          default:
            throw new StatusResponseError(
              `Unsupported message type ${MessageType[message.payloadHeader.messageType]} (${message.payloadHeader.messageType})`,
              Status.InvalidAction
            );
        }
        if (isGroupSession) {
          break;
        }
      }
    } catch (error) {
      if (causedBy(error, NoResponseTimeoutError, PeerUnresponsiveError)) {
        logger.info(this.exchange.via, this.exchange.diagnostics, error);
        return;
      }
      let errorStatusCode = Status.Failure;
      const sre = StatusResponseError.of(error);
      if (sre) {
        logger.info(
          "Status response",
          Mark.OUTBOUND,
          this.exchange.via,
          this.exchange.diagnostics,
          Diagnostic.strong(`${Status[sre.code]}#${sre.code}`),
          "due to error:",
          Diagnostic.errorMessage(sre)
        );
        errorStatusCode = sre.code;
      } else {
        logger.warn(this.exchange.via, this.exchange.diagnostics, error);
      }
      if (!isGroupSession) {
        await this.sendStatus(errorStatusCode);
      }
    } finally {
      await this.exchange.close();
    }
  }
  /**
   * Handle a DataReport with a Payload Iterator for a DataReport to send, split them into multiple DataReport
   * messages and send them out based on the size.
   */
  async sendDataReport(options) {
    const {
      baseDataReport,
      forFabricFilteredRead,
      payload,
      waitForAck = true,
      suppressEmptyReport = false
    } = options;
    const { subscriptionId, suppressResponse, interactionModelRevision } = baseDataReport;
    const dataReport = {
      subscriptionId,
      suppressResponse,
      interactionModelRevision,
      attributeReports: void 0,
      eventReports: void 0
    };
    if (payload !== void 0) {
      dataReport.moreChunkedMessages = true;
      const emptyDataReportBytes = TlvDataReportForSend.encode(dataReport);
      let allDataReceived = false;
      let processQueueFirst = true;
      const sendAndResetReport = async () => {
        await this.sendDataReportMessage(dataReport, waitForAck);
        delete dataReport.attributeReports;
        delete dataReport.eventReports;
        messageSize = emptyDataReportBytes.byteLength;
        processQueueFirst = true;
      };
      let messageSize = emptyDataReportBytes.byteLength;
      const attributeReportsToSend = new Array();
      const eventReportsToSend = new Array();
      while (true) {
        if (!allDataReceived && (attributeReportsToSend.length === 0 && eventReportsToSend.length === 0 || attributeReportsToSend.length <= DATA_REPORT_MAX_QUEUED_ATTRIBUTE_MESSAGES && !processQueueFirst && !attributeReportsToSend[0].needSendNext)) {
          const { done, value } = await payload.next();
          if (done) {
            allDataReceived = true;
            if (attributeReportsToSend.length === 0 && eventReportsToSend.length === 0) {
              delete dataReport.moreChunkedMessages;
              break;
            } else {
              for (const attributeReport of attributeReportsToSend) {
                attributeReport.needSendNext = true;
              }
              continue;
            }
          }
          if (value === void 0) {
            continue;
          }
          if ("attributeData" in value || "attributeStatus" in value) {
            const allowMissingFieldsForNonFabricFilteredRead = !forFabricFilteredRead && value.hasFabricSensitiveData;
            const encoded = encodeAttributePayload(value, {
              allowMissingFieldsForNonFabricFilteredRead
            });
            const encodedSize = TlvAny.getEncodedByteLength(encoded);
            if (attributeReportsToSend.length === 0) {
              attributeReportsToSend.push({
                attributeReport: value,
                encoded,
                encodedSize
              });
            } else {
              const firstQueuedAttributeData = attributeReportsToSend[0].attributeReport.attributeData;
              if (firstQueuedAttributeData !== void 0 && value.attributeData !== void 0 && firstQueuedAttributeData.path.nodeId === value.attributeData.path.nodeId && firstQueuedAttributeData.path.endpointId === value.attributeData.path.endpointId && firstQueuedAttributeData.path.clusterId === value.attributeData.path.clusterId) {
                attributeReportsToSend.unshift({
                  attributeReport: value,
                  encoded,
                  encodedSize
                });
              } else {
                for (const attributeReport of attributeReportsToSend) {
                  attributeReport.needSendNext = true;
                }
                attributeReportsToSend.push({
                  attributeReport: value,
                  encoded,
                  encodedSize
                });
              }
            }
          } else if ("eventData" in value || "eventStatus" in value) {
            const allowMissingFieldsForNonFabricFilteredRead = !forFabricFilteredRead && value.hasFabricSensitiveData;
            const encoded = encodeEventPayload(value, { allowMissingFieldsForNonFabricFilteredRead });
            const encodedSize = TlvAny.getEncodedByteLength(encoded);
            eventReportsToSend.push({
              eventReport: value,
              encoded,
              encodedSize
            });
          } else {
            throw new InternalError(`Invalid report type: ${value}`);
          }
        }
        if (attributeReportsToSend.length > 0) {
          const attributeToSend = attributeReportsToSend.shift();
          if (attributeToSend === void 0) {
            continue;
          }
          const { attributeReport, encoded, encodedSize, needSendNext } = attributeToSend;
          let availableBytes = this.exchange.maxPayloadSize - messageSize - 3;
          let sendOutTheMessage = false;
          if (encodedSize > availableBytes) {
            if ((allDataReceived || needSendNext) && canAttributePayloadBeChunked(attributeReport)) {
              const chunks = chunkAttributePayload(attributeReport);
              const initialChunk = chunks.shift();
              const firstDataChunk = chunks.shift();
              if (initialChunk === void 0 || firstDataChunk === void 0) {
                throw new InternalError(
                  "Chunked attribute payload is unexpected. This should not happen!"
                );
              }
              initialChunk.attributeData.payload.push(firstDataChunk.attributeData.payload);
              const allowMissingFieldsForNonFabricFilteredRead = !forFabricFilteredRead && attributeReport.hasFabricSensitiveData;
              const encodedInitialChunk = encodeAttributePayload(initialChunk, {
                allowMissingFieldsForNonFabricFilteredRead
              });
              const encodedInitialChunkSize = TlvAny.getEncodedByteLength(encodedInitialChunk);
              if (availableBytes > encodedInitialChunkSize) {
                availableBytes -= encodedInitialChunkSize;
                messageSize += encodedInitialChunkSize;
                while (chunks.length > 0) {
                  const nextChunk = chunks.shift();
                  if (nextChunk === void 0) {
                    throw new InternalError(
                      "Chunked attribute payload is undefined. This should not happen!"
                    );
                  }
                  const encodedChunkData = encodeAttributePayloadData(nextChunk, {
                    allowMissingFieldsForNonFabricFilteredRead
                  });
                  const encodedChunkDataSize = TlvAny.getEncodedByteLength(encodedChunkData);
                  if (encodedChunkDataSize > availableBytes) {
                    chunks.unshift(nextChunk);
                    for (let i = chunks.length - 1; i >= 0; i--) {
                      const chunk = chunks[i];
                      const encodedChunk = encodeAttributePayload(chunk, {
                        allowMissingFieldsForNonFabricFilteredRead
                      });
                      const encodedChunkSize = TlvAny.getEncodedByteLength(encodedChunk);
                      attributeReportsToSend.unshift({
                        attributeReport: chunk,
                        encoded: encodedChunk,
                        encodedSize: encodedChunkSize,
                        needSendNext: true
                      });
                    }
                    if (dataReport.attributeReports === void 0) {
                      dataReport.attributeReports = [];
                    }
                    dataReport.attributeReports.push(
                      encodeAttributePayload(initialChunk, {
                        allowMissingFieldsForNonFabricFilteredRead
                      })
                    );
                    break;
                  }
                  availableBytes -= encodedChunkDataSize;
                  messageSize += encodedChunkDataSize;
                  initialChunk.attributeData.payload.push(nextChunk.attributeData.payload);
                }
                continue;
              } else if (needSendNext) {
                sendOutTheMessage = true;
              }
            } else {
              if (needSendNext) {
                sendOutTheMessage = true;
              } else {
                processQueueFirst = false;
              }
            }
            let messageWasSent = false;
            if (sendOutTheMessage || availableBytes < DATA_REPORT_MIN_AVAILABLE_BYTES_BEFORE_SENDING || attributeReportsToSend.length > 0 && attributeReportsToSend[0].needSendNext || attributeReportsToSend.length >= DATA_REPORT_MAX_QUEUED_ATTRIBUTE_MESSAGES) {
              await sendAndResetReport();
              messageWasSent = true;
            }
            if (!messageWasSent) {
              attributeReportsToSend.push(attributeToSend);
              continue;
            }
            if (encodedSize > this.exchange.maxPayloadSize - emptyDataReportBytes.byteLength - 3) {
              attributeReportsToSend.unshift(attributeToSend);
              continue;
            }
          }
          messageSize += encodedSize;
          if (dataReport.attributeReports === void 0) {
            dataReport.attributeReports = [];
          }
          dataReport.attributeReports.push(encoded);
        } else if (eventReportsToSend.length > 0) {
          const eventToSend = eventReportsToSend.shift();
          if (eventToSend === void 0) {
            continue;
          }
          const { encoded, encodedSize } = eventToSend;
          if (messageSize + 3 + (dataReport.attributeReports ? 3 : 0) + encodedSize > this.exchange.maxPayloadSize) {
            await sendAndResetReport();
          }
          messageSize += encodedSize;
          if (dataReport.eventReports === void 0) {
            dataReport.eventReports = [];
          }
          dataReport.eventReports.push(encoded);
        } else if (allDataReceived) {
          delete dataReport.moreChunkedMessages;
          break;
        }
      }
    }
    if (!suppressEmptyReport || dataReport.attributeReports?.length || dataReport.eventReports?.length) {
      await this.sendDataReportMessage(dataReport, waitForAck);
    }
  }
  async sendDataReportMessage(dataReport, waitForAck = true) {
    const dataReportToSend = {
      ...dataReport,
      suppressResponse: dataReport.moreChunkedMessages ? false : dataReport.suppressResponse
      // always false when moreChunkedMessages is true
    };
    const encodedMessage = TlvDataReportForSend.encode(dataReportToSend);
    if (encodedMessage.byteLength > this.exchange.maxPayloadSize) {
      throw new MatterFlowError(
        `DataReport with ${encodedMessage.byteLength}bytes is too long to fit in a single chunk (${this.exchange.maxPayloadSize}bytes), This should not happen! Data: ${Diagnostic.json(
          dataReportToSend
        )}`
      );
    }
    const logContext = {
      ...Subscription.diagnosticOf(dataReportToSend),
      interactionFlags: Diagnostic.asFlags({
        empty: !dataReportToSend.attributeReports?.length && !dataReportToSend.eventReports?.length,
        suppressResponse: dataReportToSend.suppressResponse,
        moreChunkedMessages: dataReportToSend.moreChunkedMessages
      }),
      attr: dataReportToSend.attributeReports?.length,
      ev: dataReportToSend.eventReports?.length
    };
    if (dataReportToSend.suppressResponse) {
      try {
        await this.exchange.send(5 /* ReportData */, encodedMessage, {
          expectAckOnly: true,
          disableMrpLogic: !waitForAck,
          logContext
        });
      } catch (e) {
        UnexpectedMessageError.accept(e);
        const { receivedMessage } = e;
        this.throwIfErrorStatusMessage(receivedMessage);
      }
    } else {
      await this.exchange.send(5 /* ReportData */, encodedMessage, {
        disableMrpLogic: !waitForAck,
        logContext
      });
      await this.waitForSuccess("DataReport", { timeout: waitForAck ? void 0 : Millis(500) });
    }
  }
  /**
   * Convert a server interaction report to a DataReport entry
   * TODO remove when anything is migrated completely
   */
  static convertServerInteractionReport(report) {
    switch (report.kind) {
      case "attr-value": {
        const { path, value: payload, version: dataVersion, tlv } = report;
        if (tlv === void 0) {
          throw new InternalError(`Attribute ${path.clusterId}/${path.attributeId} not found`);
        }
        const data = {
          attributeData: {
            path,
            payload,
            tlv,
            dataVersion
          },
          hasFabricSensitiveData: true
          // With this we disable the validation for missing data in encoding, we trust behavior logic
        };
        return data;
      }
      case "attr-status": {
        const { path, status, clusterStatus } = report;
        const statusReport = {
          attributeStatus: {
            path,
            status: { status }
          },
          hasFabricSensitiveData: false
        };
        if (clusterStatus !== void 0) {
          statusReport.attributeStatus.status.clusterStatus = clusterStatus;
        }
        return statusReport;
      }
      case "event-value": {
        const { path, value: payload, number: eventNumber, priority, timestamp: epochTimestamp, tlv } = report;
        const data = {
          eventData: {
            path,
            eventNumber,
            priority,
            epochTimestamp,
            payload,
            tlv
          },
          hasFabricSensitiveData: true
          // There are no Fabric sensitive events as of now. If ever added sanitizing needs to be added
        };
        return data;
      }
      case "event-status": {
        const { path, status, clusterStatus } = report;
        const statusReport = {
          eventStatus: {
            path,
            status: { status }
          },
          hasFabricSensitiveData: false
        };
        if (clusterStatus !== void 0) {
          statusReport.eventStatus.status.clusterStatus = clusterStatus;
        }
        return statusReport;
      }
    }
  }
  /**
   * Send a WriteResponse message.
   */
  async sendWriteResponse(response, options) {
    await this.send(7 /* WriteResponse */, TlvWriteResponse.encode(response), {
      logContext: options?.logContext ? { for: options.logContext } : void 0
    });
  }
  /**
   * Wait for and decode the next WriteRequest message (for chunked writes).
   */
  async readNextWriteRequest() {
    const message = await this.nextMessage(6 /* WriteRequest */, void 0, "WriteRequest-chunk");
    return {
      writeRequest: TlvWriteRequest.decode(message.payload),
      message
    };
  }
  /**
   * Send an intermediate InvokeResponse chunk with moreChunkedMessages=true and wait for Status.Success.
   * Returns true if a client acknowledged, and we should continue, false if a client terminated the chunked series.
   */
  async sendInvokeResponseChunk(response) {
    await this.send(
      9 /* InvokeResponse */,
      TlvInvokeResponseForSend.encode({
        ...response,
        moreChunkedMessages: true
      }),
      {
        logContext: { for: "InvokeResponse-chunk" }
      }
    );
    try {
      await this.waitForSuccess("InvokeResponse-chunk");
      return true;
    } catch (error) {
      logger.debug("Chunked invoke response terminated by client", error);
      return false;
    }
  }
  /**
   * Send the final InvokeResponse (without moreChunkedMessages flag).
   */
  async sendInvokeResponse(response) {
    await this.send(9 /* InvokeResponse */, TlvInvokeResponseForSend.encode(response));
  }
}
class IncomingInteractionClientMessenger extends InteractionMessenger {
  async waitFor(expectedMessageInfo, messageType, options) {
    const message = await this.anyNextMessage(expectedMessageInfo, options);
    const {
      payloadHeader: { messageType: receivedMessageType }
    } = message;
    if (receivedMessageType !== messageType) {
      if (receivedMessageType === 1 /* StatusResponse */) {
        const statusCode = TlvStatusResponse.decode(message.payload).status;
        throw new ReceivedStatusResponseError(`Received status response ${statusCode}`, statusCode);
      }
      throw new MatterFlowError(
        `Received unexpected message type ${receivedMessageType.toString(16)}. Expected ${messageType.toString(
          16
        )}`
      );
    }
    return message;
  }
  /**
   * Read data reports as they come in on the wire.
   *
   * Data reports payloads are decoded but list attributes may be split across messages; these will require reassembly.
   */
  async *readDataReports(options) {
    while (true) {
      const dataReportMessage = await this.waitFor("DataReport", 5 /* ReportData */, options);
      const report = TlvDataReport.decode(dataReportMessage.payload);
      yield report;
      if (report.moreChunkedMessages) {
        await this.sendStatus(Status.Success, {
          multipleMessageInteraction: true,
          logContext: this.#logContextOf(report)
        });
      } else if (!report.suppressResponse) {
        try {
          this.trackEndMessage(
            this.sendStatus(Status.Success, {
              multipleMessageInteraction: true,
              logContext: this.#logContextOf(report)
            }),
            "Error sending success after final data report chunk"
          );
        } catch (e) {
          logger.info(this.exchange.via, `Error reading successful datareport read to peer:`, e);
        }
      }
      if (!report.moreChunkedMessages) {
        break;
      }
    }
  }
  #logContextOf(report) {
    return {
      subId: report.subscriptionId === void 0 ? void 0 : Subscription.idStrOf(report.subscriptionId),
      dataReportFlags: Diagnostic.asFlags({
        empty: !report.attributeReports?.length && !report.eventReports?.length,
        suppressResponse: report.suppressResponse,
        moreChunkedMessages: report.moreChunkedMessages
      }),
      attr: report.attributeReports?.length,
      ev: report.eventReports?.length
    };
  }
}
class InteractionClientMessenger extends IncomingInteractionClientMessenger {
  static async create(exchangeProvider, options) {
    const exchange = await exchangeProvider.initiateExchange(options);
    return new this(exchange);
  }
  async sendReadRequest(readRequest, options) {
    await this.send(2 /* ReadRequest */, this.#encodeReadingRequest(TlvReadRequest, readRequest), options);
  }
  #encodeReadingRequest(schema, request) {
    const encoded = schema.encode(request);
    if (encoded.byteLength <= this.exchange.maxPayloadSize) {
      return encoded;
    }
    const originalDataVersionFilters = [...request.dataVersionFilters ?? []];
    const requestWithoutDataVersionFilters = schema.encode({
      ...request,
      dataVersionFilters: []
    });
    if (requestWithoutDataVersionFilters.byteLength > this.exchange.maxPayloadSize) {
      throw new MatterFlowError(
        `Request is too long to fit in a single chunk, This should not happen! Data: ${Diagnostic.json(request)}`
      );
    }
    return schema.encode({
      ...request,
      dataVersionFilters: this.#shortenDataVersionFilters(
        originalDataVersionFilters,
        this.exchange.maxPayloadSize - requestWithoutDataVersionFilters.byteLength
      )
    });
  }
  #shortenDataVersionFilters(originalDataVersionFilters, availableBytes) {
    const dataVersionFilters = new Array();
    while (availableBytes > 0 && originalDataVersionFilters.length > 0) {
      const dataVersionFilter = originalDataVersionFilters.shift();
      if (dataVersionFilter === void 0) {
        break;
      }
      const encodedDataVersionFilter = TlvDataVersionFilter.encode(dataVersionFilter);
      const encodedDataVersionFilterLength = encodedDataVersionFilter.byteLength;
      if (encodedDataVersionFilterLength > availableBytes) {
        originalDataVersionFilters.unshift(dataVersionFilter);
        break;
      }
      dataVersionFilters.push(dataVersionFilter);
      availableBytes -= encodedDataVersionFilterLength;
    }
    logger.debug(
      `Removed ${originalDataVersionFilters.length} DataVersionFilters from Request to fit into a single message`
    );
    return dataVersionFilters;
  }
  async sendSubscribeRequest(subscribeRequest, options) {
    const request = this.#encodeReadingRequest(TlvSubscribeRequest, subscribeRequest);
    await this.send(3 /* SubscribeRequest */, request, options);
  }
  /**
   * Send an invoke command and handle chunked responses.
   * Returns a combined InvokeResponse with all responses from all chunks, or undefined if suppressResponse
   */
  async sendInvokeCommand(invokeRequest, options) {
    if (invokeRequest.suppressResponse) {
      await this.requestWithSuppressedResponse(
        8 /* InvokeRequest */,
        TlvInvokeRequest,
        invokeRequest,
        options
      );
      return void 0;
    }
    await this.send(8 /* InvokeRequest */, TlvInvokeRequest.encode(invokeRequest), options);
    const allInvokeResponses = [];
    let finalResponse;
    while (true) {
      const responseMessage = await this.nextMessage(9 /* InvokeResponse */, options, "InvokeResponse");
      const response = TlvInvokeResponse.decode(responseMessage.payload);
      if (response.invokeResponses) {
        allInvokeResponses.push(...response.invokeResponses);
      }
      if (response.moreChunkedMessages) {
        await this.sendStatus(Status.Success, {
          multipleMessageInteraction: true,
          logContext: { for: "InvokeResponse-chunk" }
        });
      } else {
        finalResponse = {
          ...response,
          invokeResponses: allInvokeResponses
        };
        break;
      }
    }
    return finalResponse;
  }
  async sendWriteCommand(writeRequest, options) {
    if (writeRequest.suppressResponse) {
      await this.requestWithSuppressedResponse(6 /* WriteRequest */, TlvWriteRequest, writeRequest, options);
    } else {
      return await this.request(
        6 /* WriteRequest */,
        TlvWriteRequest,
        7 /* WriteResponse */,
        TlvWriteResponse,
        writeRequest
      );
    }
  }
  sendTimedRequest(timeout, options) {
    return this.request(
      10 /* TimedRequest */,
      TlvTimedRequest,
      1 /* StatusResponse */,
      TlvStatusResponse,
      {
        timeout,
        interactionModelRevision: Specification.INTERACTION_MODEL_REVISION
      },
      options
    );
  }
  async requestWithSuppressedResponse(requestMessageType, requestSchema, request, options) {
    try {
      await this.send(requestMessageType, requestSchema.encode(request), {
        expectAckOnly: true,
        expectedProcessingTime: options?.expectedProcessingTime,
        abort: options?.abort,
        logContext: {
          invokeFlags: Diagnostic.asFlags({
            suppressResponse: true
          })
        }
      });
    } catch (e) {
      UnexpectedMessageError.accept(e);
      const { receivedMessage } = e;
      this.throwIfErrorStatusMessage(receivedMessage);
    }
  }
  async request(requestMessageType, requestSchema, responseMessageType, responseSchema, request, options) {
    await this.send(requestMessageType, requestSchema.encode(request), {
      expectAckOnly: false,
      expectedProcessingTime: options?.expectedProcessingTime,
      abort: options?.abort
    });
    const responseMessage = await this.nextMessage(
      responseMessageType,
      {
        expectedProcessingTime: options?.expectedProcessingTime,
        abort: options?.abort
      },
      MessageType[responseMessageType] ?? `Response-${Diagnostic.hex(responseMessageType)}`
    );
    return responseSchema.decode(responseMessage.payload);
  }
}
export {
  IncomingInteractionClientMessenger,
  InteractionClientMessenger,
  InteractionServerMessenger,
  MessageType
};
//# sourceMappingURL=InteractionMessenger.js.map
