"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
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 mockServer_exports = {};
__export(mockServer_exports, {
  MockServer: () => MockServer,
  createMockNodeOptionsFromDump: () => createMockNodeOptionsFromDump
});
module.exports = __toCommonJS(mockServer_exports);
var import_ciao = require("@homebridge/ciao");
var import_cc = require("@zwave-js/cc");
var import_core = require("@zwave-js/core");
var import_serial = require("@zwave-js/serial");
var import_mock = require("@zwave-js/serial/mock");
var import_shared = require("@zwave-js/shared");
var import_testing = require("@zwave-js/testing");
var import_deferred_promise = require("alcalzone-shared/deferred-promise");
var import_node_net = require("node:net");
var import_Testing = require("./Testing.js");
var import_Utils = require("./Utils.js");
class MockServer {
  static {
    __name(this, "MockServer");
  }
  options;
  constructor(options = {}) {
    this.options = options;
  }
  serialport;
  binding;
  server;
  responder;
  service;
  mockController;
  mockNodes;
  async start() {
    const { serial, port: mockPort } = await (0, import_mock.createAndOpenMockedZWaveSerialPort)();
    this.serialport = serial;
    this.binding = mockPort;
    console.log("Mock serial port opened");
    ({ mockController: this.mockController, mockNodes: this.mockNodes } = prepareMocks(mockPort, serial, this.options.config?.controller, this.options.config?.nodes));
    if (typeof this.options.config?.onInit === "function") {
      this.options.config.onInit(this.mockController, this.mockNodes);
    }
    const faucet = new import_serial.Faucet(serial.readable);
    this.server = (0, import_node_net.createServer)((socket) => {
      if (!this.serialport) {
        console.error("Serial port not initialized");
        socket.destroy();
        return;
      }
      console.log("Client connected");
      const writable = new WritableStream({
        write: /* @__PURE__ */ __name((chunk) => {
          if (chunk.type !== import_serial.ZWaveSerialFrameType.SerialAPI)
            return;
          if (typeof chunk.data === "number") {
            socket.write(Uint8Array.from([chunk.data]));
          } else {
            socket.write(chunk.data);
          }
        }, "write")
      });
      faucet.connect(writable);
      socket.on("close", () => {
        faucet.disconnect();
        void writable.close();
        console.log("Client disconnected");
      });
      socket.on("data", async (chunk) => {
        await this.serialport?.writeAsync(chunk).catch((e) => {
          console.error(`Error writing to serialport`, e);
        });
      });
    });
    const port = this.options.port ?? 5555;
    this.responder = (0, import_ciao.getResponder)();
    this.service = this.responder.createService({
      name: "zwave-mock-server",
      type: "zwave",
      protocol: "tcp",
      port,
      txt: {
        manufacturer: "Z-Wave JS",
        model: "Mock Server"
      }
    });
    this.server.maxConnections = 1;
    const promise = (0, import_deferred_promise.createDeferredPromise)();
    this.server.on("error", (err) => {
      if (err.code === "EADDRINUSE") {
        promise.reject(err);
      }
    });
    this.server.listen({
      host: this.options.interface,
      port
    }, async () => {
      const address = this.server.address();
      console.log(`Server listening on tcp://${address.address}:${address.port}`);
      promise.resolve();
      try {
        await this.service.advertise();
        console.log(`Enabled mDNS service discovery.`);
      } catch (e) {
        console.error(`Failed to enable mDNS service discovery: ${(0, import_shared.getErrorMessage)(e)}`);
      }
    });
  }
  async stop() {
    console.log("Shutting down mock server...");
    await this.service?.end();
    await this.service?.destroy();
    await this.responder?.shutdown();
    this.mockController?.destroy();
    this.server?.close();
    await this.serialport?.close();
    this.binding?.destroy();
    console.log("Mock server shut down");
  }
}
function prepareMocks(mockPort, serial, controller = {}, nodes = []) {
  const mockController = new import_testing.MockController({
    homeId: 2119630849,
    ownNodeId: 1,
    ...controller,
    mockPort,
    serial
  });
  mockController.defineBehavior(...(0, import_Testing.createDefaultMockControllerBehaviors)());
  if (controller.behaviors) {
    mockController.defineBehavior(...controller.behaviors);
  }
  const mockNodes = [];
  for (const node of nodes) {
    const mockNode = new import_testing.MockNode({
      ...node,
      controller: mockController
    });
    mockController.addNode(mockNode);
    mockNodes.push(mockNode);
    mockNode.defineBehavior(...(0, import_Testing.createDefaultMockNodeBehaviors)());
    if (node.behaviors) {
      mockNode.defineBehavior(...node.behaviors);
    }
  }
  return {
    mockController,
    mockNodes
  };
}
__name(prepareMocks, "prepareMocks");
function createMockNodeOptionsFromDump(dump) {
  const ret = {
    id: dump.id
  };
  ret.capabilities = (0, import_testing.getDefaultMockNodeCapabilities)();
  if (typeof dump.isListening === "boolean") {
    ret.capabilities.isListening = dump.isListening;
  }
  if (dump.isFrequentListening !== "unknown") {
    ret.capabilities.isFrequentListening = dump.isFrequentListening;
  }
  if (typeof dump.isRouting === "boolean") {
    ret.capabilities.isRouting = dump.isRouting;
  }
  if (typeof dump.supportsBeaming === "boolean") {
    ret.capabilities.supportsBeaming = dump.supportsBeaming;
  }
  if (typeof dump.supportsSecurity === "boolean") {
    ret.capabilities.supportsSecurity = dump.supportsSecurity;
  }
  if (typeof dump.supportedDataRates === "boolean") {
    ret.capabilities.supportedDataRates = dump.supportedDataRates;
  }
  if (import_Utils.ProtocolVersion[dump.protocol] !== void 0) {
    ret.capabilities.protocolVersion = import_Utils.ProtocolVersion[dump.protocol];
  }
  if (dump.deviceClass !== "unknown") {
    ret.capabilities.basicDeviceClass = dump.deviceClass.basic.key;
    ret.capabilities.genericDeviceClass = dump.deviceClass.generic.key;
    ret.capabilities.specificDeviceClass = dump.deviceClass.specific.key;
  }
  ret.capabilities.firmwareVersion = dump.fingerprint.firmwareVersion;
  ret.capabilities.manufacturerId = parseInt(dump.fingerprint.manufacturerId, 16);
  ret.capabilities.productType = parseInt(dump.fingerprint.productType, 16);
  ret.capabilities.productId = parseInt(dump.fingerprint.productId, 16);
  for (const [ccName, ccDump] of Object.entries(dump.commandClasses)) {
    const ccId = import_core.CommandClasses[ccName];
    if (ccId == void 0)
      continue;
    if (ccId === import_core.CommandClasses.Security || ccId === import_core.CommandClasses["Security 2"]) {
      continue;
    }
    if (ccId === import_core.CommandClasses.Supervision) {
      continue;
    }
    if (ccId === import_core.CommandClasses["Transport Service"]) {
      continue;
    }
    ret.capabilities.commandClasses ??= [];
    ret.capabilities.commandClasses.push(createCCCapabilitiesFromDump(ccId, ccDump));
  }
  if (dump.endpoints) {
    for (const [indexStr, endpointDump] of Object.entries(dump.endpoints)) {
      const epCaps = (0, import_testing.getDefaultMockEndpointCapabilities)(
        // @ts-expect-error We are initializing the device classes above
        ret.capabilities
      );
      let epCCs;
      if (endpointDump.deviceClass !== "unknown") {
        epCaps.genericDeviceClass = endpointDump.deviceClass.generic.key;
        epCaps.specificDeviceClass = endpointDump.deviceClass.specific.key;
      }
      for (const [ccName, ccDump] of Object.entries(endpointDump.commandClasses)) {
        const ccId = import_core.CommandClasses[ccName];
        if (ccId == void 0)
          continue;
        if (ccId === import_core.CommandClasses.Security || ccId === import_core.CommandClasses["Security 2"]) {
          continue;
        }
        epCCs ??= [];
        epCCs.push(createCCCapabilitiesFromDump(ccId, ccDump));
      }
      ret.capabilities.endpoints ??= [];
      ret.capabilities.endpoints.push({
        ...epCaps,
        commandClasses: epCCs
      });
    }
  }
  return ret;
}
__name(createMockNodeOptionsFromDump, "createMockNodeOptionsFromDump");
function createCCCapabilitiesFromDump(ccId, dump) {
  const ret = {
    ccId,
    isSupported: dump.isSupported,
    isControlled: dump.isControlled,
    secure: dump.secure,
    version: dump.version
  };
  if (ccId === import_core.CommandClasses.Configuration) {
    Object.assign(ret, createConfigurationCCCapabilitiesFromDump(dump));
  } else if (ccId === import_core.CommandClasses.Notification) {
    Object.assign(ret, createNotificationCCCapabilitiesFromDump(dump));
  } else if (ccId === import_core.CommandClasses["Binary Switch"]) {
    Object.assign(ret, createBinarySwitchCCCapabilitiesFromDump(dump));
  } else if (ccId === import_core.CommandClasses["Multilevel Switch"]) {
    Object.assign(ret, createMultilevelSwitchCCCapabilitiesFromDump(dump));
  } else if (ccId === import_core.CommandClasses["Sound Switch"]) {
    Object.assign(ret, createSoundSwitchCCCapabilitiesFromDump(dump));
  }
  return ret;
}
__name(createCCCapabilitiesFromDump, "createCCCapabilitiesFromDump");
function createConfigurationCCCapabilitiesFromDump(dump) {
  const ret = {
    bulkSupport: false,
    parameters: []
  };
  for (const val of dump.values) {
    if (typeof val.property !== "number")
      continue;
    if (val.propertyKey != void 0)
      continue;
    if (!val.metadata)
      continue;
    const meta = val.metadata;
    ret.parameters.push({
      "#": val.property,
      valueSize: meta.valueSize ?? 1,
      name: meta.label,
      info: meta.description,
      format: meta.format,
      minValue: meta.min,
      maxValue: meta.max,
      defaultValue: meta.default,
      readonly: !meta.writeable
    });
  }
  return ret;
}
__name(createConfigurationCCCapabilitiesFromDump, "createConfigurationCCCapabilitiesFromDump");
function createNotificationCCCapabilitiesFromDump(dump) {
  const supportsV1Alarm = findDumpedValue(dump, import_core.CommandClasses.Notification, import_cc.NotificationCCValues.supportsV1Alarm.id, false);
  const ret = {
    supportsV1Alarm,
    notificationTypesAndEvents: {}
  };
  const supportedNotificationTypes = findDumpedValue(dump, import_core.CommandClasses.Notification, import_cc.NotificationCCValues.supportedNotificationTypes.id, []);
  for (const type of supportedNotificationTypes) {
    const supportedEvents = findDumpedValue(dump, import_core.CommandClasses.Notification, import_cc.NotificationCCValues.supportedNotificationEvents(type).id, []);
    ret.notificationTypesAndEvents[type] = supportedEvents;
  }
  return ret;
}
__name(createNotificationCCCapabilitiesFromDump, "createNotificationCCCapabilitiesFromDump");
function createBinarySwitchCCCapabilitiesFromDump(dump) {
  const defaultValue = findDumpedValue(dump, import_core.CommandClasses["Binary Switch"], import_cc.BinarySwitchCCValues.currentValue.id, void 0);
  return {
    defaultValue
  };
}
__name(createBinarySwitchCCCapabilitiesFromDump, "createBinarySwitchCCCapabilitiesFromDump");
function createMultilevelSwitchCCCapabilitiesFromDump(dump) {
  const defaultValue = findDumpedValue(dump, import_core.CommandClasses["Multilevel Switch"], import_cc.MultilevelSwitchCCValues.currentValue.id, void 0);
  const switchType = findDumpedValue(dump, import_core.CommandClasses["Multilevel Switch"], import_cc.MultilevelSwitchCCValues.switchType.id, import_cc.SwitchType["Down/Up"]);
  return {
    defaultValue,
    primarySwitchType: switchType
  };
}
__name(createMultilevelSwitchCCCapabilitiesFromDump, "createMultilevelSwitchCCCapabilitiesFromDump");
function createSoundSwitchCCCapabilitiesFromDump(dump) {
  const defaultToneId = findDumpedValue(dump, import_core.CommandClasses["Sound Switch"], import_cc.SoundSwitchCCValues.defaultToneId.id, 1);
  const defaultVolume = findDumpedValue(dump, import_core.CommandClasses["Sound Switch"], import_cc.SoundSwitchCCValues.defaultVolume.id, 50);
  const ret = {
    defaultToneId,
    defaultVolume,
    tones: []
  };
  const tonesMetadata = findDumpedMetadata(dump, import_core.CommandClasses["Sound Switch"], import_cc.SoundSwitchCCValues.toneId.id);
  if (tonesMetadata?.states) {
    for (const [toneIdStr, nameAndDuration] of Object.entries(tonesMetadata.states)) {
      const toneId = parseInt(toneIdStr);
      if (Number.isNaN(toneId) || toneId < 1 || toneId > 254)
        continue;
      const durationIndex = nameAndDuration.lastIndexOf("(");
      if (durationIndex === -1)
        continue;
      const name = nameAndDuration.slice(0, durationIndex).trim();
      const duration = parseInt(nameAndDuration.slice(durationIndex + 1, -1), 10);
      if (Number.isNaN(duration))
        continue;
      ret.tones.push({ name, duration });
    }
  }
  return ret;
}
__name(createSoundSwitchCCCapabilitiesFromDump, "createSoundSwitchCCCapabilitiesFromDump");
function findDumpedValue(dump, commandClass, valueId, defaultValue) {
  return dump.values.find((id) => id.property === valueId.property && id.propertyKey === valueId.propertyKey)?.value ?? defaultValue;
}
__name(findDumpedValue, "findDumpedValue");
function findDumpedMetadata(dump, commandClass, valueId) {
  return dump.values.find((id) => id.property === valueId.property && id.propertyKey === valueId.propertyKey)?.metadata;
}
__name(findDumpedMetadata, "findDumpedMetadata");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  MockServer,
  createMockNodeOptionsFromDump
});
//# sourceMappingURL=mockServer.js.map
