"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_ota_software_update_requestor2 = require("#clusters/ota-software-update-requestor");
var import_general = require("#general");
var import_Node = require("#node/Node.js");
var import_protocol = require("#protocol");
/**
 * @license
 * Copyright 2022-2025 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = new import_general.Logger("OTAAnnouncements");
class OtaAnnouncements {
  #announcementQueue = new Array();
  #announcementTimer;
  #announcementDelayTimer;
  #ownFabric;
  #node;
  #otaProviderEndpoint;
  #announcementInterval;
  #currentAnnouncementPromise;
  constructor(endpoint, ownFabric, interval) {
    this.#node = import_Node.Node.forEndpoint(endpoint);
    this.#ownFabric = ownFabric;
    this.#otaProviderEndpoint = endpoint.number;
    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);
    }
    this.#announcementInterval = (0, import_general.Millis)(interval + (0, import_general.Seconds)(Math.floor(Math.random() * 120) + 60));
    this.#announcementDelayTimer = import_general.Time.getTimer(
      "OTA Node announcement delay",
      (0, import_general.Seconds)(10),
      () => this.#processQueueEntry()
    );
    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() {
    this.#announcementTimer.stop();
    this.#announcementTimer = import_general.Time.getTimer(
      "OTA All Nodes announcement timer",
      this.#announcementInterval,
      () => this.#queueAllPeers()
    );
    this.#queueAllPeers();
  }
  #queueAllPeers() {
    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) {
    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.error(`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.#ownFabric.rootNodeId,
      endpoint: this.#otaProviderEndpoint,
      fabricIndex: this.#ownFabric.fabricIndex
    };
    const existingOtaProviderRecord = otaEndpoint.stateOf(import_ota_software_update_requestor.OtaSoftwareUpdateRequestorClient).defaultOtaProviders.filter(({ fabricIndex }) => fabricIndex === this.#ownFabric.fabricIndex)[0];
    if (existingOtaProviderRecord === void 0 || !(0, import_general.isDeepEqual)(consideredOtaProviderRecord, consideredOtaProviderRecord)) {
      try {
        await peer.interaction.write(
          (0, import_protocol.Write)(
            import_protocol.Write.Attribute({
              endpoint: otaEndpoint.number,
              cluster: import_ota_software_update_requestor2.OtaSoftwareUpdateRequestor.Complete,
              attributes: ["defaultOtaProviders"],
              value: [consideredOtaProviderRecord]
            })
          )
        );
        logger.debug(
          `${existingOtaProviderRecord === void 0 ? "Added" : "Updated"} default OTA provider for`,
          peerAddress,
          `on endpoint ${otaEndpoint.number}`
        );
      } catch (error) {
        logger.info("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.isGroup || !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.debug(`Node`, (peerAddress ?? peer.id).toString(), ` is currently not applicable for OTA updates`);
      return;
    }
    const otaEndpoint = this.#findOtaRequestorEndpointOn(peer);
    if (otaEndpoint === void 0) {
      logger.debug(`Node`, (peerAddress ?? peer.id).toString(), ` does not support OTA updates`);
      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.#ownFabric.rootNodeId,
        vendorId: this.#ownFabric.rootVendorId,
        fabricIndex: this.#ownFabric.fabricIndex,
        announcementReason,
        endpoint: this.#otaProviderEndpoint
      });
    } catch (error) {
      import_general.MatterError.accept(error);
      logger.error(
        `Failed to notify node ${peerAddress.toString()}/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
