"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 Message_exports = {};
__export(Message_exports, {
  Message: () => Message,
  MessageOrigin: () => MessageOrigin,
  MessageRaw: () => MessageRaw,
  expectedCallback: () => expectedCallback,
  expectedResponse: () => expectedResponse,
  getDefaultPriority: () => getDefaultPriority,
  getDefaultPriorityStatic: () => getDefaultPriorityStatic,
  getExpectedCallback: () => getExpectedCallback,
  getExpectedCallbackStatic: () => getExpectedCallbackStatic,
  getExpectedResponse: () => getExpectedResponse,
  getExpectedResponseStatic: () => getExpectedResponseStatic,
  getFunctionType: () => getFunctionType,
  getFunctionTypeStatic: () => getFunctionTypeStatic,
  getMessageType: () => getMessageType,
  getMessageTypeStatic: () => getMessageTypeStatic,
  hasNodeId: () => hasNodeId,
  messageTypes: () => messageTypes,
  priority: () => priority
});
module.exports = __toCommonJS(Message_exports);
var import_core = require("@zwave-js/core");
var import_reflection = require("@zwave-js/core/reflection");
var import_shared = require("@zwave-js/shared");
var import_Constants = require("./Constants.js");
var import_MessageHeaders = require("./MessageHeaders.js");
var MessageOrigin;
(function(MessageOrigin2) {
  MessageOrigin2[MessageOrigin2["Controller"] = 0] = "Controller";
  MessageOrigin2[MessageOrigin2["Host"] = 1] = "Host";
})(MessageOrigin || (MessageOrigin = {}));
function hasNodeId(msg) {
  return typeof msg.nodeId === "number";
}
__name(hasNodeId, "hasNodeId");
function getMessageLength(data) {
  const remainingLength = data[1];
  return remainingLength + 2;
}
__name(getMessageLength, "getMessageLength");
class MessageRaw {
  static {
    __name(this, "MessageRaw");
  }
  type;
  functionType;
  payload;
  constructor(type, functionType, payload) {
    this.type = type;
    this.functionType = functionType;
    this.payload = payload;
  }
  static parse(data) {
    if (!data.length || data.length < 5) {
      throw new import_core.ZWaveError("Could not deserialize the message because it was truncated", import_core.ZWaveErrorCodes.PacketFormat_Truncated);
    }
    if (data[0] !== import_MessageHeaders.MessageHeaders.SOF) {
      throw new import_core.ZWaveError("Could not deserialize the message because it does not start with SOF", import_core.ZWaveErrorCodes.PacketFormat_Invalid);
    }
    const messageLength = getMessageLength(data);
    if (data.length < messageLength) {
      throw new import_core.ZWaveError("Could not deserialize the message because it was truncated", import_core.ZWaveErrorCodes.PacketFormat_Truncated);
    }
    const expectedChecksum = computeChecksum(data.subarray(0, messageLength));
    if (data[messageLength - 1] !== expectedChecksum) {
      throw new import_core.ZWaveError("Could not deserialize the message because the checksum didn't match", import_core.ZWaveErrorCodes.PacketFormat_Checksum);
    }
    const type = data[2];
    const functionType = data[3];
    const payloadLength = messageLength - 5;
    const payload = import_shared.Bytes.view(data.subarray(4, 4 + payloadLength));
    return new MessageRaw(type, functionType, payload);
  }
  withPayload(payload) {
    return new MessageRaw(this.type, this.functionType, payload);
  }
}
class Message {
  static {
    __name(this, "Message");
  }
  constructor(options = {}) {
    const {
      // Try to determine the message type if none is given
      type = getMessageType(this),
      // Try to determine the function type if none is given
      functionType = getFunctionType(this),
      // Fall back to decorated response/callback types if none is given
      expectedResponse: expectedResponse2 = getExpectedResponse(this),
      expectedCallback: expectedCallback2 = getExpectedCallback(this),
      payload = new import_shared.Bytes(),
      callbackId
    } = options;
    if (type == void 0) {
      throw new import_core.ZWaveError("A message must have a given or predefined message type", import_core.ZWaveErrorCodes.Argument_Invalid);
    }
    if (functionType == void 0) {
      throw new import_core.ZWaveError("A message must have a given or predefined function type", import_core.ZWaveErrorCodes.Argument_Invalid);
    }
    this.type = type;
    this.functionType = functionType;
    this.expectedResponse = expectedResponse2;
    this.expectedCallback = expectedCallback2;
    this.callbackId = callbackId;
    this.payload = payload;
  }
  static parse(data, ctx) {
    const raw = MessageRaw.parse(data);
    const Constructor = getMessageConstructor(raw.type, raw.functionType) ?? Message;
    return Constructor.from(raw, ctx);
  }
  /** Creates an instance of the message that is serialized in the given buffer */
  static from(raw, ctx) {
    return new this({
      type: raw.type,
      functionType: raw.functionType,
      payload: raw.payload
    });
  }
  type;
  functionType;
  expectedResponse;
  expectedCallback;
  payload;
  // TODO: Length limit 255
  /** Used to map requests to callbacks */
  callbackId;
  assertCallbackId() {
    if (this.callbackId == void 0) {
      throw new import_core.ZWaveError("Callback ID required but not set", import_core.ZWaveErrorCodes.PacketFormat_Invalid);
    }
  }
  /** Returns whether the callback ID is set */
  hasCallbackId() {
    return this.callbackId != void 0;
  }
  /**
   * Tests whether this message needs a callback ID to match its response
   */
  needsCallbackId() {
    return true;
  }
  /** Returns the response timeout for this message in case the default settings do not apply. */
  getResponseTimeout() {
    return;
  }
  /** Returns the callback timeout for this message in case the default settings do not apply. */
  getCallbackTimeout() {
    return;
  }
  /**
   * Serializes this message into a Buffer
   */
  // oxlint-disable-next-line no-unused-vars, @typescript-eslint/require-await
  async serialize(ctx) {
    const ret = new import_shared.Bytes(this.payload.length + 5);
    ret[0] = import_MessageHeaders.MessageHeaders.SOF;
    ret[1] = this.payload.length + 3;
    ret[2] = this.type;
    ret[3] = this.functionType;
    ret.set(this.payload, 4);
    ret[ret.length - 1] = computeChecksum(ret);
    return ret;
  }
  /** Generates a representation of this Message for the log */
  toLogEntry() {
    const tags = [
      this.type === import_Constants.MessageType.Request ? "REQ" : "RES",
      import_Constants.FunctionType[this.functionType]
    ];
    const nodeId = this.getNodeId();
    if (nodeId)
      tags.unshift((0, import_core.getNodeTag)(nodeId));
    return {
      tags,
      message: this.payload.length > 0 ? { payload: `0x${this.payload.toString("hex")}` } : void 0
    };
  }
  /** Generates the JSON representation of this Message */
  toJSON() {
    return this.toJSONInternal();
  }
  toJSONInternal() {
    const ret = {
      name: this.constructor.name,
      type: import_Constants.MessageType[this.type],
      functionType: import_Constants.FunctionType[this.functionType] || (0, import_shared.num2hex)(this.functionType)
    };
    if (this.expectedResponse != null) {
      ret.expectedResponse = import_Constants.FunctionType[this.functionType];
    }
    ret.payload = this.payload.toString("hex");
    return ret;
  }
  testMessage(msg, predicate) {
    if (predicate == void 0)
      return false;
    if (typeof predicate === "number") {
      return msg.functionType === predicate;
    }
    if ((0, import_shared.staticExtends)(predicate, Message)) {
      return msg instanceof predicate;
    } else {
      return predicate(this, msg);
    }
  }
  /** Tests whether this message expects an ACK from the controller */
  expectsAck() {
    return true;
  }
  /** Tests whether this message expects a response from the controller */
  expectsResponse() {
    return !!this.expectedResponse;
  }
  /** Tests whether this message expects a callback from the controller */
  expectsCallback() {
    return (
      // ...when it has a callback id that is not 0 (no callback)
      (this.hasCallbackId() && this.callbackId !== 0 || !this.needsCallbackId()) && !!this.expectedCallback
    );
  }
  /** Tests whether this message expects an update from the target node to finalize the transaction */
  // oxlint-disable-next-line no-unused-vars
  expectsNodeUpdate(ctx) {
    return false;
  }
  /** Returns a message specific timeout used to wait for an update from the target node */
  nodeUpdateTimeout;
  // Default: use driver timeout
  /** Checks if a message is an expected response for this message */
  isExpectedResponse(msg) {
    return msg.type === import_Constants.MessageType.Response && this.testMessage(msg, this.expectedResponse);
  }
  /** Checks if a message is an expected callback for this message */
  isExpectedCallback(msg) {
    if (msg.type !== import_Constants.MessageType.Request)
      return false;
    if (msg.functionType !== 0) {
      if (this.callbackId !== msg.callbackId) {
        return false;
      }
    }
    return this.testMessage(msg, this.expectedCallback);
  }
  /** Checks if a message is an expected node update for this message */
  isExpectedNodeUpdate(ctx, msg) {
    return false;
  }
  /** Gets set by the driver to remember an expected node update for this message that arrived before the Serial API command has finished. */
  prematureNodeUpdate;
  /** Finds the ID of the target or source node in a message, if it contains that information */
  getNodeId() {
    if (hasNodeId(this))
      return this.nodeId;
  }
  /**
   * Returns the node this message is linked to or undefined
   */
  tryGetNode(ctx) {
    const nodeId = this.getNodeId();
    if (nodeId != void 0)
      return ctx.getNode(nodeId);
  }
  _transmissionTimestamp;
  /** The timestamp when this message was (last) transmitted (in nanoseconds) */
  get transmissionTimestamp() {
    return this._transmissionTimestamp;
  }
  /** Marks this message as sent and sets the transmission timestamp */
  markAsSent() {
    this._transmissionTimestamp = (0, import_core.highResTimestamp)();
    this._completedTimestamp = void 0;
  }
  _completedTimestamp;
  get completedTimestamp() {
    return this._completedTimestamp;
  }
  /** Marks this message as completed and sets the corresponding timestamp */
  markAsCompleted() {
    this._completedTimestamp = (0, import_core.highResTimestamp)();
  }
  /** Returns the round trip time of this message from transmission until completion. */
  get rtt() {
    if (this._transmissionTimestamp == void 0)
      return void 0;
    if (this._completedTimestamp == void 0)
      return void 0;
    return this._completedTimestamp - this._transmissionTimestamp;
  }
}
function computeChecksum(message) {
  let ret = 255;
  for (let i = 1; i < message.length - 1; i++) {
    ret ^= message[i];
  }
  return ret;
}
__name(computeChecksum, "computeChecksum");
function getMessageTypeMapKey(messageType, functionType) {
  return JSON.stringify({ messageType, functionType });
}
__name(getMessageTypeMapKey, "getMessageTypeMapKey");
const messageTypesDecorator = (0, import_reflection.createReflectionDecorator)({
  name: "messageTypes",
  valueFromArgs: /* @__PURE__ */ __name((messageType, functionType) => ({
    messageType,
    functionType
  }), "valueFromArgs"),
  constructorLookupKey(target, messageType, functionType) {
    return getMessageTypeMapKey(messageType, functionType);
  }
});
const messageTypes = messageTypesDecorator.decorator;
function getMessageType(messageClass) {
  return messageTypesDecorator.lookupValue(messageClass)?.messageType;
}
__name(getMessageType, "getMessageType");
function getMessageTypeStatic(classConstructor) {
  return messageTypesDecorator.lookupValueStatic(classConstructor)?.messageType;
}
__name(getMessageTypeStatic, "getMessageTypeStatic");
function getFunctionType(messageClass) {
  return messageTypesDecorator.lookupValue(messageClass)?.functionType;
}
__name(getFunctionType, "getFunctionType");
function getFunctionTypeStatic(classConstructor) {
  return messageTypesDecorator.lookupValueStatic(classConstructor)?.functionType;
}
__name(getFunctionTypeStatic, "getFunctionTypeStatic");
function getMessageConstructor(messageType, functionType) {
  return messageTypesDecorator.lookupConstructorByKey(getMessageTypeMapKey(messageType, functionType));
}
__name(getMessageConstructor, "getMessageConstructor");
const expectedResponseDecorator = (0, import_reflection.createReflectionDecorator)({
  name: "expectedResponse",
  valueFromArgs: /* @__PURE__ */ __name((typeOrPredicate) => typeOrPredicate, "valueFromArgs"),
  constructorLookupKey: false
});
const expectedResponse = expectedResponseDecorator.decorator;
function getExpectedResponse(messageClass) {
  return expectedResponseDecorator.lookupValue(messageClass);
}
__name(getExpectedResponse, "getExpectedResponse");
function getExpectedResponseStatic(classConstructor) {
  return expectedResponseDecorator.lookupValueStatic(classConstructor);
}
__name(getExpectedResponseStatic, "getExpectedResponseStatic");
const expectedCallbackDecorator = (0, import_reflection.createReflectionDecorator)({
  name: "expectedCallback",
  valueFromArgs: /* @__PURE__ */ __name((typeOrPredicate) => typeOrPredicate, "valueFromArgs"),
  constructorLookupKey: false
});
function expectedCallback(typeOrPredicate) {
  return expectedCallbackDecorator.decorator(typeOrPredicate);
}
__name(expectedCallback, "expectedCallback");
function getExpectedCallback(messageClass) {
  return expectedCallbackDecorator.lookupValue(messageClass);
}
__name(getExpectedCallback, "getExpectedCallback");
function getExpectedCallbackStatic(classConstructor) {
  return expectedCallbackDecorator.lookupValueStatic(classConstructor);
}
__name(getExpectedCallbackStatic, "getExpectedCallbackStatic");
const priorityDecorator = (0, import_reflection.createReflectionDecorator)({
  name: "priority",
  valueFromArgs: /* @__PURE__ */ __name((priority2) => priority2, "valueFromArgs"),
  constructorLookupKey: false
});
const priority = priorityDecorator.decorator;
function getDefaultPriority(messageClass) {
  return priorityDecorator.lookupValue(messageClass);
}
__name(getDefaultPriority, "getDefaultPriority");
function getDefaultPriorityStatic(classConstructor) {
  return priorityDecorator.lookupValueStatic(classConstructor);
}
__name(getDefaultPriorityStatic, "getDefaultPriorityStatic");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  Message,
  MessageOrigin,
  MessageRaw,
  expectedCallback,
  expectedResponse,
  getDefaultPriority,
  getDefaultPriorityStatic,
  getExpectedCallback,
  getExpectedCallbackStatic,
  getExpectedResponse,
  getExpectedResponseStatic,
  getFunctionType,
  getFunctionTypeStatic,
  getMessageType,
  getMessageTypeStatic,
  hasNodeId,
  messageTypes,
  priority
});
//# sourceMappingURL=Message.js.map
