import { __esDecorate, __runInitializers } from "tslib";
import { MessagePriority, NodeType, Protocols, encodeNodeID, encodeNodeUpdatePayload, isLongRangeNodeId, parseNodeID, parseNodeUpdatePayload, } from "@zwave-js/core";
import { FunctionType, Message, MessageOrigin, MessageType, expectedCallback, messageTypes, priority, } from "@zwave-js/serial";
import { Bytes, buffer2hex, getEnumMemberName, } from "@zwave-js/shared";
export var AddNodeType;
(function (AddNodeType) {
    AddNodeType[AddNodeType["Any"] = 1] = "Any";
    AddNodeType[AddNodeType["Controller"] = 2] = "Controller";
    AddNodeType[AddNodeType["Slave"] = 3] = "Slave";
    AddNodeType[AddNodeType["Existing"] = 4] = "Existing";
    AddNodeType[AddNodeType["Stop"] = 5] = "Stop";
    AddNodeType[AddNodeType["StopControllerReplication"] = 6] = "StopControllerReplication";
    AddNodeType[AddNodeType["SmartStartDSK"] = 8] = "SmartStartDSK";
    AddNodeType[AddNodeType["SmartStartListen"] = 9] = "SmartStartListen";
})(AddNodeType || (AddNodeType = {}));
export var AddNodeStatus;
(function (AddNodeStatus) {
    AddNodeStatus[AddNodeStatus["Ready"] = 1] = "Ready";
    AddNodeStatus[AddNodeStatus["NodeFound"] = 2] = "NodeFound";
    AddNodeStatus[AddNodeStatus["AddingSlave"] = 3] = "AddingSlave";
    AddNodeStatus[AddNodeStatus["AddingController"] = 4] = "AddingController";
    AddNodeStatus[AddNodeStatus["ProtocolDone"] = 5] = "ProtocolDone";
    AddNodeStatus[AddNodeStatus["Done"] = 6] = "Done";
    AddNodeStatus[AddNodeStatus["Failed"] = 7] = "Failed";
})(AddNodeStatus || (AddNodeStatus = {}));
var AddNodeFlags;
(function (AddNodeFlags) {
    AddNodeFlags[AddNodeFlags["HighPower"] = 128] = "HighPower";
    AddNodeFlags[AddNodeFlags["NetworkWide"] = 64] = "NetworkWide";
    AddNodeFlags[AddNodeFlags["ProtocolLongRange"] = 32] = "ProtocolLongRange";
})(AddNodeFlags || (AddNodeFlags = {}));
export function computeNeighborDiscoveryTimeout(host, nodeType) {
    const allNodes = host.getAllNodes().filter((n) => !isLongRangeNodeId(n.id));
    const numListeningNodes = allNodes.filter((n) => n.isListening).length;
    const numFlirsNodes = allNodes.filter((n) => n.isFrequentListening).length;
    const numNodes = allNodes.length;
    // According to the Appl-Programmers-Guide
    return (76000
        + numListeningNodes * 217
        + numFlirsNodes * 3517
        + (nodeType === NodeType.Controller ? numNodes * 732 : 0));
}
let AddNodeToNetworkRequestBase = (() => {
    let _classDecorators = [messageTypes(MessageType.Request, FunctionType.AddNodeToNetwork), priority(MessagePriority.Controller)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = Message;
    var AddNodeToNetworkRequestBase = 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);
            AddNodeToNetworkRequestBase = _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 AddNodeToNetworkRequest.from(raw, ctx);
            }
            else {
                return AddNodeToNetworkRequestStatusReport.from(raw, ctx);
            }
        }
    };
    return AddNodeToNetworkRequestBase = _classThis;
})();
export { AddNodeToNetworkRequestBase };
function testCallbackForAddNodeRequest(sent, received) {
    if (!(received instanceof AddNodeToNetworkRequestStatusReport)) {
        return false;
    }
    switch (sent.addNodeType) {
        case AddNodeType.Any:
        case AddNodeType.Controller:
        case AddNodeType.Slave:
        case AddNodeType.Existing:
            return (received.status === AddNodeStatus.Ready
                || received.status === AddNodeStatus.Failed);
        case AddNodeType.Stop:
        case AddNodeType.StopControllerReplication:
            return (received.status === AddNodeStatus.Done
                || received.status === AddNodeStatus.Failed);
        default:
            return false;
    }
}
let AddNodeToNetworkRequest = (() => {
    let _classDecorators = [expectedCallback(testCallbackForAddNodeRequest)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = AddNodeToNetworkRequestBase;
    var AddNodeToNetworkRequest = 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);
            AddNodeToNetworkRequest = _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.addNodeType = options.addNodeType;
            this.highPower = !!options.highPower;
            this.networkWide = !!options.networkWide;
        }
        static from(raw, _ctx) {
            const highPower = !!(raw.payload[0] & AddNodeFlags.HighPower);
            const networkWide = !!(raw.payload[0] & AddNodeFlags.NetworkWide);
            const addNodeType = raw.payload[0] & 0b1111;
            const callbackId = raw.payload[1];
            return new this({
                callbackId,
                addNodeType,
                highPower,
                networkWide,
            });
        }
        /** The type of node to add */
        addNodeType;
        /** Whether to use high power */
        highPower = false;
        /** Whether to include network wide */
        networkWide = false;
        serialize(ctx) {
            this.assertCallbackId();
            let data = this.addNodeType || AddNodeType.Any;
            if (this.highPower)
                data |= AddNodeFlags.HighPower;
            if (this.networkWide)
                data |= AddNodeFlags.NetworkWide;
            this.payload = Bytes.from([data, this.callbackId]);
            return super.serialize(ctx);
        }
        toLogEntry() {
            let message;
            if (this.addNodeType === AddNodeType.Stop) {
                message = { action: "Stop" };
            }
            else {
                message = {
                    "node type": getEnumMemberName(AddNodeType, this.addNodeType),
                };
            }
            message = {
                ...message,
                "high power": this.highPower,
                "network wide": this.networkWide,
            };
            if (this.hasCallbackId()) {
                message["callback id"] = this.callbackId;
            }
            return {
                ...super.toLogEntry(),
                message,
            };
        }
    };
    return AddNodeToNetworkRequest = _classThis;
})();
export { AddNodeToNetworkRequest };
export class EnableSmartStartListenRequest extends AddNodeToNetworkRequestBase {
    serialize(ctx) {
        const control = AddNodeType.SmartStartListen
            | AddNodeFlags.NetworkWide;
        // The Serial API does not send a callback, so disable waiting for one
        this.callbackId = 0;
        this.payload = Bytes.from([control, this.callbackId]);
        return super.serialize(ctx);
    }
    toLogEntry() {
        return {
            ...super.toLogEntry(),
            message: {
                action: "Enable Smart Start listening mode",
            },
        };
    }
}
function testCallbackForAddNodeDSKRequest(sent, received) {
    if (!(received instanceof AddNodeToNetworkRequestStatusReport)) {
        return false;
    }
    return (received.status === AddNodeStatus.Ready
        || received.status === AddNodeStatus.Failed);
}
let AddNodeDSKToNetworkRequest = (() => {
    let _classDecorators = [expectedCallback(testCallbackForAddNodeDSKRequest)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = AddNodeToNetworkRequestBase;
    var AddNodeDSKToNetworkRequest = 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);
            AddNodeDSKToNetworkRequest = _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.nwiHomeId = options.nwiHomeId;
            this.authHomeId = options.authHomeId;
            this.highPower = !!options.highPower;
            this.networkWide = !!options.networkWide;
            this.protocol = options.protocol ?? Protocols.ZWave;
        }
        /** The home IDs of node to add */
        nwiHomeId;
        authHomeId;
        /** Whether to use high power */
        highPower;
        /** Whether to include network wide */
        networkWide;
        /** Whether to include as long-range or not */
        protocol;
        serialize(ctx) {
            this.assertCallbackId();
            let control = AddNodeType.SmartStartDSK;
            if (this.highPower)
                control |= AddNodeFlags.HighPower;
            if (this.networkWide)
                control |= AddNodeFlags.NetworkWide;
            if (this.protocol === Protocols.ZWaveLongRange) {
                control |= AddNodeFlags.ProtocolLongRange;
            }
            this.payload = Bytes.concat([
                [control, this.callbackId],
                this.nwiHomeId,
                this.authHomeId,
            ]);
            return super.serialize(ctx);
        }
        toLogEntry() {
            const message = {
                action: "Add Smart Start node",
                "NWI Home ID": buffer2hex(this.nwiHomeId),
                "high power": this.highPower,
                "network wide": this.networkWide,
                protocol: this.protocol === Protocols.ZWaveLongRange
                    ? "Z-Wave Long Range"
                    : "Z-Wave Classic",
            };
            if (this.hasCallbackId()) {
                message["callback id"] = this.callbackId;
            }
            return {
                ...super.toLogEntry(),
                message,
            };
        }
    };
    return AddNodeDSKToNetworkRequest = _classThis;
})();
export { AddNodeDSKToNetworkRequest };
export class AddNodeToNetworkRequestStatusReport extends AddNodeToNetworkRequestBase {
    constructor(options) {
        super(options);
        this.status = options.status;
        if ("nodeId" in options) {
            this.statusContext = { nodeId: options.nodeId };
        }
        else if ("nodeInfo" in options) {
            this.statusContext = options.nodeInfo;
        }
    }
    static from(raw, ctx) {
        const callbackId = raw.payload[0];
        const status = raw.payload[1];
        switch (status) {
            case AddNodeStatus.Ready:
            case AddNodeStatus.NodeFound:
            case AddNodeStatus.ProtocolDone:
            case AddNodeStatus.Failed:
                // no context for the status to parse
                return new this({
                    callbackId,
                    status,
                });
            case AddNodeStatus.Done: {
                const { nodeId } = parseNodeID(raw.payload, ctx.nodeIdType, 2);
                return new this({
                    callbackId,
                    status,
                    nodeId,
                });
            }
            case AddNodeStatus.AddingController:
            case AddNodeStatus.AddingSlave: {
                // the payload contains a node information frame
                const nodeInfo = parseNodeUpdatePayload(raw.payload.subarray(2), ctx.nodeIdType);
                return new this({
                    callbackId,
                    status,
                    nodeInfo,
                });
            }
        }
    }
    isOK() {
        // Some of the status codes are for unsolicited callbacks, but
        // Failed is the only NOK status.
        return this.status !== AddNodeStatus.Failed;
    }
    status;
    statusContext;
    serialize(ctx) {
        this.assertCallbackId();
        this.payload = Bytes.from([this.callbackId, this.status]);
        if (this.statusContext?.basicDeviceClass != undefined) {
            this.payload = Bytes.concat([
                this.payload,
                encodeNodeUpdatePayload(this.statusContext, ctx.nodeIdType),
            ]);
        }
        else if (this.status === AddNodeStatus.Done
            && this.statusContext?.nodeId != undefined) {
            this.payload = Bytes.concat([
                this.payload,
                encodeNodeID(this.statusContext.nodeId, ctx.nodeIdType),
            ]);
        }
        return super.serialize(ctx);
    }
    toLogEntry() {
        return {
            ...super.toLogEntry(),
            message: {
                status: getEnumMemberName(AddNodeStatus, this.status),
                "callback id": this.callbackId ?? "(not set)",
            },
        };
    }
}
//# sourceMappingURL=AddNodeToNetworkRequest.js.map