import { MPDUHeaderType, NODE_ID_BROADCAST, NODE_ID_BROADCAST_LR, Protocols, ZWaveError, ZWaveErrorCodes, ZnifferRegion, parseNodeBitMask, rssiToString, validatePayload, znifferProtocolDataRateToString, } from "@zwave-js/core";
import { ZnifferFrameType, } from "@zwave-js/serial";
import { parseRSSI } from "@zwave-js/serial/serialapi";
import { Bytes, buffer2hex, pick, staticExtends, } from "@zwave-js/shared";
import { ExplorerFrameCommand, LongRangeFrameType, ZWaveFrameType, } from "./_Types.js";
function getChannelConfiguration(region) {
    switch (region) {
        case ZnifferRegion.Japan:
        case ZnifferRegion.Korea:
            return "3";
        case ZnifferRegion["USA (Long Range)"]:
        case ZnifferRegion["USA (Long Range, backup)"]:
        case ZnifferRegion["USA (Long Range, end device)"]:
            return "4";
        default:
            return "1/2";
    }
}
function longRangeBeamPowerToDBm(power) {
    return [
        -6,
        -2,
        2,
        6,
        10,
        13,
        16,
        19,
        21,
        23,
        25,
        26,
        27,
        28,
        29,
        30,
    ][power];
}
function formatNodeId(nodeId) {
    return nodeId.toString().padStart(3, "0");
}
function formatRoute(source, repeaters, destination, direction, currentHop, failedHop) {
    return [
        direction === "outbound"
            ? formatNodeId(source)
            : formatNodeId(destination),
        ...repeaters.map(formatNodeId),
        direction === "outbound"
            ? formatNodeId(destination)
            : formatNodeId(source),
    ].map((id, i) => {
        if (i === 0)
            return id;
        if (i - 1 === failedHop)
            return " × " + id;
        if (i - 1 === currentHop) {
            return (direction === "outbound" ? " » " : " « ") + id;
        }
        return (direction === "outbound" ? " › " : " ‹ ") + id;
    })
        .join("");
}
export function parseMPDU(frame) {
    switch (frame.channel) {
        case 0:
        case 1:
        case 2:
            return ZWaveMPDU.from(frame);
        case 3:
        case 4:
            return LongRangeMPDU.from(frame);
        default:
            validatePayload.fail(`Unsupported channel ${frame.channel}. MPDU payload: ${buffer2hex(frame.payload)}`);
    }
}
export class LongRangeMPDU {
    constructor(options) {
        const data = options.data;
        this.frameInfo = options.frameInfo;
        if (options.frameInfo.channel !== 3) {
            validatePayload.fail(`Unsupported channel ${options.frameInfo.channel} for LongRangeMPDU`);
        }
        this.homeId = data.readUInt32BE(0);
        const nodeIds = data.readUIntBE(4, 3);
        this.sourceNodeId = nodeIds >>> 12;
        this.destinationNodeId = nodeIds & 0xfff;
        // skip length byte
        const frameControl = data[8];
        this.ackRequested = !!(frameControl & 0b1000_0000);
        const hasExtendedHeader = !!(frameControl & 0b0100_0000);
        this.headerType = frameControl & 0b0000_0111;
        this.sequenceNumber = data[9];
        this.noiseFloor = parseRSSI(data, 10);
        this.txPower = data.readInt8(11);
        let offset = 12;
        if (hasExtendedHeader) {
            const extensionControl = data[offset++];
            const extensionLength = extensionControl & 0b111;
            // const discardUnknown = extensionControl & 0b0000_1000;
            // const extensionType = (extensionControl & 0b0111_0000) >>> 4;
            // TODO: Parse extension (once there is a definition)
            offset += extensionLength;
        }
        const Constructor = this.headerType === MPDUHeaderType.Acknowledgement
            ? AckLongRangeMPDU
            : this.headerType === MPDUHeaderType.Singlecast
                ? SinglecastLongRangeMPDU
                : undefined;
        if (!Constructor) {
            validatePayload.fail(`Unsupported Long Range MPDU header type ${this.headerType}`);
        }
        else if (new.target !== Constructor
            && !staticExtends(new.target, Constructor)) {
            return new Constructor(options);
        }
        this.payload = data.subarray(offset);
    }
    frameInfo;
    homeId;
    sourceNodeId;
    destinationNodeId;
    ackRequested;
    headerType;
    sequenceNumber;
    noiseFloor;
    txPower;
    payload;
    static from(msg) {
        return new LongRangeMPDU({
            data: msg.payload,
            frameInfo: pick(msg, [
                "channel",
                "frameType",
                "region",
                "protocolDataRate",
                "rssiRaw",
            ]),
        });
    }
    toLogEntry() {
        const tags = [
            formatRoute(this.sourceNodeId, [], this.destinationNodeId, 
            // Singlecast frames do not contain a bit for this, we consider them all "outbound"
            "outbound", 0),
        ];
        if (this.headerType === MPDUHeaderType.Acknowledgement) {
            tags.unshift("ACK");
        }
        const message = {
            "sequence no.": this.sequenceNumber,
            channel: this.frameInfo.channel,
            "protocol/data rate": znifferProtocolDataRateToString(this.frameInfo.protocolDataRate),
            "TX power": `${this.txPower} dBm`,
            RSSI: this.frameInfo.rssi != undefined
                ? rssiToString(this.frameInfo.rssi)
                : this.frameInfo.rssiRaw.toString(),
            "noise floor": rssiToString(this.noiseFloor),
        };
        if (this.headerType !== MPDUHeaderType.Acknowledgement) {
            message["ack requested"] = this.ackRequested;
        }
        if (this.payload.length > 0) {
            message.payload = buffer2hex(this.payload);
        }
        return {
            tags,
            message,
        };
    }
}
export class SinglecastLongRangeMPDU extends LongRangeMPDU {
    toLogEntry() {
        const { tags, message: original } = super.toLogEntry();
        const message = {
            ...original,
            payload: buffer2hex(this.payload),
        };
        return {
            tags,
            message,
        };
    }
}
export class AckLongRangeMPDU extends LongRangeMPDU {
    constructor(options) {
        super(options);
        this.incomingRSSI = parseRSSI(this.payload, 0);
        this.payload = this.payload.subarray(1);
    }
    incomingRSSI;
    toLogEntry() {
        const { tags, message: original } = super.toLogEntry();
        const message = {
            ...original,
            "incoming RSSI": rssiToString(this.incomingRSSI),
        };
        if (this.payload.length > 0) {
            message.payload = buffer2hex(this.payload);
        }
        return {
            tags,
            message,
        };
    }
}
export class ZWaveMPDU {
    constructor(options) {
        const data = options.data;
        this.frameInfo = options.frameInfo;
        let destinationOffset = 8;
        const frameControl = data.subarray(5, 7);
        switch (options.frameInfo.channel) {
            case 0:
            case 1: {
                this.routed = !!(frameControl[0] & 0b1000_0000);
                this.ackRequested = !!(frameControl[0] & 0b0100_0000);
                this.lowPower = !!(frameControl[0] & 0b0010_0000);
                this.speedModified = !!(frameControl[0] & 0b0001_0000);
                this.headerType = frameControl[0] & 0b0000_1111;
                this.beamingInfo = frameControl[1] & 0b0110_0000;
                this.sequenceNumber = frameControl[1] & 0b0000_1111;
                break;
            }
            case 2: {
                this.routed = false;
                this.ackRequested = !!(frameControl[0] & 0b1000_0000);
                this.lowPower = !!(frameControl[0] & 0b0100_0000);
                this.speedModified = false;
                this.headerType = frameControl[0] & 0b0000_1111;
                this.beamingInfo = frameControl[1] & 0b0111_0000;
                this.sequenceNumber = data[destinationOffset];
                destinationOffset++;
                break;
            }
            case 3:
            case 4: {
                validatePayload.fail(`Channel ${options.frameInfo.channel} (ZWLR) must be parsed as a LongRangeMPDU!`);
            }
            default: {
                validatePayload.fail(`Unsupported channel ${options.frameInfo.channel}. MPDU payload: ${buffer2hex(data)}`);
            }
        }
        const Constructor = this.headerType === MPDUHeaderType.Acknowledgement
            ? AckZWaveMPDU
            : (this.headerType === MPDUHeaderType.Routed
                || (this.headerType === MPDUHeaderType.Singlecast
                    && this.routed))
                ? RoutedZWaveMPDU
                : this.headerType === MPDUHeaderType.Singlecast
                    ? SinglecastZWaveMPDU
                    : this.headerType === MPDUHeaderType.Multicast
                        ? MulticastZWaveMPDU
                        : this.headerType === MPDUHeaderType.Explorer
                            ? ExplorerZWaveMPDU
                            : undefined;
        if (!Constructor) {
            validatePayload.fail(`Unsupported MPDU header type ${this.headerType}`);
        }
        else if (new.target !== Constructor
            && !staticExtends(new.target, Constructor)) {
            return new Constructor(options);
        }
        // FIXME: Parse Beams
        this.homeId = data.readUInt32BE(0);
        this.sourceNodeId = data[4];
        // byte 7 is another length byte
        // FIXME: This should consider the multicast control byte
        const destinationLength = this.headerType === MPDUHeaderType.Multicast
            ? 30
            : 1;
        this.destinationBuffer = data.subarray(destinationOffset, destinationOffset + destinationLength);
        this.payload = data.subarray(destinationOffset + destinationLength);
    }
    frameInfo;
    homeId;
    sourceNodeId;
    routed;
    ackRequested;
    lowPower;
    speedModified;
    headerType;
    beamingInfo;
    sequenceNumber;
    destinationBuffer;
    payload;
    static from(msg) {
        return new ZWaveMPDU({
            data: msg.payload,
            frameInfo: pick(msg, [
                "channel",
                "frameType",
                "region",
                "protocolDataRate",
                "rssiRaw",
            ]),
        });
    }
    toLogEntry() {
        const tags = [formatNodeId(this.sourceNodeId)];
        const message = {
            "sequence no.": this.sequenceNumber,
            channel: this.frameInfo.channel,
            "protocol/data rate": znifferProtocolDataRateToString(this.frameInfo.protocolDataRate)
                + (this.speedModified ? " (reduced)" : ""),
            RSSI: this.frameInfo.rssi != undefined
                ? rssiToString(this.frameInfo.rssi)
                : this.frameInfo.rssiRaw.toString(),
        };
        return {
            tags,
            message,
        };
    }
}
export class SinglecastZWaveMPDU extends ZWaveMPDU {
    constructor(options) {
        super(options);
        this.destinationNodeId = this.destinationBuffer[0];
    }
    destinationNodeId;
    toLogEntry() {
        const { tags, message: original } = super.toLogEntry();
        tags[0] = formatRoute(this.sourceNodeId, [], this.destinationNodeId, 
        // Singlecast frames do not contain a bit for this, we consider them all "outbound"
        "outbound", 0);
        const message = {
            ...original,
            "ack requested": this.ackRequested,
            payload: buffer2hex(this.payload),
        };
        return {
            tags,
            message,
        };
    }
}
export class AckZWaveMPDU extends ZWaveMPDU {
    constructor(options) {
        super(options);
        this.destinationNodeId = this.destinationBuffer[0];
    }
    destinationNodeId;
    toLogEntry() {
        const { tags, message } = super.toLogEntry();
        tags[0] = formatRoute(this.sourceNodeId, [], this.destinationNodeId, 
        // ACK frames do not contain a bit for this, we consider them all "inbound"
        "inbound", 0);
        tags.unshift("ACK");
        return {
            tags,
            message,
        };
    }
}
export class RoutedZWaveMPDU extends ZWaveMPDU {
    constructor(options) {
        super(options);
        const channelConfig = getChannelConfiguration(this.frameInfo.region);
        this.direction = (this.payload[0] & 0b1) ? "inbound" : "outbound";
        this.routedAck = !!(this.payload[0] & 0b10);
        this.routedError = !!(this.payload[0] & 0b100);
        const hasExtendedHeader = !!(this.payload[0] & 0b1000);
        if (this.routedError) {
            this.failedHop = this.payload[0] >>> 4;
        }
        else if (channelConfig === "1/2") {
            // @ts-expect-error speedModified is readonly
            this.speedModified = !!(this.payload[0] & 0b10000);
        }
        this.hop = this.payload[1] & 0b1111;
        // The hop field in the MPDU indicates which repeater should handle the frame next.
        // This means that for an inbound frame between repeater 0 and 1, the value is one
        // less (0) than for an outbound frame (1). This also means that the field overflows
        // to 0x0f when the frame returns to the source node.
        //
        // We normalize this, so hop = 0 always means the frame is transmitted between the source node and repeater 0.
        if (this.direction === "inbound") {
            this.hop = (this.hop + 1) % 16;
        }
        const numRepeaters = this.payload[1] >>> 4;
        this.repeaters = [...this.payload.subarray(2, 2 + numRepeaters)];
        let offset = 2 + numRepeaters;
        if (channelConfig === "3") {
            this.destinationWakeup = this.payload[offset++] === 0x02;
        }
        if (hasExtendedHeader) {
            const headerPreamble = this.payload[offset++];
            const headerLength = headerPreamble >>> 4;
            const headerType = headerPreamble & 0b1111;
            const header = this.payload.subarray(offset, offset + headerLength);
            offset += headerLength;
            if (headerType === 0x00) {
                this.destinationWakeupType = header[0] & 0b0100_0000
                    ? "1000ms"
                    : header[0] & 0b0010_0000
                        ? "250ms"
                        : undefined;
            }
            else if (headerType === 0x01) {
                const repeaterRSSI = [];
                for (let i = 0; i < numRepeaters; i++) {
                    repeaterRSSI.push(parseRSSI(header, i));
                }
                this.repeaterRSSI = repeaterRSSI;
            }
        }
        this.payload = this.payload.subarray(offset);
        this.destinationNodeId = this.destinationBuffer[0];
    }
    destinationNodeId;
    direction;
    routedAck;
    routedError;
    failedHop;
    hop;
    repeaters;
    destinationWakeup;
    destinationWakeupType;
    repeaterRSSI;
    toLogEntry() {
        const { tags, message: original } = super.toLogEntry();
        tags[0] = formatRoute(this.sourceNodeId, this.repeaters, this.destinationNodeId, this.direction, this.hop, this.failedHop);
        const message = {
            ...original,
            "ack requested": this.ackRequested,
            payload: buffer2hex(this.payload),
        };
        return {
            tags,
            message,
        };
    }
}
export class MulticastZWaveMPDU extends ZWaveMPDU {
    constructor(options) {
        super(options);
        const control = this.destinationBuffer[0]; // 3 bits offset, 5 bits mask length, but this MUST be set to 29
        validatePayload.withReason("Invalid multicast control byte")(control === 29);
        this.destinationNodeIds = parseNodeBitMask(this.destinationBuffer.subarray(1));
    }
    destinationNodeIds;
    toLogEntry() {
        const { tags, message: original } = super.toLogEntry();
        tags.push("MULTICAST");
        const message = {
            destinations: this.destinationNodeIds.join(", "),
            ...original,
            payload: buffer2hex(this.payload),
        };
        return {
            tags,
            message,
        };
    }
}
export class ExplorerZWaveMPDU extends ZWaveMPDU {
    constructor(options) {
        super(options);
        this.version = this.payload[0] >>> 5;
        this.command = this.payload[0] & 0b0001_1111;
        this.stop = !!(this.payload[1] & 0b100);
        this.direction = this.payload[1] & 0b010 ? "inbound" : "outbound";
        this.sourceRouted = !!(this.payload[1] & 0b001);
        this.randomTXInterval = this.payload[2];
        this.ttl = this.payload[3] >>> 4;
        const numRepeaters = this.payload[3] & 0b1111;
        this.repeaters = [...this.payload.subarray(4, 4 + numRepeaters)];
        // Make sure the correct constructor gets called
        const Constructor = this.command === ExplorerFrameCommand.Normal
            ? NormalExplorerZWaveMPDU
            : this.command === ExplorerFrameCommand.InclusionRequest
                ? InclusionRequestExplorerZWaveMPDU
                : this.command === ExplorerFrameCommand.SearchResult
                    ? SearchResultExplorerZWaveMPDU
                    : undefined;
        if (!Constructor) {
            validatePayload.fail(`Unsupported Explorer MPDU command ${this.command}`);
        }
        else if (new.target !== Constructor
            && !staticExtends(new.target, Constructor)) {
            return new Constructor(options);
        }
        this.destinationNodeId = this.destinationBuffer[0];
        this.payload = this.payload.subarray(8);
    }
    destinationNodeId;
    version;
    command;
    stop;
    sourceRouted;
    direction;
    randomTXInterval;
    ttl;
    repeaters;
}
export class NormalExplorerZWaveMPDU extends ExplorerZWaveMPDU {
    constructor(options) {
        super(options);
    }
    toLogEntry() {
        const { tags, message: original } = super.toLogEntry();
        tags[0] = formatRoute(this.sourceNodeId, this.repeaters, this.destinationNodeId, 
        // Explorer frames do not contain a bit for the direction, we consider them all "outbound"
        "outbound", 4 - this.ttl);
        tags.unshift("EXPLORER");
        const message = {
            ...original,
            "ack requested": this.ackRequested,
            payload: buffer2hex(this.payload),
        };
        return {
            tags,
            message,
        };
    }
}
export class InclusionRequestExplorerZWaveMPDU extends ExplorerZWaveMPDU {
    constructor(options) {
        super(options);
        this.networkHomeId = this.payload.readUInt32BE(0);
        this.payload = this.payload.subarray(4);
    }
    /** The home ID of the repeating node */
    networkHomeId;
    toLogEntry() {
        const { tags, message: original } = super.toLogEntry();
        tags[0] = formatRoute(this.sourceNodeId, this.repeaters, this.destinationNodeId, 
        // Explorer frames do not contain a bit for the direction, we consider them all "outbound"
        "outbound", 4 - this.ttl);
        tags.unshift("INCL REQUEST");
        const message = {
            ...original,
            "network home ID": this.networkHomeId.toString(16).padStart(8, "0"),
            payload: buffer2hex(this.payload),
        };
        return {
            tags,
            message,
        };
    }
}
export class SearchResultExplorerZWaveMPDU extends ExplorerZWaveMPDU {
    constructor(options) {
        super(options);
        this.searchingNodeId = this.payload[0];
        this.frameHandle = this.payload[1];
        this.resultTTL = this.payload[2] >>> 4;
        const numRepeaters = this.payload[2] & 0b1111;
        this.resultRepeaters = [
            ...this.payload.subarray(3, 3 + numRepeaters),
        ];
        // This frame contains no payload
        this.payload = new Bytes();
    }
    /** The node ID that sent the explorer frame that's being answered here */
    searchingNodeId;
    /** The sequence number of the original explorer frame */
    frameHandle;
    resultTTL;
    resultRepeaters;
    toLogEntry() {
        const { tags, message: original } = super.toLogEntry();
        tags[0] = formatRoute(this.sourceNodeId, this.repeaters, this.destinationNodeId, 
        // Explorer frames do not contain a bit for the direction, we consider their responses "inbound"
        "inbound", 4 - this.ttl);
        tags.unshift("EXPLORER RESULT");
        const message = {
            ...original,
            "frame handle": this.frameHandle,
            "result TTL": this.resultTTL,
            "result repeaters": this.resultRepeaters.join(", "),
        };
        return {
            tags,
            message,
        };
    }
}
export function parseBeamFrame(frame) {
    if (frame.frameType === ZnifferFrameType.BeamStop) {
        return new BeamStop({
            data: frame.payload,
            frameInfo: frame,
        });
    }
    const channelConfig = getChannelConfiguration(frame.region);
    switch (channelConfig) {
        case "1/2":
        case "3": {
            return new ZWaveBeamStart({
                data: frame.payload,
                frameInfo: frame,
            });
        }
        case "4": {
            return new LongRangeBeamStart({
                data: frame.payload,
                frameInfo: frame,
            });
        }
        default:
            validatePayload.fail(
            // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
            `Unsupported channel configuration ${channelConfig}. MPDU payload: ${buffer2hex(frame.payload)}`);
    }
}
export class ZWaveBeamStart {
    constructor(options) {
        const data = options.data;
        this.frameInfo = options.frameInfo;
        const channelConfig = getChannelConfiguration(this.frameInfo.region);
        switch (channelConfig) {
            case "1/2":
            case "3":
                // OK
                break;
            case "4": {
                validatePayload.fail(`Channel configuration 4 (ZWLR) must be parsed as a LongRangeMPDU!`);
            }
            default: {
                validatePayload.fail(
                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
                `Unsupported channel configuration ${channelConfig}. MPDU payload: ${buffer2hex(data)}`);
            }
        }
        this.destinationNodeId = data[1];
        if (data[2] === 0x01) {
            this.homeIdHash = data[3];
        }
    }
    frameInfo;
    homeIdHash;
    destinationNodeId;
    toLogEntry() {
        const tags = [
            `BEAM » ${formatNodeId(this.destinationNodeId)}`,
        ];
        const message = {
            channel: this.frameInfo.channel,
            "protocol/data rate": znifferProtocolDataRateToString(this.frameInfo.protocolDataRate),
            RSSI: this.frameInfo.rssi != undefined
                ? rssiToString(this.frameInfo.rssi)
                : this.frameInfo.rssiRaw.toString(),
        };
        return {
            tags,
            message,
        };
    }
}
export class LongRangeBeamStart {
    constructor(options) {
        const data = options.data;
        this.frameInfo = options.frameInfo;
        const channelConfig = getChannelConfiguration(this.frameInfo.region);
        switch (channelConfig) {
            case "1/2":
            case "3":
                // OK
                break;
            case "4": {
                validatePayload.fail(`Channel configuration 4 (ZWLR) must be parsed as a LongRangeMPDU!`);
            }
            default: {
                validatePayload.fail(
                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
                `Unsupported channel configuration ${channelConfig}. MPDU payload: ${buffer2hex(data)}`);
            }
        }
        const txPower = data[1] >>> 4;
        this.txPower = longRangeBeamPowerToDBm(txPower);
        this.destinationNodeId = data.readUInt16BE(1) & 0x0fff;
        this.homeIdHash = data[3];
    }
    frameInfo;
    homeIdHash;
    destinationNodeId;
    txPower;
    toLogEntry() {
        const tags = [
            `BEAM » ${formatNodeId(this.destinationNodeId)}`,
        ];
        const message = {
            channel: this.frameInfo.channel,
            "protocol/data rate": znifferProtocolDataRateToString(this.frameInfo.protocolDataRate),
            "TX power": `${this.txPower} dBm`,
            RSSI: this.frameInfo.rssi != undefined
                ? rssiToString(this.frameInfo.rssi)
                : this.frameInfo.rssiRaw.toString(),
        };
        return {
            tags,
            message,
        };
    }
}
export class BeamStop {
    constructor(options) {
        this.frameInfo = options.frameInfo;
    }
    frameInfo;
    toLogEntry() {
        const tags = [
            "BEAM STOP",
        ];
        const message = {
            channel: this.frameInfo.channel,
        };
        return {
            tags,
            message,
        };
    }
}
export function mpduToFrame(mpdu, payloadCC) {
    if (mpdu instanceof ZWaveMPDU) {
        return mpduToZWaveFrame(mpdu, payloadCC);
    }
    else if (mpdu instanceof LongRangeMPDU) {
        return mpduToLongRangeFrame(mpdu, payloadCC);
    }
    throw new ZWaveError(`mpduToFrame not supported for ${mpdu.constructor.name}`, ZWaveErrorCodes.Argument_Invalid);
}
export function mpduToZWaveFrame(mpdu, payloadCC) {
    const retBase = {
        protocol: Protocols.ZWave,
        channel: mpdu.frameInfo.channel,
        region: mpdu.frameInfo.region,
        rssiRaw: mpdu.frameInfo.rssiRaw,
        rssi: mpdu.frameInfo.rssi,
        protocolDataRate: mpdu.frameInfo.protocolDataRate,
        speedModified: mpdu.speedModified,
        sequenceNumber: mpdu.sequenceNumber,
        homeId: mpdu.homeId,
        sourceNodeId: mpdu.sourceNodeId,
    };
    if (mpdu instanceof SinglecastZWaveMPDU) {
        const ret = {
            ...retBase,
            ackRequested: mpdu.ackRequested,
            payload: payloadCC ?? mpdu.payload,
        };
        if (mpdu.destinationNodeId === NODE_ID_BROADCAST) {
            return {
                type: ZWaveFrameType.Broadcast,
                destinationNodeId: mpdu.destinationNodeId,
                ...ret,
            };
        }
        else {
            return {
                type: ZWaveFrameType.Singlecast,
                destinationNodeId: mpdu.destinationNodeId,
                ...ret,
            };
        }
    }
    else if (mpdu instanceof AckZWaveMPDU) {
        return {
            type: ZWaveFrameType.AckDirect,
            ...retBase,
            destinationNodeId: mpdu.destinationNodeId,
        };
    }
    else if (mpdu instanceof MulticastZWaveMPDU) {
        return {
            type: ZWaveFrameType.Multicast,
            ...retBase,
            destinationNodeIds: [...mpdu.destinationNodeIds],
            payload: payloadCC ?? mpdu.payload,
        };
    }
    else if (mpdu instanceof RoutedZWaveMPDU) {
        return {
            type: ZWaveFrameType.Singlecast,
            ...retBase,
            destinationNodeId: mpdu.destinationNodeId,
            ackRequested: mpdu.ackRequested,
            payload: payloadCC ?? mpdu.payload,
            direction: mpdu.direction,
            hop: mpdu.hop,
            repeaters: [...mpdu.repeaters],
            repeaterRSSI: mpdu.repeaterRSSI && [...mpdu.repeaterRSSI],
            routedAck: mpdu.routedAck,
            routedError: mpdu.routedError,
            failedHop: mpdu.failedHop,
        };
    }
    else if (mpdu instanceof ExplorerZWaveMPDU) {
        const explorerBase = {
            ...retBase,
            destinationNodeId: mpdu.destinationNodeId,
            ackRequested: mpdu.ackRequested,
            direction: mpdu.direction,
            repeaters: [...mpdu.repeaters],
            ttl: mpdu.ttl,
        };
        if (mpdu instanceof NormalExplorerZWaveMPDU) {
            return {
                type: ZWaveFrameType.ExplorerNormal,
                payload: payloadCC ?? mpdu.payload,
                ...explorerBase,
            };
        }
        else if (mpdu instanceof SearchResultExplorerZWaveMPDU) {
            return {
                type: ZWaveFrameType.ExplorerSearchResult,
                ...explorerBase,
                searchingNodeId: mpdu.searchingNodeId,
                frameHandle: mpdu.frameHandle,
                resultTTL: mpdu.resultTTL,
                resultRepeaters: [...mpdu.resultRepeaters],
            };
        }
        else if (mpdu instanceof InclusionRequestExplorerZWaveMPDU) {
            return {
                type: ZWaveFrameType.ExplorerInclusionRequest,
                payload: payloadCC ?? mpdu.payload,
                ...explorerBase,
                networkHomeId: mpdu.networkHomeId,
            };
        }
    }
    throw new ZWaveError(`mpduToZWaveFrame not supported for ${mpdu.constructor.name}`, ZWaveErrorCodes.Argument_Invalid);
}
export function mpduToLongRangeFrame(mpdu, payloadCC) {
    const retBase = {
        protocol: Protocols.ZWaveLongRange,
        channel: mpdu.frameInfo.channel,
        region: mpdu.frameInfo.region,
        protocolDataRate: mpdu.frameInfo.protocolDataRate,
        rssiRaw: mpdu.frameInfo.rssiRaw,
        rssi: mpdu.frameInfo.rssi,
        noiseFloor: mpdu.noiseFloor,
        txPower: mpdu.txPower,
        sequenceNumber: mpdu.sequenceNumber,
        homeId: mpdu.homeId,
        sourceNodeId: mpdu.sourceNodeId,
        destinationNodeId: mpdu.destinationNodeId,
    };
    if (mpdu instanceof SinglecastLongRangeMPDU) {
        const ret = {
            ...retBase,
            ackRequested: mpdu.ackRequested,
            payload: payloadCC ?? mpdu.payload,
        };
        if (mpdu.destinationNodeId === NODE_ID_BROADCAST_LR) {
            return {
                type: LongRangeFrameType.Broadcast,
                ...ret,
                destinationNodeId: mpdu.destinationNodeId, // Make TS happy
            };
        }
        else {
            return {
                type: LongRangeFrameType.Singlecast,
                ...ret,
            };
        }
    }
    else if (mpdu instanceof AckLongRangeMPDU) {
        return {
            type: LongRangeFrameType.Ack,
            ...retBase,
            incomingRSSI: mpdu.incomingRSSI,
            payload: mpdu.payload,
        };
    }
    throw new ZWaveError(`mpduToLongRangeFrame not supported for ${mpdu.constructor.name}`, ZWaveErrorCodes.Argument_Invalid);
}
export function beamToFrame(beam) {
    const retBase = {
        channel: beam.frameInfo.channel,
        region: beam.frameInfo.region,
        rssiRaw: beam.frameInfo.rssiRaw,
        rssi: beam.frameInfo.rssi,
        protocolDataRate: beam.frameInfo.protocolDataRate,
    };
    if (beam instanceof ZWaveBeamStart) {
        return {
            protocol: Protocols.ZWave,
            type: ZWaveFrameType.BeamStart,
            ...retBase,
            destinationNodeId: beam.destinationNodeId,
            homeIdHash: beam.homeIdHash,
        };
    }
    else if (beam instanceof LongRangeBeamStart) {
        return {
            protocol: Protocols.ZWaveLongRange,
            type: LongRangeFrameType.BeamStart,
            ...retBase,
            destinationNodeId: beam.destinationNodeId,
            homeIdHash: beam.homeIdHash,
            txPower: beam.txPower,
        };
    }
    else {
        // Beam Stop - contains only the channel, the other fields are garbage
        const isLR = beam.frameInfo.channel === 4;
        if (isLR) {
            return {
                protocol: Protocols.ZWaveLongRange,
                type: LongRangeFrameType.BeamStop,
                channel: beam.frameInfo.channel,
            };
        }
        else {
            return {
                protocol: Protocols.ZWave,
                type: ZWaveFrameType.BeamStop,
                channel: beam.frameInfo.channel,
            };
        }
    }
}
export function znifferDataMessageToCorruptedFrame(msg, rssi) {
    if (msg.checksumOK) {
        throw new ZWaveError(`znifferDataMessageToCorruptedFrame expects the checksum to be incorrect`, ZWaveErrorCodes.Argument_Invalid);
    }
    return {
        channel: msg.channel,
        region: msg.region,
        rssiRaw: msg.rssiRaw,
        rssi,
        protocolDataRate: msg.protocolDataRate,
        payload: msg.payload,
    };
}
//# sourceMappingURL=MPDU.js.map