/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  AbortedError,
  asError,
  AsyncObservableValue,
  causedBy,
  Diagnostic,
  Duration,
  Hours,
  ImplementationError,
  Logger,
  Seconds,
  Time
} from "@matter/general";
import { Specification } from "@matter/model";
import { ClientSubscription } from "./ClientSubscription.js";
const logger = Logger.get("ClientSubscription");
class SustainedSubscription extends ClientSubscription {
  #request;
  #subscription;
  #retries;
  #subscribe;
  #read;
  #probe;
  #active = AsyncObservableValue(false);
  #inactive = AsyncObservableValue(true);
  constructor(config) {
    super(config);
    const { request, read, probe, retries, subscribe } = config;
    this.#request = request;
    this.#retries = retries;
    this.#subscribe = subscribe;
    this.#read = read;
    this.#probe = probe;
    this.done = this.#run();
  }
  /**
   * Emits when the active state changes.
   */
  get active() {
    return this.#active;
  }
  /**
   * Emits when inactive state changes.
   */
  get inactive() {
    return this.#inactive;
  }
  async #run() {
    let sessionTrusted = true;
    const updated = this.#request.updated?.bind(this.#request);
    let { bootstrapWithRead, refreshRequest } = this.#request;
    let needToRefreshRequest = false;
    while (true) {
      let request = { ...this.#request, updated };
      if (this.#request.updated) {
        request.updated = this.#request.updated.bind(request);
      }
      const closed = new Promise((resolve) => {
        request.closed = () => {
          this.#subscription = void 0;
          this.subscriptionId = ClientSubscription.NO_SUBSCRIPTION;
          sessionTrusted = false;
          resolve();
        };
      });
      if (!sessionTrusted) {
        if (!await this.#probe(this.abort)) {
          if (!this.abort.aborted) {
            sessionTrusted = true;
            logger.error(`Failed to probe reachability of peer ${this.peer}, resubscribe with new session`);
          }
        }
        if (this.abort.aborted) {
          return;
        }
      }
      for (const retry of this.#retries) {
        try {
          if (needToRefreshRequest && refreshRequest !== void 0) {
            request = refreshRequest(request);
          }
          needToRefreshRequest = true;
          if (bootstrapWithRead) {
            const response = this.#read(request, this.abort, Diagnostic.asFlags({ bootstrap: true }));
            if (request.updated) {
              await request.updated(response);
            } else {
              for await (const _chunk of response) ;
            }
            if (this.abort.aborted) {
              return;
            }
            bootstrapWithRead = false;
            if (refreshRequest !== void 0) {
              request = refreshRequest(request);
            }
          }
          this.#subscription = await this.#subscribe(request, this.abort);
          this.subscriptionId = this.#subscription.subscriptionId;
          sessionTrusted = true;
          break;
        } catch (e) {
          if (!causedBy(e, AbortedError) || !this.abort.aborted) {
            sessionTrusted = true;
            logger.error(
              `Failed to establish subscription to ${this.peer}, retry in ${Duration.format(retry)}:`,
              Diagnostic.errorMessage(asError(e))
            );
          }
          if (this.abort.aborted) {
            return;
          }
        }
        const readyForRetry = Time.sleep("subscription retry", retry);
        await this.abort.race(readyForRetry);
        readyForRetry.cancel();
        if (this.abort.aborted) {
          break;
        }
      }
      await this.#inactive.emit(false);
      await this.#active.emit(true);
      if (this.abort.aborted) {
        break;
      }
      await closed;
      await this.#active.emit(false);
      await this.#inactive.emit(true);
      if (this.abort.aborted) {
        break;
      }
      logger.error(`Replacing subscription to ${this.peer} due to timeout`);
    }
    this.#request.closed?.();
  }
  get interactionModelRevision() {
    return this.#subscription?.interactionModelRevision ?? Specification.INTERACTION_MODEL_REVISION;
  }
  get maxInterval() {
    return this.#subscription?.maxInterval ?? Hours.one;
  }
}
((SustainedSubscription2) => {
  function assert(subscription) {
    if (!(subscription instanceof SustainedSubscription2)) {
      throw new ImplementationError(`Non-sustained subscription provided where sustained subscription required`);
    }
  }
  SustainedSubscription2.assert = assert;
  SustainedSubscription2.DefaultRetrySchedule = {
    // Protocol-level level happens at the exchange level and is faster; this is an application-level retry.  Retry
    // more slowly so we do not hammer devices that are experiencing transient errors
    initialInterval: Seconds(15),
    // Similarly, we have an exchange.  If a device repeatedly fails to establish a subscription, give it plenty of
    // time to recover.  It's even possible our subscription attempt is invalid for some reason, in which case we
    // an aggressive interval would be particularly bad form
    maximumInterval: Hours(1),
    // No timeout; we run until aborted
    timeout: void 0,
    backoffFactor: 2,
    jitterFactor: 0.25
  };
})(SustainedSubscription || (SustainedSubscription = {}));
export {
  SustainedSubscription
};
//# sourceMappingURL=SustainedSubscription.js.map
