"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 GeneralDiagnosticsServer_exports = {};
__export(GeneralDiagnosticsServer_exports, {
  GeneralDiagnosticsServer: () => GeneralDiagnosticsServer
});
module.exports = __toCommonJS(GeneralDiagnosticsServer_exports);
var import_Val = require("#behavior/state/Val.js");
var import_NetworkServer = require("#behavior/system/network/NetworkServer.js");
var import_network_commissioning = require("#behaviors/network-commissioning");
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-2024 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({
  children: [(0, import_model.FieldElement)({ name: "totalOperationalHoursCounter", type: "uint64", quality: "N" })]
});
class GeneralDiagnosticsServer extends Base {
  schema = schema;
  initialize() {
    if (this.state.testEventTriggersEnabled === void 0) {
      this.state.testEventTriggersEnabled = false;
    } else if (this.state.testEventTriggersEnabled) {
      if (this.state.deviceTestEnableKey.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 });
    if (this.events.activeHardwareFaults$Changed !== void 0) {
      this.reactTo(this.events.activeHardwareFaults$Changed, this.#triggerActiveHardwareFaultsChangedEvent);
    }
    if (this.events.activeRadioFaults$Changed !== void 0) {
      this.reactTo(this.events.activeRadioFaults$Changed, this.#triggerActiveRadioFaultsChangedEvent);
    }
    if (this.events.activeNetworkFaults$Changed !== void 0) {
      this.reactTo(this.events.activeNetworkFaults$Changed, this.#triggerActiveNetworkFaultsChangedEvent);
    }
  }
  #validateTestEnabledKey(enableKey) {
    if (enableKey.every((byte) => byte === 0)) {
      throw new import_types.StatusResponseError("Invalid test enable key, all zeros", import_types.StatusCode.ConstraintError);
    }
    enableKey.forEach((byte, index) => {
      if (byte !== this.state.deviceTestEnableKey[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();
    return {
      systemTimeMs: time - this.internal.bootUpTime,
      posixTimeMs: time
    };
  }
  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
            })
          }
        }
      ]
    }).length;
    const exchange = this.context.exchange;
    if (exchange === void 0) {
      throw new import_general.ImplementationError(`Illegal operation outside exchange 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.bootUpTime = import_general.Time.nowMs();
    this.internal.lastTotalOperationalHoursCounterUpdateTime = import_general.Time.nowMs();
    this.internal.lastTotalOperationalHoursTimer = import_general.Time.getPeriodicTimer(
      "GeneralDiagnostics.operationalHours",
      5 * 6e4,
      this.callback(this.#updateTotalOperationalHoursCounter)
    ).start();
    await this.#updateNetworkList();
  }
  #goingOffline() {
    this.internal.lastTotalOperationalHoursTimer?.stop();
    this.#updateTotalOperationalHoursCounter();
  }
  #updateTotalOperationalHoursCounter() {
    const now = import_general.Time.nowMs();
    const elapsedTime = now - this.internal.lastTotalOperationalHoursCounterUpdateTime;
    this.state.totalOperationalHoursCounter = this.state.totalOperationalHoursCounter + elapsedTime;
    this.internal.lastTotalOperationalHoursCounterUpdateTime = now;
  }
  async #updateNetworkList() {
    const mdnsService = this.env.get(import_protocol.MdnsService);
    const mdnsLimitedToNetworkInterfaces = mdnsService.limitedToNetInterface;
    const networkRuntime = this.endpoint.behaviors.internalsOf(import_NetworkServer.NetworkServer).runtime;
    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,
      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-fA-F]/g, "")),
      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
    }));
  }
}
((GeneralDiagnosticsServer2) => {
  class Internal {
    /** Remember the bootUp time for the device. */
    bootUpTime = import_general.Time.nowMs();
    /** 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 operational hours, counted in seconds, 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_Val.Val.properties](endpoint, _session) {
      return {
        /**
         * Dynamically calculate the upTime. This is ok because the attribute is not send out via subscriptions
         * anyway.
         */
        get upTime() {
          const bootUpTime = endpoint.behaviors.internalsOf(GeneralDiagnosticsServer2).bootUpTime;
          return Math.round((import_general.Time.nowMs() - bootUpTime) / 1e3);
        },
        /**
         * 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
          ).lastTotalOperationalHoursCounterUpdateTime;
          const totalOperationalHoursCounter = endpoint.stateOf(GeneralDiagnosticsServer2).totalOperationalHoursCounter;
          return Math.floor(
            (import_general.Time.nowMs() - lastTotalOperationalHoursCounterUpdateTime + totalOperationalHoursCounter) / (60 * 6e4)
          );
        }
      };
    }
  }
  GeneralDiagnosticsServer2.State = State;
})(GeneralDiagnosticsServer || (GeneralDiagnosticsServer = {}));
//# sourceMappingURL=GeneralDiagnosticsServer.js.map
