import { BinarySwitchCCValues, ColorComponent, ColorSwitchCCValues, ConfigurationCCValues, SetValueStatus, } from "@zwave-js/cc";
import { ConfigValueFormat, MessagePriority, getCCName, parseBitMask, } from "@zwave-js/core";
import { FunctionType, Message, MessageType } from "@zwave-js/serial";
import { Bytes } from "@zwave-js/shared";
import { roundTo } from "alcalzone-shared/math";
export const FUNC_ID_NABUCASA = FunctionType.Proprietary_F0;
export var NabuCasaCommand;
(function (NabuCasaCommand) {
    NabuCasaCommand[NabuCasaCommand["GetSupportedCommands"] = 0] = "GetSupportedCommands";
    NabuCasaCommand[NabuCasaCommand["GetLED"] = 1] = "GetLED";
    NabuCasaCommand[NabuCasaCommand["SetLED"] = 2] = "SetLED";
    NabuCasaCommand[NabuCasaCommand["ReadGyro"] = 3] = "ReadGyro";
    NabuCasaCommand[NabuCasaCommand["SetSystemIndication"] = 4] = "SetSystemIndication";
    NabuCasaCommand[NabuCasaCommand["GetConfig"] = 5] = "GetConfig";
    NabuCasaCommand[NabuCasaCommand["SetConfig"] = 6] = "SetConfig";
    NabuCasaCommand[NabuCasaCommand["GetLEDBinary"] = 7] = "GetLEDBinary";
    NabuCasaCommand[NabuCasaCommand["SetLEDBinary"] = 8] = "SetLEDBinary";
})(NabuCasaCommand || (NabuCasaCommand = {}));
export var NabuCasaIndicationSeverity;
(function (NabuCasaIndicationSeverity) {
    NabuCasaIndicationSeverity[NabuCasaIndicationSeverity["None"] = 0] = "None";
    NabuCasaIndicationSeverity[NabuCasaIndicationSeverity["Warning"] = 1] = "Warning";
    NabuCasaIndicationSeverity[NabuCasaIndicationSeverity["Error"] = 2] = "Error";
})(NabuCasaIndicationSeverity || (NabuCasaIndicationSeverity = {}));
export var NabuCasaConfigKey;
(function (NabuCasaConfigKey) {
    NabuCasaConfigKey[NabuCasaConfigKey["EnableTiltIndicator"] = 0] = "EnableTiltIndicator";
})(NabuCasaConfigKey || (NabuCasaConfigKey = {}));
const binarySwitchCurrentValue = BinarySwitchCCValues.currentValue.id;
const binarySwitchCurrentValueTranslated = {
    ...binarySwitchCurrentValue,
    commandClassName: getCCName(binarySwitchCurrentValue.commandClass),
    propertyName: "Current value",
};
const binarySwitchTargetValue = BinarySwitchCCValues.targetValue.id;
const binarySwitchTargetValueTranslated = {
    ...binarySwitchTargetValue,
    commandClassName: getCCName(binarySwitchTargetValue.commandClass),
    propertyName: "Target value",
};
const configEnableTiltIndicator = ConfigurationCCValues.paramInformation(NabuCasaConfigKey.EnableTiltIndicator).id;
const configEnableTiltIndicatorTranslated = {
    ...configEnableTiltIndicator,
    commandClassName: getCCName(configEnableTiltIndicator.commandClass),
    propertyName: "enableTiltIndicator",
};
const configEnableTiltIndicatorMeta = {
    type: "number",
    readable: true,
    writeable: true,
    min: 0,
    max: 1,
    allowManualEntry: false,
    default: 1,
    format: ConfigValueFormat.UnsignedInteger,
    label: "Enable Tilt Indicator",
    states: {
        0: "Disable",
        1: "Enable",
    },
    valueSize: 1,
};
const WHITE = { r: 255, g: 255, b: 255 };
const BLACK = { r: 0, g: 2, b: 0 };
function parseGyro(msg) {
    // According to datasheet: 8g range => 977 µg/LSB
    const x = roundTo(msg.payload.readInt16BE(1) / 1024 * 9.77, 2);
    const y = roundTo(msg.payload.readInt16BE(3) / 1024 * 9.77, 2);
    const z = roundTo(msg.payload.readInt16BE(5) / 1024 * 9.77, 2);
    return { x, y, z };
}
export class ControllerProprietary_NabuCasa {
    constructor(driver, controller) {
        this.driver = driver;
        this.controller = controller;
    }
    driver;
    controller;
    supportedCommands;
    async interview() {
        const valueDB = this.controller.valueDB;
        const supported = await this.getSupportedCommands();
        this.supportedCommands = supported;
        if (supported.includes(NabuCasaCommand.GetLEDBinary)
            || supported.includes(NabuCasaCommand.GetLED)) {
            // Fake a binary switch for the LED
            valueDB.setMetadata(BinarySwitchCCValues.currentValue.id, BinarySwitchCCValues.currentValue.meta);
            valueDB.setMetadata(BinarySwitchCCValues.targetValue.id, BinarySwitchCCValues.targetValue.meta);
            // Clean up RGB values if they exist
            valueDB.setMetadata(ColorSwitchCCValues.currentColor.id, undefined);
            valueDB.removeValue(ColorSwitchCCValues.currentColor.id);
            valueDB.setMetadata(ColorSwitchCCValues.targetColor.id, undefined);
            valueDB.removeValue(ColorSwitchCCValues.targetColor.id);
            valueDB.setMetadata(ColorSwitchCCValues.hexColor.id, undefined);
            valueDB.removeValue(ColorSwitchCCValues.hexColor.id);
            valueDB.setMetadata(ColorSwitchCCValues
                .currentColorChannel(ColorComponent.Red).id, undefined);
            valueDB.removeValue(ColorSwitchCCValues
                .currentColorChannel(ColorComponent.Red).id);
            valueDB.setMetadata(ColorSwitchCCValues
                .currentColorChannel(ColorComponent.Green).id, undefined);
            valueDB.removeValue(ColorSwitchCCValues
                .currentColorChannel(ColorComponent.Green).id);
            valueDB.setMetadata(ColorSwitchCCValues
                .currentColorChannel(ColorComponent.Blue).id, undefined);
            valueDB.removeValue(ColorSwitchCCValues
                .currentColorChannel(ColorComponent.Blue).id);
        }
        if (supported.includes(NabuCasaCommand.GetLEDBinary)) {
            const state = await this.getLEDBinary();
            this.persistLEDState(valueDB, state);
        }
        else if (supported.includes(NabuCasaCommand.GetLED)) {
            const rgb = await this.getLED();
            this.persistRGBValue(valueDB, rgb);
        }
        if (supported.includes(NabuCasaCommand.GetConfig)) {
            const enableTiltIndicator = await this.getConfig(NabuCasaConfigKey.EnableTiltIndicator);
            valueDB.setMetadata(configEnableTiltIndicator, configEnableTiltIndicatorMeta);
            valueDB.setValue(configEnableTiltIndicator, enableTiltIndicator);
        }
    }
    persistRGBValue(valueDB, rgb) {
        // Treat any other color than black as "on"
        valueDB.setValue(BinarySwitchCCValues.currentValue.id, rgb.r > BLACK.r || rgb.g > BLACK.g || rgb.b > BLACK.b);
        valueDB.setValue(BinarySwitchCCValues.targetValue.id, rgb.r > BLACK.r || rgb.g > BLACK.g || rgb.b > BLACK.b);
    }
    persistLEDState(valueDB, state) {
        valueDB.setValue(BinarySwitchCCValues.currentValue.id, state);
        valueDB.setValue(BinarySwitchCCValues.targetValue.id, state);
    }
    async getSupportedCommands() {
        // HOST->ZW: NABU_CASA_CMD_SUPPORTED
        // ZW->HOST: NABU_CASA_CMD_SUPPORTED | supportedBitmask
        const getSupportedCmd = new Message({
            type: MessageType.Request,
            functionType: FUNC_ID_NABUCASA,
            payload: Bytes.from([NabuCasaCommand.GetSupportedCommands]),
            expectedResponse: (self, msg) => {
                return (msg.functionType === FUNC_ID_NABUCASA
                    && msg.type === MessageType.Response
                    && msg.payload[0] === NabuCasaCommand.GetSupportedCommands);
            },
        });
        const result = await this.driver.sendMessage(getSupportedCmd, {
            priority: MessagePriority.Controller,
            supportCheck: false,
        });
        const supported = result.payload.subarray(1);
        return parseBitMask(supported, NabuCasaCommand.GetSupportedCommands);
    }
    async getLED() {
        // HOST->ZW: NABU_CASA_LED_GET
        // ZW->HOST: NABU_CASA_LED_GET | r | g | b |
        const getLEDStateCmd = new Message({
            type: MessageType.Request,
            functionType: FUNC_ID_NABUCASA,
            payload: Bytes.from([NabuCasaCommand.GetLED]),
            expectedResponse: (self, msg) => {
                return (msg.functionType === FUNC_ID_NABUCASA
                    && msg.type === MessageType.Response
                    && msg.payload[0] === NabuCasaCommand.GetLED);
            },
        });
        const resultPromise = this.driver.sendMessage(getLEDStateCmd, {
            priority: MessagePriority.Controller,
            supportCheck: false,
        });
        const { payload: result } = await resultPromise;
        return {
            r: result[1],
            g: result[2],
            b: result[3],
        };
    }
    async setLED(rgb) {
        // HOST->ZW: NABU_CASA_LED_SET | r | g | b | [ effect | [ speed ]]
        // ZW->HOST: NABU_CASA_LED_SET | success
        const payload = Bytes.from([
            NabuCasaCommand.SetLED,
            rgb.r,
            rgb.g,
            rgb.b,
            1, // SOLID
        ]);
        const setLEDStateCmd = new Message({
            type: MessageType.Request,
            functionType: FUNC_ID_NABUCASA,
            payload,
            expectedResponse: (self, msg) => {
                return (msg.functionType === FUNC_ID_NABUCASA
                    && msg.type === MessageType.Response
                    && msg.payload[0] === NabuCasaCommand.SetLED);
            },
        });
        const result = await this.driver.sendMessage(setLEDStateCmd, {
            priority: MessagePriority.Controller,
            supportCheck: false,
        });
        const success = !!result.payload[1];
        return success;
    }
    async getLEDBinary() {
        // HOST->ZW: NABU_CASA_LED_GET_BINARY
        // ZW->HOST: NABU_CASA_LED_GET | state
        const getLEDStateCmd = new Message({
            type: MessageType.Request,
            functionType: FUNC_ID_NABUCASA,
            payload: Bytes.from([NabuCasaCommand.GetLEDBinary]),
            expectedResponse: (self, msg) => {
                return (msg.functionType === FUNC_ID_NABUCASA
                    && msg.type === MessageType.Response
                    && msg.payload[0] === NabuCasaCommand.GetLEDBinary);
            },
        });
        const resultPromise = this.driver.sendMessage(getLEDStateCmd, {
            priority: MessagePriority.Controller,
            supportCheck: false,
        });
        const { payload: result } = await resultPromise;
        return !!result[1]; // 0 = off, 1 = on
    }
    async setLEDBinary(state) {
        // HOST->ZW: NABU_CASA_LED_SET | state
        // ZW->HOST: NABU_CASA_LED_SET | success
        const payload = Bytes.from([
            NabuCasaCommand.SetLEDBinary,
            state ? 0x01 : 0x00,
        ]);
        const setLEDStateCmd = new Message({
            type: MessageType.Request,
            functionType: FUNC_ID_NABUCASA,
            payload,
            expectedResponse: (self, msg) => {
                return (msg.functionType === FUNC_ID_NABUCASA
                    && msg.type === MessageType.Response
                    && msg.payload[0] === NabuCasaCommand.SetLEDBinary);
            },
        });
        const result = await this.driver.sendMessage(setLEDStateCmd, {
            priority: MessagePriority.Controller,
            supportCheck: false,
        });
        const success = !!result.payload[1];
        return success;
    }
    async readGyro() {
        // HOST->ZW (REQ): NABU_CASA_GYRO_MEASURE
        // ZW->HOST (RES): NABU_CASA_GYRO_MEASURE | success
        // later
        // ZW->HOST (CB):  NABU_CASA_GYRO_MEASURE
        //           | accel_x (MSB) | accel_x (LSB)
        //           | accel_y (MSB) | accel_y (LSB)
        //           | accel_z (MSB) | accel_z (LSB)
        const readGyroCmd = new Message({
            type: MessageType.Request,
            functionType: FUNC_ID_NABUCASA,
            payload: Bytes.from([NabuCasaCommand.ReadGyro]),
            expectedResponse: (self, msg) => {
                return (msg.functionType === FUNC_ID_NABUCASA
                    && msg.type === MessageType.Response
                    && msg.payload[0] === NabuCasaCommand.ReadGyro);
            },
        });
        const callbackPromise = this.driver.waitForMessage((msg) => {
            return (msg.functionType === FUNC_ID_NABUCASA
                && msg.type === MessageType.Request
                && msg.payload[0] === NabuCasaCommand.ReadGyro);
        }, 1000);
        const response = await this.driver.sendMessage(readGyroCmd, {
            priority: MessagePriority.Controller,
            supportCheck: false,
        });
        if (!response.payload[0]) {
            return;
        }
        const callback = await callbackPromise;
        // According to datasheet: 8g range => 977 µg/LSB
        return parseGyro(callback);
    }
    async setSystemIndication(severity) {
        // HOST->ZW: NABU_CASA_SET_SYSTEM_INDICATION | severity
        // ZW->HOST: NABU_CASA_SET_SYSTEM_INDICATION | success
        const payload = Bytes.from([
            NabuCasaCommand.SetSystemIndication,
            severity,
        ]);
        const systemIndicationCmd = new Message({
            type: MessageType.Request,
            functionType: FUNC_ID_NABUCASA,
            payload,
            expectedResponse: (self, msg) => {
                return (msg.functionType === FUNC_ID_NABUCASA
                    && msg.type === MessageType.Response
                    && msg.payload[0] === NabuCasaCommand.SetSystemIndication);
            },
        });
        const result = await this.driver.sendMessage(systemIndicationCmd, {
            priority: MessagePriority.Controller,
            supportCheck: false,
        });
        const success = !!result.payload[1];
        return success;
    }
    async getConfig(key) {
        // HOST->ZW (REQ): NABU_CASA_CONFIG_GET | key
        // ZW->HOST (RES): NABU_CASA_CONFIG_GET | key | size | value...
        const payload = Bytes.from([
            NabuCasaCommand.GetConfig,
            key,
        ]);
        const getConfigCmd = new Message({
            type: MessageType.Request,
            functionType: FUNC_ID_NABUCASA,
            payload,
            expectedResponse: (self, msg) => {
                return (msg.functionType === FUNC_ID_NABUCASA
                    && msg.type === MessageType.Response
                    && msg.payload[0] === NabuCasaCommand.GetConfig
                    && msg.payload[1] === key);
            },
        });
        const resultPromise = this.driver.sendMessage(getConfigCmd, {
            priority: MessagePriority.Controller,
            supportCheck: false,
        });
        const { payload: result } = await resultPromise;
        const size = result[2];
        if (result.length > 3 + size) {
            // TODO: Decide what to do when the response is invalid
            return Number.NEGATIVE_INFINITY;
        }
        else {
            return result.readIntBE(3, size);
        }
    }
    async setConfig(key, value) {
        // HOST->ZW (REQ): NABU_CASA_CONFIG_SET | key | size | value...
        // ZW->HOST (RES): NABU_CASA_CONFIG_SET | success
        const payload = Bytes.from([
            NabuCasaCommand.SetConfig,
            key,
            // For now, only 1 byte is supported
            1,
            value,
        ]);
        const setConfigCmd = new Message({
            type: MessageType.Request,
            functionType: FUNC_ID_NABUCASA,
            payload,
            expectedResponse: (self, msg) => {
                return (msg.functionType === FUNC_ID_NABUCASA
                    && msg.type === MessageType.Response
                    && msg.payload[0] === NabuCasaCommand.SetConfig);
            },
        });
        const result = await this.driver.sendMessage(setConfigCmd, {
            priority: MessagePriority.Controller,
            supportCheck: false,
        });
        const success = !!result.payload[1];
        return success;
    }
    getDefinedValueIDs() {
        // TODO: Make dynamic
        return [
            // LED: Binary Switch
            binarySwitchCurrentValueTranslated,
            binarySwitchTargetValueTranslated,
            // Configuration
            configEnableTiltIndicatorTranslated,
        ];
    }
    async pollValue(valueId) {
        if (BinarySwitchCCValues.targetValue.is(valueId)
            || BinarySwitchCCValues.currentValue.is(valueId)) {
            if (this.supportedCommands?.includes(NabuCasaCommand.GetLEDBinary)) {
                const state = await this.getLEDBinary();
                this.persistLEDState(this.controller.valueDB, state);
                return state;
            }
            else {
                // The binary LED command is not supported, fall back to the RGB variant
                const rgb = await this.getLED();
                this.persistRGBValue(this.controller.valueDB, rgb);
                return rgb;
            }
        }
        if (ConfigurationCCValues.paramInformation.is(valueId)
            && valueId.propertyKey == undefined) {
            switch (valueId.property) {
                case NabuCasaConfigKey.EnableTiltIndicator:
                    // OK
                    break;
                default:
                    return undefined;
            }
            const value = await this.getConfig(valueId.property);
            if (value != undefined) {
                this.controller.valueDB.setValue(valueId, value);
                return value;
            }
        }
    }
    async setValue(valueId, value) {
        if (BinarySwitchCCValues.targetValue.is(valueId)
            && typeof value === "boolean") {
            if (this.supportedCommands?.includes(NabuCasaCommand.SetLEDBinary)) {
                await this.setLEDBinary(value);
                this.persistLEDState(this.controller.valueDB, value);
                return { status: SetValueStatus.Success };
            }
            else {
                // The binary LED command is not supported, fall back to the RGB variant
                const rgb = value ? WHITE : BLACK;
                await this.setLED(rgb);
                this.persistRGBValue(this.controller.valueDB, rgb);
                return { status: SetValueStatus.Success };
            }
        }
        if (ConfigurationCCValues.paramInformation.is(valueId)
            && valueId.propertyKey == undefined
            && typeof value === "number"
            && [
                NabuCasaConfigKey.EnableTiltIndicator,
            ].includes(valueId.property)) {
            await this.setConfig(valueId.property, value);
            this.controller.valueDB.setValue(valueId, value);
            return { status: SetValueStatus.Success };
        }
        return { status: SetValueStatus.NoDeviceSupport };
    }
    // eslint-disable-next-line @typescript-eslint/require-await
    async handleUnsolicited(_msg) {
        // 	if (
        // 		msg.functionType === FUNC_ID_NABUCASA
        // 		&& msg.type === MessageType.Request
        // 	) {
        // 		const command = msg.payload[0];
        // 		if (
        // 			command === NabuCasaCommand.ReadGyro && msg.payload.length >= 7
        // 		) {
        // 			const gyro = parseGyro(msg);
        // 			this.persistGyroValues(this.controller.valueDB, gyro);
        // 			return true;
        // 		}
        // 	}
        return false;
    }
}
//# sourceMappingURL=NabuCasa.js.map