import { __esDecorate, __runInitializers } from "tslib";
import { CommandClasses, MessagePriority, encodeFloatWithScale, parseFloatWithScale, timespan, validatePayload, } from "@zwave-js/core";
import { Bytes, getEnumMemberName, pick, } from "@zwave-js/shared";
import { CCAPI, POLL_VALUE, PhysicalCCAPI, throwUnsupportedProperty, } from "../lib/API.js";
import { CommandClass, } from "../lib/CommandClass.js";
import { API, CCCommand, ccValueProperty, ccValues, commandClass, expectedCCResponse, implementedVersion, } from "../lib/CommandClassDecorators.js";
import { BatteryChargingStatus, BatteryCommand, BatteryReplacementStatus, } from "../lib/_Types.js";
import { NotificationCCValues } from "./NotificationCC.js";
import { BatteryCCValues } from "./_CCValues.generated.js";
export { BatteryCCValues };
// @noSetValueAPI This CC is read-only
let BatteryCCAPI = (() => {
    let _classDecorators = [API(CommandClasses.Battery)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = PhysicalCCAPI;
    var BatteryCCAPI = 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);
            BatteryCCAPI = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
        supportsCommand(cmd) {
            switch (cmd) {
                case BatteryCommand.Get:
                    return true; // This is mandatory
                case BatteryCommand.HealthGet:
                    return this.version >= 2;
            }
            return super.supportsCommand(cmd);
        }
        get [POLL_VALUE]() {
            return async function ({ property }) {
                switch (property) {
                    case "level":
                    case "chargingStatus":
                    case "rechargeable":
                    case "backup":
                    case "overheating":
                    case "lowFluid":
                    case "rechargeOrReplace":
                    case "lowTemperatureStatus":
                    case "disconnected":
                        return (await this.get())?.[property];
                    case "maximumCapacity":
                    case "temperature":
                        return (await this.getHealth())?.[property];
                    default:
                        throwUnsupportedProperty(this.ccId, property);
                }
            };
        }
        // oxlint-disable-next-line typescript/explicit-module-boundary-types
        async get() {
            this.assertSupportsCommand(BatteryCommand, BatteryCommand.Get);
            const cc = new BatteryCCGet({
                nodeId: this.endpoint.nodeId,
                endpointIndex: this.endpoint.index,
            });
            const response = await this.host.sendCommand(cc, this.commandOptions);
            if (response) {
                return pick(response, [
                    "level",
                    "chargingStatus",
                    "rechargeable",
                    "backup",
                    "overheating",
                    "lowFluid",
                    "rechargeOrReplace",
                    "lowTemperatureStatus",
                    "disconnected",
                ]);
            }
        }
        // oxlint-disable-next-line typescript/explicit-module-boundary-types
        async getHealth() {
            this.assertSupportsCommand(BatteryCommand, BatteryCommand.HealthGet);
            const cc = new BatteryCCHealthGet({
                nodeId: this.endpoint.nodeId,
                endpointIndex: this.endpoint.index,
            });
            const response = await this.host.sendCommand(cc, this.commandOptions);
            if (response) {
                return pick(response, ["maximumCapacity", "temperature"]);
            }
        }
    };
    return BatteryCCAPI = _classThis;
})();
export { BatteryCCAPI };
let BatteryCC = (() => {
    let _classDecorators = [commandClass(CommandClasses.Battery), implementedVersion(3), ccValues(BatteryCCValues)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = CommandClass;
    var BatteryCC = 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);
            BatteryCC = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
        async interview(ctx) {
            const node = this.getNode(ctx);
            ctx.logNode(node.id, {
                endpoint: this.endpointIndex,
                message: `Interviewing ${this.ccName}...`,
                direction: "none",
            });
            // Query the Battery status
            await this.refreshValues(ctx);
            // Remember that the interview is complete
            this.setInterviewComplete(ctx, true);
        }
        async refreshValues(ctx, options) {
            const node = this.getNode(ctx);
            const endpoint = this.getEndpoint(ctx);
            const api = CCAPI.create(CommandClasses.Battery, ctx, endpoint).withOptions({
                priority: options?.priority ?? MessagePriority.NodeQuery,
            });
            ctx.logNode(node.id, {
                endpoint: this.endpointIndex,
                message: "querying battery status...",
                direction: "outbound",
            });
            const batteryStatus = await api.get();
            if (batteryStatus) {
                let logMessage = "received response for battery information:";
                if (batteryStatus.disconnected) {
                    logMessage += `
is disconnected:                 true`;
                }
                else {
                    logMessage += `
level:                           ${batteryStatus.level === 0xff
                        ? "low"
                        : (batteryStatus.level + " %")}`;
                }
                if (api.version >= 2) {
                    if (!batteryStatus.disconnected) {
                        logMessage += `
status:                          ${BatteryChargingStatus[batteryStatus.chargingStatus]}`;
                    }
                    logMessage += `
rechargeable:                    ${batteryStatus.rechargeable}
is backup:                       ${batteryStatus.backup}
is overheating:                  ${batteryStatus.overheating}
fluid is low:                    ${batteryStatus.lowFluid}
needs to be replaced or charged: ${BatteryReplacementStatus[batteryStatus.rechargeOrReplace]}
is low temperature               ${batteryStatus.lowTemperatureStatus}`;
                }
                ctx.logNode(node.id, {
                    endpoint: this.endpointIndex,
                    message: logMessage,
                    direction: "inbound",
                });
            }
            if (api.version >= 2) {
                // always query the health
                ctx.logNode(node.id, {
                    endpoint: this.endpointIndex,
                    message: "querying battery health...",
                    direction: "outbound",
                });
                const batteryHealth = await api.getHealth();
                if (batteryHealth) {
                    const logMessage = `received response for battery health:
max. capacity: ${batteryHealth.maximumCapacity} %
temperature:   ${batteryHealth.temperature} °C`;
                    ctx.logNode(node.id, {
                        endpoint: this.endpointIndex,
                        message: logMessage,
                        direction: "inbound",
                    });
                }
            }
        }
        shouldRefreshValues(ctx) {
            // Check when the battery state was last updated
            const valueDB = ctx.tryGetValueDB(this.nodeId);
            if (!valueDB)
                return true;
            const lastUpdated = valueDB.getTimestamp(BatteryCCValues.level.endpoint(this.endpointIndex));
            return (lastUpdated == undefined
                // The specs say once per month, but that's a bit too unfrequent IMO
                // Also the maximum that setInterval supports is ~24.85 days
                || Date.now() - lastUpdated > timespan.days(7));
        }
    };
    return BatteryCC = _classThis;
})();
export { BatteryCC };
let BatteryCCReport = (() => {
    let _classDecorators = [CCCommand(BatteryCommand.Report), ccValueProperty("level", BatteryCCValues.level), ccValueProperty("chargingStatus", BatteryCCValues.chargingStatus), ccValueProperty("rechargeable", BatteryCCValues.rechargeable), ccValueProperty("backup", BatteryCCValues.backup), ccValueProperty("overheating", BatteryCCValues.overheating), ccValueProperty("lowFluid", BatteryCCValues.lowFluid), ccValueProperty("rechargeOrReplace", BatteryCCValues.rechargeOrReplace), ccValueProperty("disconnected", BatteryCCValues.disconnected), ccValueProperty("lowTemperatureStatus", BatteryCCValues.lowTemperatureStatus)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = BatteryCC;
    var BatteryCCReport = 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);
            BatteryCCReport = _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.level = typeof options.level === "number" ? options.level : 0xff;
            this.chargingStatus = options.chargingStatus;
            this.rechargeable = options.rechargeable;
            this.backup = options.backup;
            this.overheating = options.overheating;
            this.lowFluid = options.lowFluid;
            this.rechargeOrReplace = options.rechargeOrReplace;
            this.disconnected = options.disconnected;
            this.lowTemperatureStatus = options.lowTemperatureStatus;
        }
        static from(raw, ctx) {
            let ccOptions;
            validatePayload(raw.payload.length >= 1);
            const level = raw.payload[0];
            validatePayload.withReason(`Invalid battery level ${level}`)(
            // This field MUST be in the range 0x00..0x64 or set to 0xFF
            level <= 100 || level === 0xff);
            ccOptions = {
                level,
            };
            if (raw.payload.length >= 3) {
                // Starting with V2
                const chargingStatus = raw.payload[1] >>> 6;
                const rechargeable = !!(raw.payload[1] & 32);
                const backup = !!(raw.payload[1] & 16);
                const overheating = !!(raw.payload[1] & 0b1000);
                const lowFluid = !!(raw.payload[1] & 0b0100);
                const rechargeOrReplace = !!(raw.payload[1] & 0b10)
                    ? BatteryReplacementStatus.Now
                    : !!(raw.payload[1] & 0b1)
                        ? BatteryReplacementStatus.Soon
                        : BatteryReplacementStatus.No;
                const lowTemperatureStatus = !!(raw.payload[2] & 0b10);
                const disconnected = !!(raw.payload[2] & 0b1);
                ccOptions = {
                    ...ccOptions,
                    chargingStatus,
                    rechargeable,
                    backup,
                    overheating,
                    lowFluid,
                    rechargeOrReplace,
                    lowTemperatureStatus,
                    disconnected,
                };
            }
            return new this({
                nodeId: ctx.sourceNodeId,
                ...ccOptions,
            });
        }
        persistValues(ctx) {
            // Setting the values to undefined is a bit hacky, but we need to avoid persisting
            // them in these special cases, and I don't like copy-pasting what persistValues does.
            if (this.disconnected) {
                // Level and chargingStatus are meaningless when disconnected.
                // Suppress persisting them, then remove any previously stored values.
                const savedLevel = this.level;
                const savedChargingStatus = this.chargingStatus;
                // @ts-expect-error
                this.level = undefined;
                // @ts-expect-error
                this.chargingStatus = undefined;
                const ret = super.persistValues(ctx);
                // Add the values back to the CC instance, so they can be evaluated downstream if needed
                // @ts-expect-error
                this.level = savedLevel;
                // @ts-expect-error
                this.chargingStatus = savedChargingStatus;
                this.removeValue(ctx, BatteryCCValues.level);
                this.removeValue(ctx, BatteryCCValues.chargingStatus);
                return ret;
            }
            else if (this.level === 0xff) {
                // 0xff is a "low battery" notification, not an actual level
                // @ts-expect-error
                this.level = undefined;
                const ret = super.persistValues(ctx);
                // Add the value back to the CC instance, so it can be evaluated downstream if needed
                // @ts-expect-error
                this.level = 0xff;
                return ret;
            }
            // Default behavior for all other cases
            if (!super.persistValues(ctx))
                return false;
            // Naïve heuristic for a full battery
            if (this.level >= 90) {
                // Some devices send Notification CC Reports with battery information,
                // or this information is mapped from legacy V1 alarm values.
                // We may need to idle the corresponding values when the battery is full
                const notificationCCVersion = ctx.getSupportedCCVersion(CommandClasses.Notification, this.nodeId, this.endpointIndex);
                if (
                // supported
                notificationCCVersion > 0
                    // but idling is not required
                    && notificationCCVersion < 8) {
                    const batteryLevelStatusValue = NotificationCCValues
                        .notificationVariable("Power Management", "Battery level status");
                    // If not undefined and not idle
                    if (this.getValue(ctx, batteryLevelStatusValue)) {
                        this.setValue(ctx, batteryLevelStatusValue, 0);
                    }
                }
            }
            return true;
        }
        level;
        chargingStatus;
        rechargeable;
        backup;
        overheating;
        lowFluid;
        rechargeOrReplace;
        disconnected;
        lowTemperatureStatus;
        serialize(ctx) {
            this.payload = Bytes.from([
                this.disconnected
                    // CC:0080.02.03.11.010
                    // If this field is set to 1, the Battery Level field MUST
                    // be set to 0x00.
                    ? 0
                    : this.level,
            ]);
            if (this.chargingStatus != undefined) {
                this.payload = Bytes.concat([
                    this.payload,
                    [
                        (this.chargingStatus << 6)
                            + (this.rechargeable ? 32 : 0)
                            + (this.backup ? 16 : 0)
                            + (this.overheating ? 0b1000 : 0)
                            + (this.lowFluid ? 0b0100 : 0)
                            + (this.rechargeOrReplace === BatteryReplacementStatus.Now
                                ? 0b10
                                : this.rechargeOrReplace
                                    === BatteryReplacementStatus.Soon
                                    ? 0b1
                                    : 0),
                        (this.lowTemperatureStatus ? 0b10 : 0)
                            + (this.disconnected ? 0b1 : 0),
                    ],
                ]);
            }
            return super.serialize(ctx);
        }
        toLogEntry(ctx) {
            const message = {};
            if (this.disconnected) {
                message.disconnected = true;
            }
            else if (this.level === 0xff) {
                message["is low"] = true;
            }
            else {
                message.level = this.level;
            }
            if (!this.disconnected && this.chargingStatus != undefined) {
                message["charging status"] = getEnumMemberName(BatteryChargingStatus, this.chargingStatus);
            }
            if (this.rechargeable != undefined) {
                message.rechargeable = this.rechargeable;
            }
            if (this.backup != undefined) {
                message.backup = this.backup;
            }
            if (this.overheating != undefined) {
                message.overheating = this.overheating;
            }
            if (this.lowFluid != undefined) {
                message["low fluid"] = this.lowFluid;
            }
            if (this.rechargeOrReplace != undefined) {
                message["recharge or replace"] = getEnumMemberName(BatteryReplacementStatus, this.rechargeOrReplace);
            }
            if (this.lowTemperatureStatus != undefined) {
                message.lowTemperatureStatus = this.lowTemperatureStatus;
            }
            return {
                ...super.toLogEntry(ctx),
                message,
            };
        }
    };
    return BatteryCCReport = _classThis;
})();
export { BatteryCCReport };
let BatteryCCGet = (() => {
    let _classDecorators = [CCCommand(BatteryCommand.Get), expectedCCResponse(BatteryCCReport)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = BatteryCC;
    var BatteryCCGet = 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);
            BatteryCCGet = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
    };
    return BatteryCCGet = _classThis;
})();
export { BatteryCCGet };
let BatteryCCHealthReport = (() => {
    let _classDecorators = [CCCommand(BatteryCommand.HealthReport), ccValueProperty("maximumCapacity", BatteryCCValues.maximumCapacity), ccValueProperty("temperature", BatteryCCValues.temperature)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = BatteryCC;
    var BatteryCCHealthReport = 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);
            BatteryCCHealthReport = _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);
            // TODO: Check implementation:
            this.maximumCapacity = options.maximumCapacity;
            this.temperature = options.temperature;
            this.temperatureScale = options.temperatureScale;
        }
        static from(raw, ctx) {
            validatePayload(raw.payload.length >= 2);
            // Parse maximum capacity. 0xff means unknown
            let maximumCapacity = raw.payload[0];
            if (maximumCapacity === 0xff)
                maximumCapacity = undefined;
            const { value: temperature, scale: temperatureScale, } = parseFloatWithScale(raw.payload.subarray(1), true);
            return new this({
                nodeId: ctx.sourceNodeId,
                maximumCapacity,
                temperature,
                temperatureScale,
            });
        }
        maximumCapacity;
        temperature;
        temperatureScale;
        serialize(ctx) {
            const temperature = this.temperature != undefined
                ? encodeFloatWithScale(this.temperature, this.temperatureScale ?? 0x00)
                // size, precision and scale must be 0 if the temperature is omitted
                : Bytes.from([0x00]);
            this.payload = Bytes.concat([
                [this.maximumCapacity ?? 0xff],
                temperature,
            ]);
            return super.serialize(ctx);
        }
        toLogEntry(ctx) {
            return {
                ...super.toLogEntry(ctx),
                message: {
                    temperature: this.temperature != undefined
                        ? this.temperature
                        : "unknown",
                    "max capacity": this.maximumCapacity != undefined
                        ? `${this.maximumCapacity} %`
                        : "unknown",
                },
            };
        }
    };
    return BatteryCCHealthReport = _classThis;
})();
export { BatteryCCHealthReport };
let BatteryCCHealthGet = (() => {
    let _classDecorators = [CCCommand(BatteryCommand.HealthGet), expectedCCResponse(BatteryCCHealthReport)];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = BatteryCC;
    var BatteryCCHealthGet = 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);
            BatteryCCHealthGet = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
            __runInitializers(_classThis, _classExtraInitializers);
        }
    };
    return BatteryCCHealthGet = _classThis;
})();
export { BatteryCCHealthGet };
//# sourceMappingURL=BatteryCC.js.map