"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
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 OtaAnnouncements_exports = {};
__export(OtaAnnouncements_exports, {
  OtaAnnouncements: () => OtaAnnouncements
});
module.exports = __toCommonJS(OtaAnnouncements_exports);
var import_CommissioningClient = require("#behavior/system/commissioning/CommissioningClient.js");
var import_NetworkClient = require("#behavior/system/network/NetworkClient.js");
var import_basic_information = require("#behaviors/basic-information");
var import_ota_software_update_requestor = require("#behaviors/ota-software-update-requestor");
var import_Node = require("#node/Node.js");
var import_general = require("@matter/general");
var import_protocol = require("@matter/protocol");
var import_ota_software_update_requestor2 = require("@matter/types/clusters/ota-software-update-requestor");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = new import_general.Logger("OTAAnnouncements");
class OtaAnnouncements {
  #announcementQueue = new Array();
  #announcementTimer;
  #announcementDelayTimer;
  #ownNodeId;
  #ownFabricIndex;
  #ownVendorId;
  #node;
  #otaProviderEndpoint;
  #announcementInterval;
  #currentAnnouncementPromise;
  constructor(endpoint, ownFabric) {
    this.#node = import_Node.Node.forEndpoint(endpoint);
    this.#ownNodeId = ownFabric.rootNodeId;
    this.#ownFabricIndex = ownFabric.fabricIndex;
    this.#ownVendorId = ownFabric.rootVendorId;
    this.#otaProviderEndpoint = endpoint.number;
    this.#announcementDelayTimer = import_general.Time.getTimer(
      "OTA Node announcement delay",
      (0, import_general.Seconds)(10),
      () => this.#processQueueEntry()
    );
  }
  /**
   * Set the interval to a time value or undefined to disable announcements
   */
  set interval(interval) {
    if (interval === void 0) {
      this.#announcementInterval = void 0;
      this.#announcementTimer?.stop();
      this.#announcementTimer = void 0;
      return;
    }
    if (interval < (0, import_general.Hours)(24)) {
      logger.warn("Announcements interval is too short, consider increasing it to at least 24 hours.");
      interval = (0, import_general.Hours)(24);
    }
    if (interval === this.#announcementInterval) {
      return;
    }
    this.#announcementInterval = (0, import_general.Millis)(interval + (0, import_general.Seconds)(Math.floor(Math.random() * 120) + 60));
    this.#announcementTimer?.stop();
    const initialDelay = (0, import_general.Millis)((0, import_general.Seconds)(Math.floor(Math.random() * 300)) + (0, import_general.Minutes)(10));
    logger.debug(`Initial OTA announcement delay is ${import_general.Duration.format(initialDelay)}`);
    this.#announcementTimer = import_general.Time.getTimer(
      "Delay for initial OTA announcement",
      initialDelay,
      () => this.#initializeAnnouncements()
    ).start();
  }
  #initializeAnnouncements() {
    if (this.#announcementInterval === void 0) {
      return;
    }
    this.#announcementTimer?.stop();
    this.#announcementTimer = import_general.Time.getTimer(
      "OTA All Nodes announcement timer",
      this.#announcementInterval,
      () => this.#queueAllPeers()
    );
    this.#queueAllPeers();
  }
  #queueAllPeers() {
    if (this.#announcementTimer === void 0) {
      return;
    }
    this.#announcementTimer.stop();
    for (const peer of this.#node.peers) {
      if (!peer.lifecycle.isCommissioned || !peer.lifecycle.isOnline) {
        continue;
      }
      const peerAddress = (0, import_protocol.PeerAddress)(peer.maybeStateOf(import_CommissioningClient.CommissioningClient)?.peerAddress);
      if (peerAddress === void 0 || this.#announcementQueue.some((p) => import_protocol.PeerAddress.is(p, peerAddress))) {
        continue;
      }
      this.#queuePeer(peerAddress);
    }
    this.#announcementTimer.start();
  }
  // Queue a peer because processing is delayed and better to check /get peer anew when we process it
  #queuePeer(peerAddress) {
    if (this.#announcementTimer === void 0) {
      return;
    }
    this.#announcementQueue.push(peerAddress);
    logger.debug("Queued peer", peerAddress, "for OTA announcement;", this.#announcementQueue.length, "queued");
    if (this.#announcementQueue.length > 0 && !this.#announcementTimer.isRunning) {
      this.#announcementDelayTimer.start();
    }
  }
  async #processQueueEntry() {
    if (this.#currentAnnouncementPromise !== void 0) {
      return;
    }
    if (this.#announcementQueue.length === 0) {
      this.#announcementDelayTimer.stop();
      return;
    }
    const peerAddress = this.#announcementQueue[0];
    try {
      this.#currentAnnouncementPromise = this.#announceOtaProvider(peerAddress);
      await this.#currentAnnouncementPromise;
    } catch (error) {
      logger.warn(`Error announcing OTA provider to ${peerAddress}`, error);
    } finally {
      this.#currentAnnouncementPromise = void 0;
    }
    this.#announcementQueue.shift();
    if (this.#announcementQueue.length > 0) {
      this.#announcementDelayTimer.start();
    }
  }
  async #announceOtaProvider(peerAddress) {
    const peer = this.#node.peers.get(peerAddress);
    if (peer === void 0 || !peer.lifecycle.isCommissioned || !peer.lifecycle.isOnline) {
      return;
    }
    const { otaEndpoint } = this.peerApplicableForUpdate(peer) ?? {};
    if (otaEndpoint === void 0) {
      return;
    }
    const consideredOtaProviderRecord = {
      providerNodeId: this.#ownNodeId,
      endpoint: this.#otaProviderEndpoint,
      fabricIndex: this.#ownFabricIndex
    };
    const existingOtaProviderRecord = otaEndpoint.stateOf(import_ota_software_update_requestor.OtaSoftwareUpdateRequestorClient).defaultOtaProviders.filter(({ fabricIndex }) => fabricIndex === this.#ownFabricIndex)[0];
    if (existingOtaProviderRecord === void 0 || !(0, import_general.isDeepEqual)(consideredOtaProviderRecord, existingOtaProviderRecord)) {
      try {
        import_protocol.WriteResult.assertSuccess(
          await peer.interaction.write(
            (0, import_protocol.Write)(
              import_protocol.Write.Attribute({
                endpoint: otaEndpoint.number,
                cluster: import_ota_software_update_requestor2.OtaSoftwareUpdateRequestor,
                attributes: ["defaultOtaProviders"],
                value: [consideredOtaProviderRecord]
              })
            )
          )
        );
        logger.debug(
          `${existingOtaProviderRecord === void 0 ? "Added" : "Updated"} default OTA provider for`,
          peerAddress,
          `on endpoint ${otaEndpoint.number}`
        );
      } catch (error) {
        logger.notice("Could not set default OTA provider", error);
      }
    }
  }
  /**
   * Determine if we can request an update for the given node and return node meta-data needed for the process.
   * The Node needs to be commissioned (have a peer address), being not disabled, and having an active subscription
   * (to not work with outdated data).
   * When a node is applicable for updates, it also subscribes to softwareVersion changes to be able to react
   */
  peerApplicableForUpdate(peer) {
    if (peer.nodeType === "group" || !peer.behaviors.has(import_basic_information.BasicInformationClient)) {
      return;
    }
    const peerAddress = (0, import_protocol.PeerAddress)(peer.stateOf(import_CommissioningClient.CommissioningClient).peerAddress);
    if (!peer.behaviors.has(import_NetworkClient.NetworkClient) || peerAddress === void 0 || peer.stateOf(import_NetworkClient.NetworkClient).isDisabled || peer.behaviors.internalsOf(import_NetworkClient.NetworkClient).activeSubscription === void 0) {
      logger.info(
        `Node`,
        (peerAddress ?? peer.id).toString(),
        `is currently not in a state where it is applicable for OTA updates (not subscribed)`
      );
      return;
    }
    const otaEndpoint = this.#findOtaRequestorEndpointOn(peer);
    if (otaEndpoint === void 0) {
      logger.debug(
        `Node`,
        (peerAddress ?? peer.id).toString(),
        ` does not support OTA updates (no OTA requestor cluster found)`
      );
      return;
    }
    return { otaEndpoint, peerAddress };
  }
  /** Searches all endpoints of a peer for the OtaSoftwareUpdateRequestor cluster and returns it if found */
  #findOtaRequestorEndpointOn(peer) {
    for (const ep of peer.endpoints) {
      if (ep.behaviors.has(import_ota_software_update_requestor.OtaSoftwareUpdateRequestorClient)) {
        return ep;
      }
    }
  }
  /** Announce ourselves as OTA Provider to the given node's endpoint */
  async announceOtaProvider(endpoint, peerAddress, announcementReason = import_ota_software_update_requestor2.OtaSoftwareUpdateRequestor.AnnouncementReason.SimpleAnnouncement) {
    try {
      await endpoint.commandsOf(import_ota_software_update_requestor.OtaSoftwareUpdateRequestorClient).announceOtaProvider({
        providerNodeId: this.#ownNodeId,
        vendorId: this.#ownVendorId,
        announcementReason,
        endpoint: this.#otaProviderEndpoint
      });
    } catch (error) {
      import_general.MatterError.accept(error);
      if ((0, import_general.causedBy)(error, import_general.ClosedError)) {
        logger.debug(
          `Node ${(0, import_protocol.PeerAddress)(peerAddress)}/ep${endpoint.number} closed connection during OTA announcement, will retry later`
        );
      } else {
        logger.info(
          `Failed to notify node ${(0, import_protocol.PeerAddress)(peerAddress)}/ep${endpoint.number} about available OTA update:`,
          error
        );
      }
    }
  }
  async close() {
    this.#announcementTimer?.stop();
    this.#announcementDelayTimer.stop();
    if (this.#currentAnnouncementPromise !== void 0) {
      await this.#currentAnnouncementPromise;
    }
  }
}
//# sourceMappingURL=OtaAnnouncements.js.map
