/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { BdxError } from "#bdx/BdxError.js";
import { FileDesignator } from "#bdx/FileDesignator.js";
import { PersistedFileDesignator } from "#bdx/PersistedFileDesignator.js";
import { PeerAddress } from "#peer/PeerAddress.js";
import { ExchangeManager } from "#protocol/ExchangeManager.js";
import { SecureSession } from "#session/SecureSession.js";
import { Environmental, isDeepEqual, Logger, MatterError, Observable } from "@matter/general";
import { BDX_PROTOCOL_ID, BdxMessageType, BdxStatusCode } from "@matter/types";
import { BdxMessenger } from "./BdxMessenger.js";
import { BdxSession } from "./BdxSession.js";
import { BdxInitMessageSchema } from "./schema/BdxInitMessagesSchema.js";
const logger = Logger.get("BdxProtocol");
class BdxProtocol {
  id = BDX_PROTOCOL_ID;
  requiresSecureSession = true;
  #activeBdxSessions = /* @__PURE__ */ new Map();
  #peerScopes = /* @__PURE__ */ new Map();
  #sessionStarted = Observable();
  #sessionClosed = Observable();
  static [Environmental.create](env) {
    const instance = new BdxProtocol();
    env.get(ExchangeManager).addProtocolHandler(instance);
    env.set(BdxProtocol, instance);
    return instance;
  }
  get sessionStarted() {
    return this.#sessionStarted;
  }
  get sessionClosed() {
    return this.#sessionClosed;
  }
  #peerScopeKey(peer, scope) {
    if (scope === void 0) {
      throw new MatterError("StorageContext must have a defined scope to be used with enableScope");
    }
    return `${peer.toString()}-${scope}`;
  }
  enablePeerForScope(peer, storage, config) {
    const peerScopeStr = this.#peerScopeKey(peer, storage.scope);
    const peerDetails = this.#peerScopes.get(peerScopeStr);
    if (peerDetails !== void 0) {
      const { storage: existingStorage, config: existingConfig } = peerDetails;
      if (existingStorage !== storage || !isDeepEqual(config, existingConfig)) {
        logger.warn(
          "Cannot enable BDX for peer",
          peer,
          "with different storage or config:",
          config,
          "vs",
          existingConfig
        );
        return false;
      }
    } else {
      this.#peerScopes.set(peerScopeStr, { peer, storage, config });
    }
    return true;
  }
  async disablePeerForScope(peer, storage, force = false) {
    if (this.#activeBdxSessions.size > 0) {
      for (const { session, scope } of this.#activeBdxSessions.values()) {
        if (storage.scope === scope && PeerAddress.is(peer, session.peerAddress)) {
          if (force) {
            await session.close(new MatterError("BDX protocol scope disabled"));
            break;
          }
          throw new MatterError(
            `Cannot disable BDX for peer "${peer}" (scope: ${scope}) because there are active BDX sessions using it`
          );
        }
      }
    }
    this.#peerScopes.delete(this.#peerScopeKey(peer, storage.scope));
  }
  async onNewExchange(exchange, message) {
    const initMessageType = message.payloadHeader.messageType;
    SecureSession.assert(exchange.session);
    switch (initMessageType) {
      case BdxMessageType.SendInit:
      case BdxMessageType.ReceiveInit:
        let messenger = void 0;
        try {
          logger.debug(
            `Initialize Session for ${BdxMessageType[initMessageType]} message on BDX protocol for exchange ${exchange.id}`
          );
          await exchange.nextMessage();
          const { payload } = message;
          const initMessage = new BdxInitMessageSchema().decode(payload);
          const { fileDesignator: messageFileDesignator } = initMessage;
          const fd = new FileDesignator(messageFileDesignator);
          const [storageScope, fileDesignator] = fd.text.split("/");
          const { storage, config } = this.#peerScopes.get(this.#peerScopeKey(exchange.session.peerAddress, storageScope)) ?? {};
          if (storage === void 0 || fileDesignator === void 0) {
            throw new BdxError(
              `No storage context found for BDX file designator "${fd.text}"`,
              BdxStatusCode.FileDesignatorUnknown
            );
          }
          messenger = new BdxMessenger(exchange, config?.messageTimeout);
          const bdxSession = BdxSession.fromMessage(messenger, {
            initMessageType,
            initMessage,
            fileDesignator: new PersistedFileDesignator(fileDesignator, storage),
            ...config
          });
          await this.#registerSession(messenger, bdxSession, storageScope);
          try {
            await bdxSession.processTransfer();
          } catch (error) {
            logger.error(`Error processing BDX transfer:`, error);
          }
        } catch (error) {
          BdxError.accept(error);
          await (messenger ?? new BdxMessenger(exchange)).sendError(error.code);
          logger.warn(`BDX session failed with error:`, error);
          return;
        }
        break;
      default:
        logger.warn(
          `Unexpected BDX message type ${BdxMessageType[initMessageType]} on new exchange ${exchange.id}`
        );
        await new BdxMessenger(exchange).sendError(BdxStatusCode.UnexpectedMessage);
    }
  }
  /** Make sure only one BDX session can be active per exchange and that the exchange is closed with the BDX session. */
  async #registerSession(messenger, bdxSession, scope) {
    const exchange = messenger.exchange;
    if (this.#activeBdxSessions.has(exchange)) {
      logger.warn(`BDX session for exchange ${exchange.id} already exists, sending error`);
      await messenger.sendError(BdxStatusCode.UnexpectedMessage);
      return;
    }
    this.#activeBdxSessions.set(exchange, { session: bdxSession, scope });
    this.#sessionStarted.emit(bdxSession, scope);
    bdxSession.closed.on(async () => {
      logger.debug(`BDX session for exchange ${exchange.id} closed`);
      this.#activeBdxSessions.delete(exchange);
      await exchange.close();
      this.#sessionClosed.emit(bdxSession, scope);
    });
  }
  async close() {
    logger.debug(`Closing BDX protocol handler with ${this.#activeBdxSessions.size} active sessions`);
    for (const { session } of this.#activeBdxSessions.values()) {
      await session.close(new MatterError("BDX protocol handler closed"));
    }
    this.#activeBdxSessions.clear();
  }
  sessionFor(peerAddress, scope) {
    for (const { session, scope: activeScope } of this.#activeBdxSessions.values()) {
      if (PeerAddress.is(peerAddress, session.peerAddress) && activeScope === scope) {
        return session;
      }
    }
    return void 0;
  }
}
export {
  BdxProtocol
};
//# sourceMappingURL=BdxProtocol.js.map
