var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
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();
};
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { NetworkRuntime } from "#behavior/system/network/NetworkRuntime.js";
import { NetworkCommissioningServer } from "#behaviors/network-commissioning";
import { TimeSynchronizationBehavior } from "#behaviors/time-synchronization";
import { GeneralDiagnostics } from "#clusters/general-diagnostics";
import {
  Bytes,
  Hours,
  ImplementationError,
  ipv4ToBytes,
  ipv6ToBytes,
  Logger,
  Millis,
  Minutes,
  Seconds,
  Time,
  Timespan
} from "#general";
import { FieldElement, Specification } from "#model";
import { assertRemoteActor, MdnsService, Val } from "#protocol";
import { CommandId, StatusCode, StatusResponseError, TlvInvokeResponse } from "#types";
import { GeneralDiagnosticsBehavior } from "./GeneralDiagnosticsBehavior.js";
const logger = Logger.get("GeneralDiagnosticsServer");
const Base = GeneralDiagnosticsBehavior.with(GeneralDiagnostics.Feature.DataModelTest);
const schema = Base.schema.extend(
  {},
  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 = Bytes.of(this.state.deviceTestEnableKey);
      if (enableKey.every((byte) => byte === 0)) {
        throw new 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 = Bytes.of(enableKey);
    if (keyData.every((byte) => byte === 0)) {
      throw new StatusResponseError("Invalid test enable key, all zeros", StatusCode.ConstraintError);
    }
    const expectedKeyData = Bytes.of(this.state.deviceTestEnableKey);
    keyData.forEach((byte, index) => {
      if (byte !== expectedKeyData[index]) {
        throw new StatusResponseError("Invalid test enable key", StatusCode.ConstraintError);
      }
    });
  }
  testEventTrigger({ eventTrigger, enableKey }) {
    this.#validateTestEnabledKey(enableKey);
    this.triggerTestEvent(eventTrigger);
  }
  triggerTestEvent(eventTrigger) {
    throw new StatusResponseError(`Unsupported test event trigger ${eventTrigger}`, StatusCode.InvalidCommand);
  }
  timeSnapshot() {
    const time = Time.nowMs;
    const posixTimeMs = this.agent.has(TimeSynchronizationBehavior) && this.agent.get(TimeSynchronizationBehavior).state.utcTime !== null ? time : null;
    return {
      systemTimeMs: time - Time.startup.systemMs,
      posixTimeMs
    };
  }
  payloadTestRequest({
    enableKey,
    value,
    count
  }) {
    this.#validateTestEnabledKey(enableKey);
    if (!this.state.testEventTriggersEnabled) {
      throw new StatusResponseError("Test event triggers are disabled", StatusCode.ConstraintError);
    }
    const payload = new Uint8Array(count).fill(value);
    const responseSize = TlvInvokeResponse.encode({
      suppressResponse: false,
      interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
      moreChunkedMessages: true,
      invokeResponses: [
        {
          command: {
            commandPath: {
              endpointId: this.endpoint.number,
              clusterId: GeneralDiagnostics.Complete.id,
              commandId: CommandId(4)
              // Hardcode for now
            },
            commandRef: 0,
            commandFields: GeneralDiagnostics.TlvPayloadTestResponse.encodeTlv({
              payload
            })
          }
        }
      ]
    }).byteLength;
    assertRemoteActor(this.context);
    const { exchange } = this.context;
    if (responseSize > exchange.maxPayloadSize) {
      throw new StatusResponseError("Response too large", 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 ?? GeneralDiagnostics.BootReason.Unspecified },
      this.context
    );
    this.internal.lastTotalOperationalHoursCounterUpdateTime = Time.nowMs;
    this.internal.lastTotalOperationalHoursTimer = Time.getPeriodicTimer(
      "GeneralDiagnostics.operationalHours",
      Minutes(5),
      this.callback(this.#updateTotalOperationalHoursCounter, { lock: true })
    ).start();
    await this.#updateNetworkList();
  }
  #goingOffline() {
    this.internal.lastTotalOperationalHoursTimer?.stop();
    this.#updateTotalOperationalHoursCounter();
  }
  #updateTotalOperationalHoursCounter() {
    const now = Time.nowMs;
    const elapsedTime = Timespan(this.internal.lastTotalOperationalHoursCounterUpdateTime, now).duration;
    this.state.totalOperationalHoursCounter = 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(MdnsService);
      const mdnsLimitedToNetworkInterfaces = mdnsService.limitedToNetInterface;
      const networkRuntime = this.env.get(NetworkRuntime);
      const systemNetworkInterfaces = await networkRuntime.getNetworkInterfaces();
      let networkType = GeneralDiagnostics.InterfaceType.Ethernet;
      if (this.endpoint.behaviors.has(NetworkCommissioningServer)) {
        const networkCommissioning = this.agent.get(NetworkCommissioningServer);
        if ("wiFiNetworkInterface" in networkCommissioning.features && networkCommissioning.features.wiFiNetworkInterface) {
          networkType = GeneralDiagnostics.InterfaceType.WiFi;
        } else if ("threadNetworkInterface" in networkCommissioning.features && networkCommissioning.features.threadNetworkInterface) {
          networkType = 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: Bytes.fromHex(mac.replace(/[^\da-f]/gi, "")),
        iPv4Addresses: ipV4.slice(0, 4).map((ip) => ipv4ToBytes(ip)),
        iPv6Addresses: ipV6.slice(0, 8).map((ip) => ipv6ToBytes(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 = 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);
    [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 Seconds.of(Timespan(onlineAt, 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 = Timespan(
            lastTotalOperationalHoursCounterUpdateTime,
            Time.nowMs
          ).duration;
          const timeAsOfLastUpdate = endpoint.stateOf(GeneralDiagnosticsServer2).totalOperationalHoursCounter;
          const totalOperationalTime = Millis(timeAsOfLastUpdate + timeSinceLastUpdate);
          return Hours.of(totalOperationalTime);
        }
      };
    }
  }
  GeneralDiagnosticsServer2.State = State;
})(GeneralDiagnosticsServer || (GeneralDiagnosticsServer = {}));
export {
  GeneralDiagnosticsServer
};
//# sourceMappingURL=GeneralDiagnosticsServer.js.map
