/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
import { Duration, Logger, Millis, Time } from "@matter/main";
import { PeerAddressMap } from "@matter/main/protocol";
import { formatNodeId } from "../util/formatNodeId.js";
const logger = Logger.get("CustomClusterPoller");
const VENDOR_ID_EVE = 4874;
const EVE_CLUSTER_ID = 319486977;
const EVE_ENERGY_ATTRIBUTE_IDS = {
  watt: 319422474,
  wattAccumulated: 319422475,
  voltage: 319422472,
  current: 319422473
};
const ELECTRICAL_POWER_MEASUREMENT_CLUSTER_ID = 144;
const POLLING_INTERVAL_MS = 6e4;
const MAX_INITIAL_DELAY_MS = 3e4;
function checkPolledAttributes(attributes) {
  const polledAttributes = /* @__PURE__ */ new Set();
  const vendorId = attributes["0/40/2"];
  if (vendorId !== VENDOR_ID_EVE) {
    return polledAttributes;
  }
  const eveEndpoints = /* @__PURE__ */ new Set();
  for (const attrPath of Object.keys(attributes)) {
    const [endpointStr, clusterStr] = attrPath.split("/");
    const clusterId = Number(clusterStr);
    if (clusterId === EVE_CLUSTER_ID) {
      eveEndpoints.add(Number(endpointStr));
    }
  }
  if (eveEndpoints.size === 0) {
    return polledAttributes;
  }
  for (const attrPath of Object.keys(attributes)) {
    const [, clusterStr] = attrPath.split("/");
    const clusterId = Number(clusterStr);
    if (clusterId === ELECTRICAL_POWER_MEASUREMENT_CLUSTER_ID) {
      logger.debug("Node has standard ElectricalPowerMeasurement cluster, skipping Eve energy polling");
      return polledAttributes;
    }
  }
  for (const endpoint of eveEndpoints) {
    for (const [, attributeId] of Object.entries(EVE_ENERGY_ATTRIBUTE_IDS)) {
      const attrPath = `${endpoint}/${EVE_CLUSTER_ID}/${attributeId}`;
      if (attributes[attrPath] !== void 0) {
        polledAttributes.add(attrPath);
      }
    }
  }
  if (polledAttributes.size > 0) {
    logger.info(`Eve device detected, will poll ${polledAttributes.size} energy attributes`);
  }
  return polledAttributes;
}
class CustomClusterPoller {
  #polledAttributes = new PeerAddressMap();
  #pollerTimer;
  #attributeReader;
  #isPolling = false;
  #currentDelayPromise;
  #currentReadPromise;
  #closed = false;
  constructor(attributeReader) {
    this.#attributeReader = attributeReader;
    const delay = Millis(MAX_INITIAL_DELAY_MS + Math.random() * MAX_INITIAL_DELAY_MS);
    this.#pollerTimer = Time.getTimer("eve-poller", delay, () => this.#pollAllNodes());
  }
  /**
   * Register a node for polling if it has custom attributes that need polling.
   * Call this after a node is connected and its attributes are available.
   */
  registerNode(peer, attributes) {
    const attributesToPoll = checkPolledAttributes(attributes);
    if (attributesToPoll.size === 0) {
      this.unregisterNode(peer);
      return;
    }
    this.#polledAttributes.set(peer, attributesToPoll);
    logger.info(
      `Registered node ${formatNodeId(peer)} for custom attribute polling: ${Array.from(attributesToPoll).join(", ")}`
    );
    this.#schedulePoller();
  }
  /**
   * Unregister a node from polling (e.g., when decommissioned or disconnected).
   */
  unregisterNode(peer) {
    if (this.#polledAttributes.delete(peer)) {
      logger.info(`Unregistered node ${formatNodeId(peer)} from custom attribute polling`);
    }
    if (this.#polledAttributes.size === 0) {
      this.#pollerTimer.stop();
    }
  }
  /**
   * Stop all polling and cleanup. Awaits any in-flight read operation.
   */
  async stop() {
    this.#closed = true;
    this.#currentDelayPromise?.cancel(new Error("Close"));
    this.#pollerTimer?.stop();
    this.#polledAttributes.clear();
    if (this.#currentReadPromise) {
      await this.#currentReadPromise;
    }
    logger.info("Custom attribute poller stopped");
  }
  /**
   * Schedule the next polling cycle.
   * Uses a random initial delay (0-30s) on first run to stagger startup,
   * then polls every 30s thereafter.
   */
  #schedulePoller() {
    if (this.#polledAttributes.size === 0 || this.#closed) {
      return;
    }
    if (this.#pollerTimer?.isRunning || this.#isPolling) {
      return;
    }
    this.#pollerTimer.start();
  }
  /**
   * Poll all registered nodes for their custom attributes.
   */
  async #pollAllNodes() {
    if (this.#isPolling) {
      return;
    }
    const targetInterval = Millis(POLLING_INTERVAL_MS);
    if (this.#pollerTimer.interval !== targetInterval) {
      this.#pollerTimer.interval = targetInterval;
    }
    this.#isPolling = true;
    let polledNodes = 0;
    try {
      const entries = Array.from(this.#polledAttributes.entries());
      for (let i = 0; i < entries.length; i++) {
        if (this.#closed) {
          break;
        }
        const [peer, attributePaths] = entries[i];
        if (!this.#polledAttributes.has(peer)) {
          continue;
        }
        polledNodes++;
        await this.#pollNode(peer, attributePaths);
        if (i < entries.length - 1) {
          this.#currentDelayPromise = Time.sleep("sleep", Millis(2e3)).finally(() => {
            this.#currentDelayPromise = void 0;
          });
          await this.#currentDelayPromise;
        }
      }
    } finally {
      this.#isPolling = false;
      this.#schedulePoller();
    }
    if (polledNodes > 0) {
      logger.info(
        `Polled ${polledNodes} nodes for energy data. Scheduling next poll in ${Duration.format(this.#pollerTimer.interval)}`
      );
    }
  }
  /**
   * Poll a single node for its custom attributes.
   * The read will automatically trigger change events through the normal attribute flow.
   */
  async #pollNode(peer, attributePaths) {
    if (!this.#attributeReader.nodeConnected(peer)) {
      logger.debug(`Node ${formatNodeId(peer)} not connected, skipping custom attribute polling`);
      return;
    }
    const paths = Array.from(attributePaths);
    logger.debug(`Polling ${paths.length} custom attributes for node ${formatNodeId(peer)}`);
    try {
      const readPromise = this.#attributeReader.handleReadAttributes(peer, paths, true);
      this.#currentReadPromise = readPromise.then(
        () => {
        },
        () => {
        }
      );
      await readPromise;
    } catch (error) {
      logger.warn(`Failed to poll custom attributes for node ${formatNodeId(peer)}: `, error);
    } finally {
      this.#currentReadPromise = void 0;
    }
  }
}
export {
  CustomClusterPoller,
  checkPolledAttributes
};
//# sourceMappingURL=CustomClusterPoller.js.map
