"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 GeneralDiagnosticsServer_exports = {};
__export(GeneralDiagnosticsServer_exports, {
  GeneralDiagnosticsServer: () => GeneralDiagnosticsServer
});
module.exports = __toCommonJS(GeneralDiagnosticsServer_exports);
var import_NetworkRuntime = require("#behavior/system/network/NetworkRuntime.js");
var import_network_commissioning = require("#behaviors/network-commissioning");
var import_time_synchronization = require("#behaviors/time-synchronization");
var import_general_diagnostics = require("#clusters/general-diagnostics");
var import_general = require("#general");
var import_model = require("#model");
var import_protocol = require("#protocol");
var import_types = require("#types");
var import_GeneralDiagnosticsBehavior = require("./GeneralDiagnosticsBehavior.js");
/**
 * @license
 * Copyright 2022-2025 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("GeneralDiagnosticsServer");
const Base = import_GeneralDiagnosticsBehavior.GeneralDiagnosticsBehavior.with(import_general_diagnostics.GeneralDiagnostics.Feature.DataModelTest);
const schema = Base.schema.extend(
  {},
  (0, import_model.FieldElement)({ name: "totalOperationalHoursCounter", type: "uint64", quality: "N", conformance: "M" })
);
class GeneralDiagnosticsServer extends Base {
  static schema = schema;
  initialize() {
    if (this.state.testEventTriggersEnabled === void 0) {
      this.state.testEventTriggersEnabled = false;
    } else if (this.state.testEventTriggersEnabled) {
      const enableKey = import_general.Bytes.of(this.state.deviceTestEnableKey);
      if (enableKey.every((byte) => byte === 0)) {
        throw new import_general.ImplementationError("Test event triggers are enabled but no deviceTestEnableKey is set.");
      }
      logger.warn("Test event triggers are enabled. Make sure to disable them in production.");
    }
    if (this.state.rebootCount === void 0) {
      this.state.rebootCount = 0;
    } else {
      this.state.rebootCount++;
    }
    const lifecycle = this.endpoint.lifecycle;
    this.reactTo(lifecycle.online, this.#online, { lock: true });
    this.reactTo(lifecycle.goingOffline, this.#goingOffline, { lock: true });
    this.maybeReactTo(this.events.activeHardwareFaults$Changed, this.#triggerActiveHardwareFaultsChangedEvent);
    this.maybeReactTo(this.events.activeRadioFaults$Changed, this.#triggerActiveRadioFaultsChangedEvent);
    this.maybeReactTo(this.events.activeNetworkFaults$Changed, this.#triggerActiveNetworkFaultsChangedEvent);
  }
  #validateTestEnabledKey(enableKey) {
    const keyData = import_general.Bytes.of(enableKey);
    if (keyData.every((byte) => byte === 0)) {
      throw new import_types.StatusResponseError("Invalid test enable key, all zeros", import_types.StatusCode.ConstraintError);
    }
    const expectedKeyData = import_general.Bytes.of(this.state.deviceTestEnableKey);
    keyData.forEach((byte, index) => {
      if (byte !== expectedKeyData[index]) {
        throw new import_types.StatusResponseError("Invalid test enable key", import_types.StatusCode.ConstraintError);
      }
    });
  }
  testEventTrigger({ eventTrigger, enableKey }) {
    this.#validateTestEnabledKey(enableKey);
    this.triggerTestEvent(eventTrigger);
  }
  triggerTestEvent(eventTrigger) {
    throw new import_types.StatusResponseError(`Unsupported test event trigger ${eventTrigger}`, import_types.StatusCode.InvalidCommand);
  }
  timeSnapshot() {
    const time = import_general.Time.nowMs;
    const posixTimeMs = this.agent.has(import_time_synchronization.TimeSynchronizationBehavior) && this.agent.get(import_time_synchronization.TimeSynchronizationBehavior).state.utcTime !== null ? time : null;
    return {
      systemTimeMs: time - import_general.Time.startup.systemMs,
      posixTimeMs
    };
  }
  payloadTestRequest({
    enableKey,
    value,
    count
  }) {
    this.#validateTestEnabledKey(enableKey);
    if (!this.state.testEventTriggersEnabled) {
      throw new import_types.StatusResponseError("Test event triggers are disabled", import_types.StatusCode.ConstraintError);
    }
    const payload = new Uint8Array(count).fill(value);
    const responseSize = import_types.TlvInvokeResponse.encode({
      suppressResponse: false,
      interactionModelRevision: import_model.Specification.INTERACTION_MODEL_REVISION,
      moreChunkedMessages: true,
      invokeResponses: [
        {
          command: {
            commandPath: {
              endpointId: this.endpoint.number,
              clusterId: import_general_diagnostics.GeneralDiagnostics.Complete.id,
              commandId: (0, import_types.CommandId)(4)
              // Hardcode for now
            },
            commandRef: 0,
            commandFields: import_general_diagnostics.GeneralDiagnostics.TlvPayloadTestResponse.encodeTlv({
              payload
            })
          }
        }
      ]
    }).byteLength;
    (0, import_protocol.assertRemoteActor)(this.context);
    const { exchange } = this.context;
    if (responseSize > exchange.maxPayloadSize) {
      throw new import_types.StatusResponseError("Response too large", import_types.StatusCode.ResourceExhausted);
    }
    return {
      payload
    };
  }
  /**
   * Register a hardware fault. This convenience method updates the activeHardwareFaults attribute and sends out the
   * HardwareFaultChanged event. Make sure to clear the fault when it is resolved.
   * This method requires that the activeHardwareFaults attribute is activated when the cluster gets initialized.
   *
   * @param faultType The hardware fault to register.
   */
  registerHardwareFault(faultType) {
    const currentFaults = this.requireAttributeEnabled("activeHardwareFaults");
    const list = currentFaults.filter((fault) => fault !== faultType);
    list.push(faultType);
    this.state.activeHardwareFaults = list;
  }
  /**
   * Clear a hardware fault. This convenience method updates the activeHardwareFaults attribute and sends out the
   * HardwareFaultChanged event.
   *
   * @param faultType The hardware fault to clear.
   */
  clearHardwareFault(faultType) {
    const currentFaults = this.requireAttributeEnabled("activeHardwareFaults");
    this.state.activeHardwareFaults = currentFaults.filter((fault) => fault !== faultType);
  }
  #triggerActiveHardwareFaultsChangedEvent(current, previous) {
    this.events.hardwareFaultChange?.emit(
      {
        current,
        previous
      },
      this.context
    );
  }
  /**
   * Register a radio fault. This convenience method updates the activeRadioFaults attribute and sends out the
   * RadioFaultChanged event. Make sure to clear the fault when it is resolved.
   * This method requires that the activeRadioFaults attribute is activated when the cluster gets initialized.
   *
   * @param faultType The radio fault to register.
   */
  registerRadioFault(faultType) {
    const currentFaults = this.requireAttributeEnabled("activeRadioFaults");
    const list = currentFaults.filter((fault) => fault !== faultType);
    list.push(faultType);
    this.state.activeRadioFaults = list;
  }
  /**
   * Clear a radio fault. This convenience method updates the activeRadioFaults attribute and sends out the
   * RadioFaultChanged event.
   *
   * @param faultType The radio fault to clear.
   */
  clearRadioFault(faultType) {
    const currentFaults = this.requireAttributeEnabled("activeRadioFaults");
    this.state.activeRadioFaults = currentFaults.filter((fault) => fault !== faultType);
  }
  #triggerActiveRadioFaultsChangedEvent(current, previous) {
    this.events.radioFaultChange?.emit(
      {
        current,
        previous
      },
      this.context
    );
  }
  /**
   * Register a network fault. This convenience method updates the activeNetworkFaults attribute and sends out the
   * NetworkFaultChanged event. Make sure to clear the fault when it is resolved.
   * This method requires that the activeNetworkFaults attribute is activated when the cluster gets initialized.
   *
   * @param faultType The network fault to register.
   */
  registerNetworkFault(faultType) {
    const currentFaults = this.requireAttributeEnabled("activeNetworkFaults");
    const list = currentFaults.filter((fault) => fault !== faultType);
    list.push(faultType);
    this.state.activeNetworkFaults = list;
  }
  /**
   * Clear a network fault. This convenience method updates the activeNetworkFaults attribute and sends out the
   * NetworkFaultChanged event.
   *
   * @param faultType The network fault to clear.
   */
  clearNetworkFault(faultType) {
    const currentFaults = this.requireAttributeEnabled("activeNetworkFaults");
    this.state.activeNetworkFaults = currentFaults.filter((fault) => fault !== faultType);
  }
  #triggerActiveNetworkFaultsChangedEvent(current, previous) {
    this.events.networkFaultChange?.emit(
      {
        current,
        previous
      },
      this.context
    );
  }
  async #online() {
    this.events.bootReason.emit(
      { bootReason: this.state.bootReason ?? import_general_diagnostics.GeneralDiagnostics.BootReason.Unspecified },
      this.context
    );
    this.internal.lastTotalOperationalHoursCounterUpdateTime = import_general.Time.nowMs;
    this.internal.lastTotalOperationalHoursTimer = import_general.Time.getPeriodicTimer(
      "GeneralDiagnostics.operationalHours",
      (0, import_general.Minutes)(5),
      this.callback(this.#updateTotalOperationalHoursCounter, { lock: true })
    ).start();
    await this.#updateNetworkList();
  }
  #goingOffline() {
    this.internal.lastTotalOperationalHoursTimer?.stop();
    this.#updateTotalOperationalHoursCounter();
  }
  #updateTotalOperationalHoursCounter() {
    const now = import_general.Time.nowMs;
    const elapsedTime = (0, import_general.Timespan)(this.internal.lastTotalOperationalHoursCounterUpdateTime, now).duration;
    this.state.totalOperationalHoursCounter = (0, import_general.Millis)(this.state.totalOperationalHoursCounter + elapsedTime);
    this.internal.lastTotalOperationalHoursCounterUpdateTime = now;
  }
  async #updateNetworkList() {
    var _stack = [];
    try {
      const services = __using(_stack, this.env.asDependent(), true);
      const mdnsService = services.get(import_protocol.MdnsService);
      const mdnsLimitedToNetworkInterfaces = mdnsService.limitedToNetInterface;
      const networkRuntime = this.env.get(import_NetworkRuntime.NetworkRuntime);
      const systemNetworkInterfaces = await networkRuntime.getNetworkInterfaces();
      let networkType = import_general_diagnostics.GeneralDiagnostics.InterfaceType.Ethernet;
      if (this.endpoint.behaviors.has(import_network_commissioning.NetworkCommissioningServer)) {
        const networkCommissioning = this.agent.get(import_network_commissioning.NetworkCommissioningServer);
        if ("wiFiNetworkInterface" in networkCommissioning.features && networkCommissioning.features.wiFiNetworkInterface) {
          networkType = import_general_diagnostics.GeneralDiagnostics.InterfaceType.WiFi;
        } else if ("threadNetworkInterface" in networkCommissioning.features && networkCommissioning.features.threadNetworkInterface) {
          networkType = import_general_diagnostics.GeneralDiagnostics.InterfaceType.Thread;
        }
      }
      const isOperationalReachable = mdnsLimitedToNetworkInterfaces === void 0 ? () => true : (name) => {
        return name === mdnsLimitedToNetworkInterfaces;
      };
      this.state.networkInterfaces = systemNetworkInterfaces.filter(({ mac }) => mac !== "00:00:00:00:00:00").sort(({ name: nameA }, { name: nameB }) => {
        if (isOperationalReachable(nameA) && !isOperationalReachable(nameB)) {
          return -1;
        }
        if (!isOperationalReachable(nameA) && isOperationalReachable(nameB)) {
          return 1;
        }
        return 0;
      }).slice(0, 8).map(({ name, mac, ipV4, ipV6, type }) => ({
        name: name.substring(0, 32),
        isOperational: isOperationalReachable(name),
        offPremiseServicesReachableIPv4: null,
        // null means unknown or not supported
        offPremiseServicesReachableIPv6: null,
        // null means unknown or not supported
        hardwareAddress: import_general.Bytes.fromHex(mac.replace(/[^\da-f]/gi, "")),
        iPv4Addresses: ipV4.slice(0, 4).map((ip) => (0, import_general.ipv4ToBytes)(ip)),
        iPv6Addresses: ipV6.slice(0, 8).map((ip) => (0, import_general.ipv4ToBytes)(ip)),
        type: type ?? networkType
      }));
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      var _promise = __callDispose(_stack, _error, _hasError);
      _promise && await _promise;
    }
  }
}
((GeneralDiagnosticsServer2) => {
  class Internal {
    /** Last time the total operational hours counter was updated. */
    lastTotalOperationalHoursCounterUpdateTime = import_general.Time.nowMs;
    /** Timer to update the total operational hours counter every 5 minutes. */
    lastTotalOperationalHoursTimer;
  }
  GeneralDiagnosticsServer2.Internal = Internal;
  class State extends Base.State {
    /** Internal counter of the total time, updated every 5 minutes. */
    totalOperationalHoursCounter = 0;
    /** The TestEnableKey set for this device for the test commands. Default means "not enabled"." */
    deviceTestEnableKey = new Uint8Array(16).fill(0);
    [import_protocol.Val.properties](endpoint, _session) {
      return {
        /**
         * Report uptime
         *
         * This value is not available for subscription so we compute dynamically.
         *
         * As of 1.4 the spec does not specify what should be considered the "start time" for computing uptime.
         * They just say "since the device's last reboot".  This could be from power on, or from when the device
         * is first usable by a user, when it's first available online, etc.
         *
         * The tests however expect uptime to reset after factory reset.  So we consider "time brought online"
         * our boot time.
         */
        get upTime() {
          const onlineAt = endpoint.lifecycle.onlineAt;
          if (onlineAt === void 0) {
            return 0;
          }
          return import_general.Seconds.of((0, import_general.Timespan)(onlineAt, import_general.Time.nowMs).duration);
        },
        /**
         * Dynamically calculate the operating Hours from our internal counter. This is ok because the attribute
         * is not send out via subscriptions anyway.
         */
        get totalOperationalHours() {
          const { lastTotalOperationalHoursCounterUpdateTime } = endpoint.behaviors.internalsOf(GeneralDiagnosticsServer2);
          const timeSinceLastUpdate = (0, import_general.Timespan)(
            lastTotalOperationalHoursCounterUpdateTime,
            import_general.Time.nowMs
          ).duration;
          const timeAsOfLastUpdate = endpoint.stateOf(GeneralDiagnosticsServer2).totalOperationalHoursCounter;
          const totalOperationalTime = (0, import_general.Millis)(timeAsOfLastUpdate + timeSinceLastUpdate);
          return import_general.Hours.of(totalOperationalTime);
        }
      };
    }
  }
  GeneralDiagnosticsServer2.State = State;
})(GeneralDiagnosticsServer || (GeneralDiagnosticsServer = {}));
//# sourceMappingURL=GeneralDiagnosticsServer.js.map
