/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { TransientPeerCommunicationError } from "#peer/PeerCommunicationError.js";
import {
  DataReader,
  DataWriter,
  Diagnostic,
  Endian,
  Millis,
  Seconds,
  UnexpectedDataError
} from "@matter/general";
import { GeneralStatusCode, SecureChannelStatusCode, SecureMessageType } from "@matter/types";
import { SecureChannelStatusMessage } from "./SecureChannelStatusMessageSchema.js";
class ChannelStatusResponseError extends TransientPeerCommunicationError {
  constructor(message, generalStatusCode, protocolStatusCode, vendorId, protocolData) {
    super(
      `(${GeneralStatusCode[generalStatusCode]} (${generalStatusCode}) / ${SecureChannelStatusCode[protocolStatusCode]} (${protocolStatusCode})) ${message}`
    );
    this.generalStatusCode = generalStatusCode;
    this.protocolStatusCode = protocolStatusCode;
    this.vendorId = vendorId;
    this.protocolData = protocolData;
    if (generalStatusCode === GeneralStatusCode.Busy && protocolStatusCode === SecureChannelStatusCode.Busy && protocolData?.byteLength === 2) {
      this.busyDelay = Millis(new DataReader(protocolData, Endian.Little).readUInt16());
    }
  }
  busyDelay;
}
const EXPECTED_CRYPTO_PROCESSING_TIME = Seconds(35);
const DEFAULT_NORMAL_PROCESSING_TIME = Seconds(2);
class SecureChannelMessenger {
  constructor(exchange, defaultExpectedProcessingTime = EXPECTED_CRYPTO_PROCESSING_TIME) {
    this.exchange = exchange;
    this.#defaultExpectedProcessingTime = defaultExpectedProcessingTime;
  }
  #defaultExpectedProcessingTime;
  get channel() {
    return this.exchange.channel;
  }
  get via() {
    return this.exchange.via;
  }
  /**
   * Waits for the next message and returns it.
   *
   * {@link expectedProcessingTime} defaults to {@link EXPECTED_CRYPTO_PROCESSING_TIME}.
   */
  async nextMessage({
    type,
    expectedProcessingTime = this.#defaultExpectedProcessingTime,
    description,
    abort
  } = {}) {
    const message = await this.exchange.nextMessage({ expectedProcessingTime, abort });
    const messageType = message.payloadHeader.messageType;
    if (type !== void 0 && description === void 0) {
      description = SecureMessageType[type];
    }
    this.throwIfErrorStatusReport(message, description);
    if (type !== void 0 && messageType !== type)
      throw new UnexpectedDataError(
        `Received unexpected message type: ${messageType}, expected: ${type} (${description})`
      );
    return message;
  }
  /**
   * Waits for the next message and decodes it.
   *
   * When no expectedProcessingTimeMs is provided, the default value of EXPECTED_CRYPTO_PROCESSING_TIME is used.
   */
  async nextMessageDecoded(schema, options) {
    return schema.decode((await this.nextMessage(options)).payload);
  }
  /**
   * Waits for the next message and returns it.
   *
   * When no expectedProcessingTimeMs is provided, the default value of EXPECTED_CRYPTO_PROCESSING_TIME is used.
   */
  async waitForSuccess(options) {
    await this.nextMessage({
      ...options,
      type: SecureMessageType.StatusReport
    });
  }
  /**
   * Sends a message of the given type with the given payload.
   *
   * If no ExchangeSendOptions are provided, the expectedProcessingTimeMs will be set to
   * EXPECTED_CRYPTO_PROCESSING_TIME.
   */
  async send(message, type, schema, options) {
    options = {
      ...options,
      expectedProcessingTime: options?.expectedProcessingTime ?? this.#defaultExpectedProcessingTime
    };
    const payload = schema.encode(message);
    await this.exchange.send(type, payload, options);
    return payload;
  }
  sendError(code, abort) {
    return this.#sendStatusReport(GeneralStatusCode.Failure, code, abort);
  }
  sendSuccess(abort) {
    return this.#sendStatusReport(GeneralStatusCode.Success, SecureChannelStatusCode.Success, abort);
  }
  sendBusy(minimumRetryInterval, abort) {
    if (minimumRetryInterval <= 0) {
      throw new Error("Busy minimum retry interval must be greater than 0ms");
    }
    const writer = new DataWriter(Endian.Little);
    writer.writeUInt16(Math.min(minimumRetryInterval, 65535));
    return this.#sendStatusReport(
      GeneralStatusCode.Busy,
      SecureChannelStatusCode.Busy,
      abort,
      void 0,
      writer.toByteArray()
    );
  }
  sendCloseSession(abort) {
    return this.#sendStatusReport(GeneralStatusCode.Success, SecureChannelStatusCode.CloseSession, abort, false);
  }
  get channelName() {
    return this.exchange.channel.channel.name;
  }
  [Symbol.asyncDispose]() {
    return this.close();
  }
  async close() {
    await this.exchange.close();
  }
  async #sendStatusReport(generalStatus, protocolStatus, abort, requiresAck, protocolData) {
    await this.exchange.send(
      SecureMessageType.StatusReport,
      SecureChannelStatusMessage.encode({
        generalStatus,
        protocolStatus,
        protocolData
      }),
      {
        requiresAck,
        logContext: {
          generalStatus: GeneralStatusCode[generalStatus] ?? Diagnostic.hex(generalStatus),
          protocolStatus: SecureChannelStatusCode[protocolStatus] ?? Diagnostic.hex(protocolStatus)
        },
        abort
      }
    );
  }
  throwIfErrorStatusReport(message, logHint) {
    const {
      payloadHeader: { messageType },
      payload
    } = message;
    if (messageType !== SecureMessageType.StatusReport) return;
    const { generalStatus, protocolId, protocolStatus, vendorId, protocolData } = SecureChannelStatusMessage.decode(payload);
    if (generalStatus !== GeneralStatusCode.Success) {
      throw new ChannelStatusResponseError(
        `Received general error status for protocol ${protocolId}${logHint ? ` (${logHint})` : ""}`,
        generalStatus,
        protocolStatus,
        vendorId,
        protocolData
      );
    }
    if (protocolStatus !== SecureChannelStatusCode.Success) {
      throw new ChannelStatusResponseError(
        `Received general success status, but protocol status is not Success${logHint ? ` (${logHint})` : ""}`,
        generalStatus,
        protocolStatus
      );
    }
  }
}
export {
  ChannelStatusResponseError,
  DEFAULT_NORMAL_PROCESSING_TIME,
  EXPECTED_CRYPTO_PROCESSING_TIME,
  SecureChannelMessenger
};
//# sourceMappingURL=SecureChannelMessenger.js.map
