import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core";
import { Bytes } from "@zwave-js/shared";
import { decodeVarInt, encodeVarInt } from "./ProtobufHelpers.js";
/**
 * ESPHome message types enum
 */
export var ESPHomeMessageType;
(function (ESPHomeMessageType) {
    // Connection messages
    ESPHomeMessageType[ESPHomeMessageType["HelloRequest"] = 1] = "HelloRequest";
    ESPHomeMessageType[ESPHomeMessageType["HelloResponse"] = 2] = "HelloResponse";
    ESPHomeMessageType[ESPHomeMessageType["DisconnectRequest"] = 5] = "DisconnectRequest";
    ESPHomeMessageType[ESPHomeMessageType["DisconnectResponse"] = 6] = "DisconnectResponse";
    ESPHomeMessageType[ESPHomeMessageType["PingRequest"] = 7] = "PingRequest";
    ESPHomeMessageType[ESPHomeMessageType["PingResponse"] = 8] = "PingResponse";
    ESPHomeMessageType[ESPHomeMessageType["DeviceInfoRequest"] = 9] = "DeviceInfoRequest";
    ESPHomeMessageType[ESPHomeMessageType["DeviceInfoResponse"] = 10] = "DeviceInfoResponse";
    // Z-Wave proxy messages
    ESPHomeMessageType[ESPHomeMessageType["ZWaveProxyFrame"] = 128] = "ZWaveProxyFrame";
    ESPHomeMessageType[ESPHomeMessageType["ZWaveProxyRequest"] = 129] = "ZWaveProxyRequest";
})(ESPHomeMessageType || (ESPHomeMessageType = {}));
/**
 * Represents a raw ESPHome message with minimal parsing
 */
export class ESPHomeMessageRaw {
    messageType;
    payload;
    constructor(messageType, payload) {
        this.messageType = messageType;
        this.payload = payload;
    }
    /**
     * Parses a raw plaintext ESPHome frame into a MessageRaw instance.
     * Format: [0x00][VarInt size][VarInt type][payload]
     */
    static parse(data) {
        if (data.length < 3) {
            throw new ZWaveError("Frame too short", ZWaveErrorCodes.PacketFormat_Truncated);
        }
        let offset = 0;
        // Check indicator byte
        if (data[offset] !== 0x00) {
            throw new ZWaveError(`Invalid frame indicator: expected 0x00, got 0x${data[offset].toString(16).padStart(2, "0")}`, ZWaveErrorCodes.PacketFormat_Invalid);
        }
        offset++;
        // Decode payload size
        const payloadSize = decodeVarInt(data, offset);
        offset += payloadSize.bytesRead;
        // Decode message type
        const messageType = decodeVarInt(data, offset);
        offset += messageType.bytesRead;
        // Extract payload
        if (offset + payloadSize.value > data.length) {
            throw new ZWaveError("Could not deserialize the message because it was truncated", ZWaveErrorCodes.PacketFormat_Truncated);
        }
        const payload = Bytes.view(data.slice(offset, offset + payloadSize.value));
        return {
            message: new ESPHomeMessageRaw(messageType.value, payload),
            bytesRead: offset + payloadSize.value,
        };
    }
    /**
     * Parses a raw Noise-decrypted ESPHome message into a MessageRaw instance.
     * Format: [2-byte BE type][2-byte BE len][payload]
     */
    static parseFromNoise(data) {
        if (data.length < 4) {
            throw new ZWaveError("Noise message too short", ZWaveErrorCodes.PacketFormat_Truncated);
        }
        const messageType = data.readUInt16BE(0);
        const dataLen = data.readUInt16BE(2);
        if (4 + dataLen > data.length) {
            throw new ZWaveError("Noise message was truncated", ZWaveErrorCodes.PacketFormat_Truncated);
        }
        const payload = Bytes.view(data.subarray(4, 4 + dataLen));
        return {
            message: new ESPHomeMessageRaw(messageType, payload),
            bytesRead: 4 + dataLen,
        };
    }
}
/**
 * Base class for all ESPHome messages
 */
export class ESPHomeMessage {
    constructor(options = {}) {
        const messageType = options.messageType ?? getMessageType(this);
        if (messageType == undefined) {
            throw new ZWaveError("An ESPHome message must have a given or predefined message type", ZWaveErrorCodes.Argument_Invalid);
        }
        this.messageType = messageType;
        this.payload = options.payload ?? new Bytes();
    }
    messageType;
    payload;
    /**
     * Parses a raw ESPHome message and returns the appropriate message instance
     */
    static parse(data) {
        const { message: raw, bytesRead } = ESPHomeMessageRaw.parse(data);
        const Constructor = getESPHomeMessageConstructor(raw.messageType)
            ?? ESPHomeMessage;
        return { message: Constructor.from(raw), bytesRead };
    }
    /**
     * Creates an instance of the message from a raw message.
     */
    static from(raw) {
        return new this({
            messageType: raw.messageType,
            payload: raw.payload,
        });
    }
    /**
     * Serializes this message into an ESPHome frame (plaintext format)
     */
    serialize() {
        return Bytes.concat([
            [0x00], // Indicator byte
            encodeVarInt(this.payload.length),
            encodeVarInt(this.messageType),
            this.payload,
        ]);
    }
    /**
     * Serializes this message for Noise transport.
     * Format: [2-byte BE type][2-byte BE len][payload]
     */
    serializeForNoise() {
        const header = new Bytes(4);
        header.writeUInt16BE(this.messageType, 0);
        header.writeUInt16BE(this.payload.length, 2);
        return Bytes.concat([header, this.payload]);
    }
    /**
     * Parses a message from Noise-decrypted data.
     * Format: [2-byte BE type][2-byte BE len][payload]
     */
    static parseFromNoise(data) {
        const { message: raw, bytesRead } = ESPHomeMessageRaw.parseFromNoise(data);
        const Constructor = getESPHomeMessageConstructor(raw.messageType)
            ?? ESPHomeMessage;
        return { message: Constructor.from(raw), bytesRead };
    }
}
// Storage for message type decorators
const MESSAGE_TYPE_STORAGE = new Map();
const MESSAGE_CONSTRUCTOR_STORAGE = new Map();
/**
 * Decorator to set the message type for a message class
 */
export function messageType(type) {
    return function (target) {
        MESSAGE_TYPE_STORAGE.set(target, type);
        MESSAGE_CONSTRUCTOR_STORAGE.set(type, target);
        return target;
    };
}
/**
 * Gets the message type for a message instance/class
 */
function getMessageType(msg) {
    return MESSAGE_TYPE_STORAGE.get(msg.constructor);
}
/**
 * Looks up the message constructor for a given ESPHome message type
 */
function getESPHomeMessageConstructor(messageType) {
    return MESSAGE_CONSTRUCTOR_STORAGE.get(messageType);
}
//# sourceMappingURL=ESPHomeMessage.js.map