import { __esDecorate, __runInitializers } from "tslib";
import { MAX_NODES, MessagePriority, TransmitOptions, TransmitStatus, ZWaveError, ZWaveErrorCodes, encodeNodeID, parseNodeID, } from "@zwave-js/core";
import { FunctionType, Message, MessageOrigin, MessageType, expectedCallback, expectedResponse, messageTypes, priority, } from "@zwave-js/serial";
import { Bytes, getEnumMemberName, num2hex, } from "@zwave-js/shared";
import { clamp } from "alcalzone-shared/math";
import { ApplicationCommandRequest } from "../application/ApplicationCommandRequest.js";
import { BridgeApplicationCommandRequest } from "../application/BridgeApplicationCommandRequest.js";
import { containsCC } from "../utils.js";
import { encodeTXReport, parseTXReport, serializableTXReportToTXReport, txReportToMessageRecord, } from "./SendDataShared.js";
export const MAX_SEND_ATTEMPTS = 5;
let SendDataRequestBase = (() => {
    let _classDecorators = [messageTypes(MessageType.Request, FunctionType.SendData), priority(MessagePriority.Normal)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = Message;
    var SendDataRequestBase = class extends _classSuper {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            SendDataRequestBase = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
        static from(raw, ctx) {
            if (ctx.origin === MessageOrigin.Host) {
                return SendDataRequest.from(raw, ctx);
            }
            else {
                return SendDataRequestTransmitReport.from(raw, ctx);
            }
        }
    };
    return SendDataRequestBase = _classThis;
})();
export { SendDataRequestBase };
let SendDataRequest = (() => {
    let _classDecorators = [expectedResponse(FunctionType.SendData), expectedCallback(FunctionType.SendData)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = SendDataRequestBase;
    var SendDataRequest = class extends _classSuper {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            SendDataRequest = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
        constructor(options) {
            super(options);
            if ("command" in options) {
                if (!options.command.isSinglecast()
                    && !options.command.isBroadcast()) {
                    throw new ZWaveError(`SendDataRequest can only be used for singlecast and broadcast CCs`, ZWaveErrorCodes.Argument_Invalid);
                }
                this.command = options.command;
                this._nodeId = this.command.nodeId;
            }
            else {
                this._nodeId = options.nodeId;
                this.serializedCC = options.serializedCC;
            }
            this.transmitOptions = options.transmitOptions
                ?? TransmitOptions.DEFAULT;
            if (options.maxSendAttempts != undefined) {
                this.maxSendAttempts = options.maxSendAttempts;
            }
        }
        static from(raw, ctx) {
            let offset = 0;
            const { nodeId, bytesRead: nodeIdBytes } = parseNodeID(raw.payload, ctx.nodeIdType, offset);
            offset += nodeIdBytes;
            const serializedCCLength = raw.payload[offset++];
            const transmitOptions = raw.payload[offset + serializedCCLength];
            const callbackId = raw.payload[offset + 1 + serializedCCLength];
            const serializedCC = raw.payload.subarray(offset, offset + serializedCCLength);
            return new this({
                transmitOptions,
                callbackId,
                nodeId,
                serializedCC,
            });
        }
        /** The command this message contains */
        command;
        /** Options regarding the transmission of the message */
        transmitOptions;
        _maxSendAttempts = 1;
        /** The number of times the driver may try to send this message */
        get maxSendAttempts() {
            return this._maxSendAttempts;
        }
        set maxSendAttempts(value) {
            this._maxSendAttempts = clamp(value, 1, MAX_SEND_ATTEMPTS);
        }
        _nodeId;
        getNodeId() {
            return this.command?.nodeId ?? this._nodeId;
        }
        serializedCC;
        async serializeCC(ctx) {
            if (!this.serializedCC) {
                if (!this.command) {
                    throw new ZWaveError(`Cannot serialize a ${this.constructor.name} without a command`, ZWaveErrorCodes.Argument_Invalid);
                }
                this.serializedCC = await this.command.serialize(ctx);
            }
            return this.serializedCC;
        }
        prepareRetransmission() {
            this.command?.prepareRetransmission();
            this.serializedCC = undefined;
            this.callbackId = undefined;
        }
        async serialize(ctx) {
            this.assertCallbackId();
            const nodeId = encodeNodeID(this.command?.nodeId ?? this._nodeId, ctx.nodeIdType);
            const serializedCC = await this.serializeCC(ctx);
            this.payload = Bytes.concat([
                nodeId,
                [serializedCC.length],
                serializedCC,
                [this.transmitOptions, this.callbackId],
            ]);
            return super.serialize(ctx);
        }
        toLogEntry() {
            return {
                ...super.toLogEntry(),
                message: {
                    "transmit options": num2hex(this.transmitOptions),
                    "callback id": this.callbackId ?? "(not set)",
                },
            };
        }
        expectsNodeUpdate(ctx) {
            return (
            // We can only answer this if the command is known
            this.command != undefined
                // Only true singlecast commands may expect a response
                && this.command.isSinglecast()
                // ... and only if the command expects a response
                && this.command.expectsCCResponse(ctx));
        }
        isExpectedNodeUpdate(ctx, msg) {
            return (
            // We can only answer this if the command is known
            this.command != undefined
                && (msg instanceof ApplicationCommandRequest
                    || msg instanceof BridgeApplicationCommandRequest)
                && containsCC(msg)
                && this.command.isExpectedCCResponse(ctx, msg.command));
        }
    };
    return SendDataRequest = _classThis;
})();
export { SendDataRequest };
export class SendDataRequestTransmitReport extends SendDataRequestBase {
    constructor(options) {
        super(options);
        this.callbackId = options.callbackId;
        this.transmitStatus = options.transmitStatus;
        this.txReport = options.txReport
            && serializableTXReportToTXReport(options.txReport);
    }
    static from(raw, _ctx) {
        const callbackId = raw.payload[0];
        const transmitStatus = raw.payload[1];
        // TODO: Consider NOT parsing this for transmit status other than OK or NoACK
        const txReport = parseTXReport(transmitStatus !== TransmitStatus.NoAck, raw.payload.subarray(2));
        return new this({
            callbackId,
            transmitStatus,
            txReport,
        });
    }
    transmitStatus;
    txReport;
    serialize(ctx) {
        this.assertCallbackId();
        this.payload = Bytes.from([
            this.callbackId,
            this.transmitStatus,
        ]);
        if (this.txReport) {
            this.payload = Bytes.concat([
                this.payload,
                encodeTXReport(this.txReport),
            ]);
        }
        return super.serialize(ctx);
    }
    isOK() {
        return this.transmitStatus === TransmitStatus.OK;
    }
    toLogEntry() {
        return {
            ...super.toLogEntry(),
            message: {
                "callback id": this.callbackId ?? "(not set)",
                "transmit status": getEnumMemberName(TransmitStatus, this.transmitStatus)
                    + (this.txReport
                        ? `, took ${this.txReport.txTicks * 10} ms`
                        : ""),
                // Show TX report fields for OK and NoAck (NoAck still provides useful routing info)
                ...(this.txReport && (this.transmitStatus === TransmitStatus.OK
                    || this.transmitStatus === TransmitStatus.NoAck)
                    ? txReportToMessageRecord(this.txReport)
                    : {}),
            },
        };
    }
}
let SendDataResponse = (() => {
    let _classDecorators = [messageTypes(MessageType.Response, FunctionType.SendData)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = Message;
    var SendDataResponse = class extends _classSuper {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            SendDataResponse = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
        constructor(options) {
            super(options);
            this.wasSent = options.wasSent;
        }
        static from(raw, _ctx) {
            const wasSent = raw.payload[0] !== 0;
            return new this({
                wasSent,
            });
        }
        wasSent;
        serialize(ctx) {
            this.payload = Bytes.from([this.wasSent ? 1 : 0]);
            return super.serialize(ctx);
        }
        isOK() {
            return this.wasSent;
        }
        toLogEntry() {
            return {
                ...super.toLogEntry(),
                message: { "was sent": this.wasSent },
            };
        }
    };
    return SendDataResponse = _classThis;
})();
export { SendDataResponse };
let SendDataMulticastRequestBase = (() => {
    let _classDecorators = [messageTypes(MessageType.Request, FunctionType.SendDataMulticast), priority(MessagePriority.Normal)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = Message;
    var SendDataMulticastRequestBase = class extends _classSuper {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            SendDataMulticastRequestBase = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
        static from(raw, ctx) {
            if (ctx.origin === MessageOrigin.Host) {
                return SendDataMulticastRequest.from(raw, ctx);
            }
            else {
                return SendDataMulticastRequestTransmitReport.from(raw, ctx);
            }
        }
    };
    return SendDataMulticastRequestBase = _classThis;
})();
export { SendDataMulticastRequestBase };
let SendDataMulticastRequest = (() => {
    let _classDecorators = [expectedResponse(FunctionType.SendDataMulticast), expectedCallback(FunctionType.SendDataMulticast)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = SendDataMulticastRequestBase;
    var SendDataMulticastRequest = class extends _classSuper {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            SendDataMulticastRequest = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
        constructor(options) {
            super(options);
            if ("command" in options) {
                if (!options.command.isMulticast()) {
                    throw new ZWaveError(`SendDataMulticastRequest can only be used for multicast CCs`, ZWaveErrorCodes.Argument_Invalid);
                }
                else if (options.command.nodeId.length === 0) {
                    throw new ZWaveError(`At least one node must be targeted`, ZWaveErrorCodes.Argument_Invalid);
                }
                else if (options.command.nodeId.some((n) => n < 1 || n > MAX_NODES)) {
                    throw new ZWaveError(`All node IDs must be between 1 and ${MAX_NODES}!`, ZWaveErrorCodes.Argument_Invalid);
                }
                this.command = options.command;
                this.nodeIds = this.command.nodeId;
            }
            else {
                this.nodeIds = options.nodeIds;
                this.serializedCC = options.serializedCC;
            }
            this.transmitOptions = options.transmitOptions
                ?? TransmitOptions.DEFAULT;
            if (options.maxSendAttempts != undefined) {
                this.maxSendAttempts = options.maxSendAttempts;
            }
        }
        static from(raw, ctx) {
            const numNodeIDs = raw.payload[0];
            let offset = 1;
            const nodeIds = [];
            for (let i = 0; i < numNodeIDs; i++) {
                const { nodeId, bytesRead } = parseNodeID(raw.payload, ctx.nodeIdType, offset);
                nodeIds.push(nodeId);
                offset += bytesRead;
            }
            const serializedCCLength = raw.payload[offset];
            offset++;
            const serializedCC = raw.payload.subarray(offset, offset + serializedCCLength);
            offset += serializedCCLength;
            const transmitOptions = raw.payload[offset];
            offset++;
            const callbackId = raw.payload[offset];
            return new this({
                transmitOptions,
                callbackId,
                nodeIds: nodeIds,
                serializedCC,
            });
        }
        /** The command this message contains */
        command;
        /** Options regarding the transmission of the message */
        transmitOptions;
        _maxSendAttempts = 1;
        /** The number of times the driver may try to send this message */
        get maxSendAttempts() {
            return this._maxSendAttempts;
        }
        set maxSendAttempts(value) {
            this._maxSendAttempts = clamp(value, 1, MAX_SEND_ATTEMPTS);
        }
        nodeIds;
        getNodeId() {
            // This is multicast, getNodeId must return undefined here
            return undefined;
        }
        serializedCC;
        async serializeCC(ctx) {
            if (!this.serializedCC) {
                if (!this.command) {
                    throw new ZWaveError(`Cannot serialize a ${this.constructor.name} without a command`, ZWaveErrorCodes.Argument_Invalid);
                }
                this.serializedCC = await this.command.serialize(ctx);
            }
            return this.serializedCC;
        }
        prepareRetransmission() {
            this.command?.prepareRetransmission();
            this.serializedCC = undefined;
            this.callbackId = undefined;
        }
        async serialize(ctx) {
            this.assertCallbackId();
            const serializedCC = await this.serializeCC(ctx);
            const destinationNodeIDs = (this.command?.nodeId ?? this.nodeIds)
                .map((id) => encodeNodeID(id, ctx.nodeIdType));
            this.payload = Bytes.concat([
                // # of target nodes, not # of bytes
                [destinationNodeIDs.length],
                ...destinationNodeIDs,
                [serializedCC.length],
                // payload
                serializedCC,
                [this.transmitOptions, this.callbackId],
            ]);
            return super.serialize(ctx);
        }
        toLogEntry() {
            return {
                ...super.toLogEntry(),
                message: {
                    "target nodes": (this.command?.nodeId ?? this.nodeIds).join(", "),
                    "transmit options": num2hex(this.transmitOptions),
                    "callback id": this.callbackId ?? "(not set)",
                },
            };
        }
    };
    return SendDataMulticastRequest = _classThis;
})();
export { SendDataMulticastRequest };
export class SendDataMulticastRequestTransmitReport extends SendDataMulticastRequestBase {
    constructor(options) {
        super(options);
        this.callbackId = options.callbackId;
        this.transmitStatus = options.transmitStatus;
    }
    static from(raw, _ctx) {
        const callbackId = raw.payload[0];
        const transmitStatus = raw.payload[1];
        return new this({
            callbackId,
            transmitStatus,
        });
    }
    transmitStatus;
    serialize(ctx) {
        this.assertCallbackId();
        this.payload = Bytes.from([this.callbackId, this.transmitStatus]);
        return super.serialize(ctx);
    }
    isOK() {
        return this.transmitStatus === TransmitStatus.OK;
    }
    toLogEntry() {
        return {
            ...super.toLogEntry(),
            message: {
                "callback id": this.callbackId ?? "(not set)",
                "transmit status": getEnumMemberName(TransmitStatus, this.transmitStatus),
            },
        };
    }
}
let SendDataMulticastResponse = (() => {
    let _classDecorators = [messageTypes(MessageType.Response, FunctionType.SendDataMulticast)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = Message;
    var SendDataMulticastResponse = class extends _classSuper {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            SendDataMulticastResponse = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
        constructor(options) {
            super(options);
            this.wasSent = options.wasSent;
        }
        static from(raw, _ctx) {
            const wasSent = raw.payload[0] !== 0;
            return new this({
                wasSent,
            });
        }
        wasSent;
        serialize(ctx) {
            this.payload = Bytes.from([this.wasSent ? 1 : 0]);
            return super.serialize(ctx);
        }
        isOK() {
            return this.wasSent;
        }
        toLogEntry() {
            return {
                ...super.toLogEntry(),
                message: { "was sent": this.wasSent },
            };
        }
    };
    return SendDataMulticastResponse = _classThis;
})();
export { SendDataMulticastResponse };
let SendDataAbort = (() => {
    let _classDecorators = [messageTypes(MessageType.Request, FunctionType.SendDataAbort), priority(MessagePriority.Controller)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = Message;
    var SendDataAbort = class extends _classSuper {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            SendDataAbort = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
    };
    return SendDataAbort = _classThis;
})();
export { SendDataAbort };
//# sourceMappingURL=SendDataMessages.js.map