/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { SecureSession } from "#session/SecureSession.js";
import { AsyncObservable, Diagnostic, Logger, Observable } from "@matter/general";
import { BdxMessageType, BdxStatusCode } from "@matter/types";
import { bdxSessionInitiator } from "./bdx-session-initiator.js";
import { BdxError } from "./BdxError.js";
import { BdxSessionConfiguration } from "./BdxSessionConfiguration.js";
import { DrivenSendingFlow } from "./flow/DrivenSendingFlow.js";
import { DrivingReceivingFlow } from "./flow/DrivingReceivingFlow.js";
import { Flow } from "./flow/Flow.js";
import { FollowingReceivingFlow } from "./flow/FollowingReceivingFlow.js";
import { FollowingSendingFlow } from "./flow/FollowingSendingFlow.js";
const logger = Logger.get("BdxSession");
class BdxSession {
  #messenger;
  #started = false;
  #closed = AsyncObservable();
  #isClosed = false;
  #config;
  #transferFlow;
  #progressInfo = Observable();
  #progressFinished = Observable();
  #progressCancelled = Observable();
  /** Initializes a BdxSession as a sender, means that we upload data to the peer. */
  static asSender(messenger, options) {
    return new BdxSession(messenger, { isSender: true, ...options });
  }
  /** Initializes a BdxSession as a receiver, means that we download data from the peer. */
  static asReceiver(messenger, options) {
    return new BdxSession(messenger, { isSender: false, ...options });
  }
  /**
   * Initializes a BdxSession from an incoming *Init message. The message determines the direction of the transfer.
   * The provided storages allow to map the file designator to a storage context depending on the path prefix in the
   * file designator.
   */
  static fromMessage(messenger, options) {
    const { initMessageType } = options;
    if (initMessageType !== BdxMessageType.SendInit && initMessageType !== BdxMessageType.ReceiveInit) {
      throw new BdxError(
        `Invalid message type for BDX session initialization: ${BdxMessageType[initMessageType]} (${initMessageType})`,
        BdxStatusCode.UnexpectedMessage
      );
    }
    return new BdxSession(messenger, {
      isSender: initMessageType === BdxMessageType.ReceiveInit,
      ...options
    });
  }
  constructor(messenger, options) {
    this.#messenger = messenger;
    this.#config = new BdxSessionConfiguration(options);
    const exchange = messenger.exchange;
    if (!exchange.session.isSecure) {
      throw new BdxError("Bdx Protocol requires a secure session.");
    }
    exchange.closed.on(async () => {
      if (this.#isClosed) {
        return;
      }
      logger.debug(`Closing BDX session for exchange ${exchange.id}`);
      await this.close();
    });
  }
  get progressInfo() {
    return this.#progressInfo;
  }
  get progressFinished() {
    return this.#progressFinished;
  }
  get progressCancelled() {
    return this.#progressCancelled;
  }
  /** Method called to start the session. It will end with a successful Transfer or with an error */
  async processTransfer() {
    if (this.#started) {
      throw new BdxError("BDX session already started", BdxStatusCode.UnexpectedMessage);
    }
    if (this.#isClosed) {
      throw new BdxError("BDX session already closed", BdxStatusCode.UnexpectedMessage);
    }
    const { isSender, isInitiator, fileDesignator } = this.#config;
    logger.info(
      `Starting BDX session`,
      Diagnostic.dict({
        exId: this.#messenger.exchange.id,
        isSender,
        isInitiator,
        blobName: fileDesignator?.text
      })
    );
    this.#started = true;
    let transferError;
    try {
      this.#transferFlow = this.#initializeFlow(await bdxSessionInitiator(this.#messenger, this.#config));
      this.#transferFlow.progressInfo.on(
        (bytesTransferred, totalBytesLength) => this.#progressInfo.emit(bytesTransferred, totalBytesLength)
      );
      this.#transferFlow.progressFinished.on(
        (totalBytesTransferred) => this.#progressFinished.emit(totalBytesTransferred)
      );
      this.#transferFlow.progressCancelled.on(() => this.#progressCancelled.emit());
      await this.#transferFlow.processTransfer();
    } catch (error) {
      transferError = error;
      if (BdxError.is(error)) {
        await this.#messenger.sendError(error.code).catch((sendError) => {
          logger.info(`Failed to send BDX error status to peer:`, Diagnostic.errorMessage(sendError));
        });
        logger.warn(`BDX session failed with error:`, error);
        error.bytesTransferred = this.#transferFlow?.transferredBytes ?? 0;
        error.totalBytesLength = this.#transferFlow?.dataLength;
      }
      throw error;
    } finally {
      await this.close(transferError);
    }
  }
  #initializeFlow(transferParameters) {
    const { transferMode, asynchronousTransfer, dataLength, isDriver, fileDesignator } = transferParameters;
    const isSenderDrive = transferMode === Flow.DriverMode.SenderDrive;
    const role = `${isSenderDrive ? `${asynchronousTransfer ? "async " : ""}sending` : "receiving"} ${isDriver ? "driver" : "follower"}`;
    logger.debug(
      `Starting transfer flow as ${role}`,
      Diagnostic.dict({
        exId: this.#messenger.exchange.id,
        dataLength,
        blobName: fileDesignator.text
      })
    );
    let FlowImpl;
    if (isDriver) {
      if (isSenderDrive || asynchronousTransfer) {
        FlowImpl = DrivenSendingFlow;
      } else {
        FlowImpl = DrivingReceivingFlow;
      }
    } else if (isSenderDrive || asynchronousTransfer) {
      FlowImpl = FollowingSendingFlow;
    } else {
      FlowImpl = FollowingReceivingFlow;
    }
    return new FlowImpl(this.#messenger, transferParameters);
  }
  get closed() {
    return this.#closed;
  }
  get peerAddress() {
    SecureSession.assert(this.#messenger.exchange.session);
    return this.#messenger.exchange.session.peerAddress;
  }
  get session() {
    SecureSession.assert(this.#messenger.exchange.session);
    return this.#messenger.exchange.session;
  }
  async close(error) {
    if (this.#isClosed) {
      return;
    }
    this.#isClosed = true;
    await this.#transferFlow?.close(error);
    await this.#closed.emit();
  }
}
export {
  BdxSession
};
//# sourceMappingURL=BdxSession.js.map
