"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __using = (stack, value, async) => {
  if (value != null) {
    if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
    var dispose, inner;
    if (async) dispose = value[__knownSymbol("asyncDispose")];
    if (dispose === void 0) {
      dispose = value[__knownSymbol("dispose")];
      if (async) inner = dispose;
    }
    if (typeof dispose !== "function") __typeError("Object not disposable");
    if (inner) dispose = function() {
      try {
        inner.call(this);
      } catch (e) {
        return Promise.reject(e);
      }
    };
    stack.push([async, dispose, value]);
  } else if (async) {
    stack.push([async]);
  }
  return value;
};
var __callDispose = (stack, error, hasError) => {
  var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
    return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
  };
  var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
  var next = (it) => {
    while (it = stack.pop()) {
      try {
        var result = it[1] && it[1].call(it[2]);
        if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
      } catch (e) {
        fail(e);
      }
    }
    if (hasError) throw error;
  };
  return next();
};
var Session_exports = {};
__export(Session_exports, {
  Session: () => Session
});
module.exports = __toCommonJS(Session_exports);
var import_errors = require("#protocol/errors.js");
var import_MessageChannel = require("#protocol/MessageChannel.js");
var import_SessionIntervals = require("#session/SessionIntervals.js");
var import_general = require("@matter/general");
var import_SessionParameters = require("./SessionParameters.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("Session");
class Session {
  #channel;
  #lifetime;
  #manager;
  timestamp = import_general.Time.nowMs;
  createdAt = import_general.Time.nowMs;
  activeTimestamp = 0;
  #closing = (0, import_general.ObservableValue)();
  #gracefulClose = (0, import_general.AsyncObservable)();
  #exchanges = /* @__PURE__ */ new Set();
  deferredClose = false;
  idleInterval;
  activeInterval;
  activeThreshold;
  dataModelRevision;
  interactionModelRevision;
  specificationVersion;
  maxPathsPerInvoke;
  messageCounter;
  messageReceptionState;
  supportedTransports;
  maxTcpMessageSize;
  constructor(config) {
    const { manager, channel, messageCounter, messageReceptionState, sessionParameters, setActiveTimestamp } = config;
    const {
      idleInterval,
      activeInterval,
      activeThreshold,
      dataModelRevision,
      interactionModelRevision,
      specificationVersion,
      maxPathsPerInvoke,
      supportedTransports,
      maxTcpMessageSize
    } = (0, import_SessionParameters.SessionParameters)(sessionParameters);
    this.#manager = manager;
    if (channel) {
      this.#channel = new import_MessageChannel.MessageChannel(channel, this);
    }
    this.messageCounter = messageCounter;
    this.messageReceptionState = messageReceptionState;
    this.idleInterval = idleInterval;
    this.activeInterval = activeInterval;
    this.activeThreshold = activeThreshold;
    this.dataModelRevision = dataModelRevision;
    this.interactionModelRevision = interactionModelRevision;
    this.specificationVersion = specificationVersion;
    this.maxPathsPerInvoke = maxPathsPerInvoke;
    this.supportedTransports = supportedTransports;
    this.maxTcpMessageSize = maxTcpMessageSize;
    if (setActiveTimestamp) {
      this.activeTimestamp = this.timestamp;
    }
  }
  get exchanges() {
    return this.#exchanges;
  }
  addExchange(exchange) {
    this.#exchanges.add(exchange);
  }
  deleteExchange(exchange) {
    this.#exchanges.delete(exchange);
  }
  get closing() {
    return this.#closing;
  }
  notifyActivity(messageReceived) {
    this.timestamp = import_general.Time.nowMs;
    if (messageReceived) {
      this.activeTimestamp = this.timestamp;
    }
  }
  get isPeerActive() {
    return (0, import_general.Timespan)(this.activeTimestamp, import_general.Time.nowMs).duration < this.activeThreshold;
  }
  getIncrementedMessageCounter() {
    return this.messageCounter.getIncrementedCounter();
  }
  updateMessageCounter(messageCounter, _sourceNodeId, _operationalKey) {
    if (this.messageReceptionState === void 0) {
      throw new import_general.InternalError("MessageReceptionState is not defined for this session");
    }
    this.messageReceptionState.updateMessageCounter(messageCounter);
  }
  /**
   * Emits on graceful close.
   *
   * During normal operation this should trigger a close message to notify the peer of closure.
   */
  get gracefulClose() {
    return this.#gracefulClose;
  }
  /**
   * Once set this flag prevents establishment of new exchanges.
   */
  get isClosing() {
    return this.#closing.value || this.deferredClose;
  }
  static generateNonce(securityFlags, messageId, nodeId) {
    const writer = new import_general.DataWriter(import_general.Endian.Little);
    writer.writeUInt8(securityFlags);
    writer.writeUInt32(messageId);
    writer.writeUInt64(nodeId);
    return writer.toByteArray();
  }
  /**
   * The peer's session parameters.
   */
  get parameters() {
    const {
      idleInterval,
      activeInterval,
      activeThreshold,
      dataModelRevision,
      interactionModelRevision,
      specificationVersion,
      maxPathsPerInvoke,
      supportedTransports,
      maxTcpMessageSize
    } = this;
    return {
      idleInterval,
      activeInterval,
      activeThreshold,
      dataModelRevision,
      interactionModelRevision,
      specificationVersion,
      maxPathsPerInvoke,
      supportedTransports,
      maxTcpMessageSize
    };
  }
  /**
   * Allows updating the Session timing parameters based on received information from the peer during PASE/CASE initialization
   */
  set timingParameters(intervals) {
    const { idleInterval, activeInterval, activeThreshold } = (0, import_SessionIntervals.SessionIntervals)(intervals);
    this.idleInterval = idleInterval;
    this.activeInterval = activeInterval;
    this.activeThreshold = activeThreshold;
  }
  static idStrOf(source) {
    let id;
    if (typeof source === "number") {
      id = source;
    } else {
      if ("header" in source) {
        id = source.header?.sessionId;
      } else if ("sessionId" in source) {
        id = source.sessionId;
      }
      if (typeof id !== "number") {
        return "?";
      }
    }
    return import_general.hex.word(id);
  }
  /**
   * Close the session.
   *
   * This begins the close process.  {@link shutdownLogic} is logic that should run once the session no longer accepts
   * new exchanges.  It may set {@link deferredClose} to prevent the close from proceeding until all exchanges are
   * finished.  Otherwise, the close will proceed immediately.
   */
  async initiateClose(shutdownLogic) {
    if (this.isClosing) {
      return;
    }
    this.#lifetime?.closing();
    this.#closing.emit(true);
    await shutdownLogic?.();
    if (this.deferredClose && this.hasActiveExchanges) {
      return;
    }
    await this.close();
  }
  /**
   * Force-close the session.
   *
   * This terminates (potentially) subscriptions and exchanges without notifying peers.  It places the session in a
   * closing state so no further exchanges are accepted.
   *
   * TODO refactor when moving subscriptions away from sessions
   */
  async initiateForceClose(context) {
    await this.initiateClose(async () => {
      if (!context.keepSubscriptions) {
        await this.closeSubscriptions();
      }
      for (const exchange of this.#exchanges) {
        if (exchange === context.currentExchange) {
          this.deferredClose = true;
          continue;
        }
        await exchange.close(context.cause);
      }
    });
  }
  get isClosed() {
    return !this.#channel;
  }
  /**
   * The {@link MessageChannel} other components use for session communication.
   */
  get channel() {
    if (this.#channel === void 0) {
      throw new import_errors.SessionClosedError(`Session ${this.via} ended`);
    }
    return this.#channel;
  }
  get usesMrp() {
    return this.supportsMRP && !this.#channel?.isReliable;
  }
  get supportsLargeMessages() {
    return this.#channel !== void 0 && !!this.#channel?.supportsLargeMessages;
  }
  get hasActiveExchanges() {
    return !!this.#exchanges.size;
  }
  async closeSubscriptions(_cancelledByPeer = false) {
    return 0;
  }
  detachChannel() {
    const channel = this.#channel;
    this.#channel = void 0;
    logger.info(this.via, "Channel detached");
    return channel;
  }
  async close() {
    var _stack = [];
    try {
      const _closing = __using(_stack, this.#lifetime?.closing());
      if (this.#channel) {
        await this.#channel.close();
        this.#channel = void 0;
        logger.info(this.via, "Session ended");
      }
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  async [Symbol.asyncDispose]() {
    await this.close();
  }
  get manager() {
    return this.#manager;
  }
  set channel(channel) {
    if (this.#channel !== void 0) {
      throw new import_general.ImplementationError("Cannot replace active channel");
    }
    this.#channel = channel;
  }
  join(...name) {
    return this.activate().join(...name);
  }
  /**
   * Invoked by the manager when the session is "live".
   *
   * This is separate from construction because we sometimes discard sessions without installing in a manager or
   * closing.
   */
  activate() {
    if (!this.#lifetime) {
      this.#lifetime = (this.#manager?.construction ?? import_general.Lifetime.process).join("session", this.via);
    }
    return this.#lifetime;
  }
  set lifetime(lifetime) {
    this.#lifetime = lifetime;
  }
}
//# sourceMappingURL=Session.js.map
