"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateDefinition = generateDefinition;
exports.generateGreenPowerDefinition = generateGreenPowerDefinition;
const zigbee_herdsman_1 = require("zigbee-herdsman");
const logger_1 = require("./logger");
const m = __importStar(require("./modernExtend"));
const philips = __importStar(require("./philips"));
const utils_1 = require("./utils");
const NS = "zhc:gendef";
// Generator allows to define instances of GeneratedExtend that have typed arguments to extender.
class ExtendGenerator {
    extend;
    args;
    source;
    lib;
    constructor(args) {
        this.extend = args.extend;
        this.args = args.args;
        this.source = args.source;
        this.lib = args.lib;
    }
    getExtend() {
        return this.extend(this.args);
    }
    getSource() {
        let jsonArgs = JSON.stringify(this.args);
        if (!this.args || jsonArgs === "{}") {
            jsonArgs = "";
        }
        return `${this.source}(${jsonArgs})`;
    }
}
// If generator will have endpoint argument - generator implementation
// should not provide it if only the first device endpoint is passed in.
// If multiple endpoints provided(maybe including the first device endpoint) -
// they all should be passed as an argument, where possible, to be explicit.
const INPUT_EXTENDERS = [
    [
        ["msTemperatureMeasurement"],
        async (d, eps) => [new ExtendGenerator({ extend: m.temperature, args: maybeEndpointArgs(d, eps), source: "temperature" })],
    ],
    [["msPressureMeasurement"], async (d, eps) => [new ExtendGenerator({ extend: m.pressure, args: maybeEndpointArgs(d, eps), source: "pressure" })]],
    [["msRelativeHumidity"], async (d, eps) => [new ExtendGenerator({ extend: m.humidity, args: maybeEndpointArgs(d, eps), source: "humidity" })]],
    [["msCO2"], async (d, eps) => [new ExtendGenerator({ extend: m.co2, args: maybeEndpointArgs(d, eps), source: "co2" })]],
    [["genPowerCfg"], async (d, eps) => [new ExtendGenerator({ extend: m.battery, source: "battery" })]],
    [["genOnOff", "genLevelCtrl", "lightingColorCtrl"], extenderOnOffLight],
    [["seMetering", "haElectricalMeasurement"], extenderElectricityMeter],
    [["closuresDoorLock"], extenderLock],
    [
        ["msIlluminanceMeasurement"],
        async (d, eps) => [new ExtendGenerator({ extend: m.illuminance, args: maybeEndpointArgs(d, eps), source: "illuminance" })],
    ],
    [["msOccupancySensing"], async (d, eps) => [new ExtendGenerator({ extend: m.occupancy, source: "occupancy" })]],
    [
        ["ssIasZone"],
        async (d, eps) => [
            new ExtendGenerator({
                extend: m.iasZoneAlarm,
                args: {
                    zoneType: "generic",
                    zoneAttributes: ["alarm_1", "alarm_2", "tamper", "battery_low"],
                },
                source: "iasZoneAlarm",
            }),
        ],
    ],
    [["ssIasWd"], async (d, eps) => [new ExtendGenerator({ extend: m.iasWarning, source: "iasWarning" })]],
    [
        ["genDeviceTempCfg"],
        async (d, eps) => [new ExtendGenerator({ extend: m.deviceTemperature, args: maybeEndpointArgs(d, eps), source: "deviceTemperature" })],
    ],
    [["pm25Measurement"], async (d, eps) => [new ExtendGenerator({ extend: m.pm25, args: maybeEndpointArgs(d, eps), source: "pm25" })]],
    [["msFlowMeasurement"], async (d, eps) => [new ExtendGenerator({ extend: m.flow, args: maybeEndpointArgs(d, eps), source: "flow" })]],
    [["msSoilMoisture"], async (d, eps) => [new ExtendGenerator({ extend: m.soilMoisture, args: maybeEndpointArgs(d, eps), source: "soilMoisture" })]],
    [
        ["closuresWindowCovering"],
        async (d, eps) => [new ExtendGenerator({ extend: m.windowCovering, args: { controls: ["lift", "tilt"] }, source: "windowCovering" })],
    ],
    [["genBinaryInput"], extenderBinaryInput],
    [["genBinaryOutput"], extenderBinaryOutput],
];
const OUTPUT_EXTENDERS = [
    [["genOnOff"], async (d, eps) => [new ExtendGenerator({ extend: m.commandsOnOff, args: maybeEndpointArgs(d, eps), source: "commandsOnOff" })]],
    [
        ["genLevelCtrl"],
        async (d, eps) => [new ExtendGenerator({ extend: m.commandsLevelCtrl, args: maybeEndpointArgs(d, eps), source: "commandsLevelCtrl" })],
    ],
    [
        ["lightingColorCtrl"],
        async (d, eps) => [new ExtendGenerator({ extend: m.commandsColorCtrl, args: maybeEndpointArgs(d, eps), source: "commandsColorCtrl" })],
    ],
    [
        ["closuresWindowCovering"],
        async (d, eps) => [
            new ExtendGenerator({ extend: m.commandsWindowCovering, args: maybeEndpointArgs(d, eps), source: "commandsWindowCovering" }),
        ],
    ],
];
function generateSource(definition, generatedExtend) {
    const imports = [...new Set(generatedExtend.map((e) => e.lib ?? "modernExtend"))];
    const importsStr = imports.map((e) => `import * as ${e === "modernExtend" ? "m" : e} from 'zigbee-herdsman-converters/lib/${e}';`).join("\n");
    return `${importsStr}

export default {
    zigbeeModel: ['${definition.zigbeeModel}'],
    model: '${definition.model}',
    vendor: '${definition.vendor}',
    description: 'Automatically generated definition',
    extend: [${generatedExtend.map((e) => `${e.lib ?? "m"}.${e.getSource()}`).join(", ")}],
    meta: ${JSON.stringify(definition.meta || {})},
};
`;
}
function generateGreenPowerSource(definition, ieeeAddr) {
    return `import {genericGreenPower} from 'zigbee-herdsman-converters/lib/modernExtend';

export default {
    fingerprint: [{modelID: '${definition.model}', ieeeAddr: new RegExp('^${ieeeAddr}$')}],
    model: '${definition.model}',
    vendor: '${definition.vendor}',
    description: 'Automatically generated definition for Green Power',
    extend: [genericGreenPower()],
};`;
}
async function generateDefinition(device) {
    if (device.type === "GreenPower") {
        return generateGreenPowerDefinition(device);
    }
    // Map cluster to all endpoints that have this cluster.
    const mapClusters = (endpoint, clusters, clusterMap) => {
        for (const cluster of clusters) {
            if (!clusterMap.has(cluster.name)) {
                clusterMap.set(cluster.name, []);
            }
            const endpointsWithCluster = clusterMap.get(cluster.name);
            endpointsWithCluster.push(endpoint);
        }
    };
    const knownInputClusters = INPUT_EXTENDERS.flatMap((ext) => ext[0]);
    const knownOutputClusters = OUTPUT_EXTENDERS.flatMap((ext) => ext[0]);
    const inputClusterMap = new Map();
    const outputClusterMap = new Map();
    for (const endpoint of device.endpoints) {
        // Filter clusters to leave only the ones that we can generate extenders for.
        const inputClusters = endpoint.getInputClusters().filter((c) => knownInputClusters.find((known) => known === c.name));
        const outputClusters = endpoint.getOutputClusters().filter((c) => knownOutputClusters.find((known) => known === c.name));
        mapClusters(endpoint, inputClusters, inputClusterMap);
        mapClusters(endpoint, outputClusters, outputClusterMap);
    }
    // Generate extenders
    const usedExtenders = [];
    const generatedExtend = [];
    const addGenerators = async (clusterName, endpoints, extenders) => {
        const extender = extenders.find((e) => e[0].includes(clusterName));
        if (!extender || usedExtenders.includes(extender)) {
            return;
        }
        usedExtenders.push(extender);
        generatedExtend.push(...(await extender[1](device, endpoints)));
    };
    for (const [cluster, endpoints] of inputClusterMap) {
        await addGenerators(cluster, endpoints, INPUT_EXTENDERS);
    }
    for (const [cluster, endpoints] of outputClusterMap) {
        await addGenerators(cluster, endpoints, OUTPUT_EXTENDERS);
    }
    const extenders = generatedExtend.map((e) => e.getExtend());
    // Generated definition below will provide this.
    for (const extender of extenders) {
        extender.endpoint = undefined;
    }
    // Currently multiEndpoint is enabled if device has more then 1 endpoint.
    // It is possible to better check if device should be considered multiEndpoint
    // based, for example, on generator arguments(i.e. presence of "endpointNames"),
    // but this will be enough for now.
    const endpointsWithoutGreenPower = device.endpoints.filter((e) => e.ID !== 242);
    const multiEndpoint = endpointsWithoutGreenPower.length > 1;
    if (multiEndpoint) {
        const endpoints = {};
        for (const endpoint of endpointsWithoutGreenPower) {
            endpoints[endpoint.ID.toString()] = endpoint.ID;
        }
        // Add to beginning for better visibility.
        generatedExtend.unshift(new ExtendGenerator({ extend: m.deviceEndpoints, args: { endpoints }, source: "deviceEndpoints" }));
        extenders.unshift(generatedExtend[0].getExtend());
    }
    const definition = {
        zigbeeModel: [device.modelID],
        model: device.modelID ?? "",
        vendor: device.manufacturerName ?? "",
        description: "Automatically generated definition",
        extend: extenders,
        generated: true,
    };
    if (multiEndpoint) {
        definition.meta = { multiEndpoint };
    }
    const externalDefinitionSource = generateSource(definition, generatedExtend);
    return { externalDefinitionSource, definition };
}
function generateGreenPowerDefinition(device) {
    const definition = {
        fingerprint: [{ modelID: device.modelID, ieeeAddr: new RegExp(`^${device.ieeeAddr}$`) }],
        model: device.modelID ?? "",
        vendor: device.manufacturerName ?? "",
        description: "Automatically generated definition for Green Power",
        extend: [m.genericGreenPower()],
        generated: true,
    };
    const externalDefinitionSource = generateGreenPowerSource(definition, device.ieeeAddr);
    return { externalDefinitionSource, definition };
}
function stringifyEps(endpoints) {
    return endpoints.map((e) => e.ID.toString());
}
// This function checks if provided array of endpoints contain
// only first device endpoint, which is passed in as `firstEndpoint`.
function onlyFirstDeviceEnpoint(device, endpoints) {
    return endpoints.length === 1 && endpoints[0].ID === device.endpoints[0].ID;
}
// maybeEndpoints returns either `toExtend` if only first device endpoint is provided
// as `endpoints`, or `endpointNames` with `toExtend`.
// This allows to drop unnecessary `endpointNames` argument if it is not needed.
function maybeEndpointArgs(device, endpoints, toExtend) {
    if (onlyFirstDeviceEnpoint(device, endpoints)) {
        return toExtend;
    }
    return { endpointNames: stringifyEps(endpoints), ...toExtend };
}
async function extenderLock(device, endpoints) {
    // TODO: Support multiple endpoints
    if (endpoints.length > 1) {
        logger_1.logger.warning("extenderLock can accept only one endpoint", NS);
    }
    const endpoint = endpoints[0];
    const pinCodeCount = await (0, utils_1.getClusterAttributeValue)(endpoint, "closuresDoorLock", "numOfPinUsersSupported", 50);
    return [new ExtendGenerator({ extend: m.lock, args: { pinCodeCount }, source: "lock" })];
}
async function extenderOnOffLight(device, endpoints) {
    const generated = [];
    const lightEndpoints = endpoints.filter((e) => e.supportsInputCluster("lightingColorCtrl") || e.supportsInputCluster("genLevelCtrl"));
    const onOffEndpoints = endpoints.filter((e) => lightEndpoints.findIndex((ep) => e.ID === ep.ID) === -1);
    if (onOffEndpoints.length !== 0) {
        let endpointNames = undefined;
        if (!onlyFirstDeviceEnpoint(device, endpoints)) {
            endpointNames = endpoints.map((e) => e.ID.toString());
        }
        generated.push(new ExtendGenerator({ extend: m.onOff, args: { powerOnBehavior: false, endpointNames }, source: "onOff" }));
    }
    for (const endpoint of lightEndpoints) {
        // In case read fails, support all features with 31
        let colorCapabilities = 0;
        if (endpoint.supportsInputCluster("lightingColorCtrl")) {
            colorCapabilities = await (0, utils_1.getClusterAttributeValue)(endpoint, "lightingColorCtrl", "colorCapabilities", 31);
        }
        const supportsHueSaturation = (colorCapabilities & (1 << 0)) > 0;
        const supportsEnhancedHueSaturation = (colorCapabilities & (1 << 1)) > 0;
        const supportsColorXY = (colorCapabilities & (1 << 3)) > 0;
        const supportsColorTemperature = (colorCapabilities & (1 << 4)) > 0;
        const args = {};
        if (supportsColorTemperature) {
            const minColorTemp = await (0, utils_1.getClusterAttributeValue)(endpoint, "lightingColorCtrl", "colorTempPhysicalMin", 150);
            const maxColorTemp = await (0, utils_1.getClusterAttributeValue)(endpoint, "lightingColorCtrl", "colorTempPhysicalMax", 500);
            args.colorTemp = { range: [minColorTemp, maxColorTemp] };
        }
        if (supportsColorXY) {
            args.color = true;
            if (supportsHueSaturation || supportsEnhancedHueSaturation) {
                args.color = {};
                if (supportsHueSaturation)
                    args.color.modes = ["xy", "hs"];
                if (supportsEnhancedHueSaturation)
                    args.color.enhancedHue = true;
            }
        }
        if (endpoint.getDevice().manufacturerID === zigbee_herdsman_1.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V) {
            generated.push(new ExtendGenerator({ extend: philips.m.light, args, source: "m.light", lib: "philips" }));
        }
        else {
            generated.push(new ExtendGenerator({ extend: m.light, args, source: "light" }));
        }
    }
    return generated;
}
function extenderElectricityMeter(device, endpoints) {
    // TODO: Support multiple endpoints
    if (endpoints.length > 1) {
        logger_1.logger.warning("extenderElectricityMeter can accept only one endpoint", NS);
    }
    const endpoint = endpoints[0];
    const metering = endpoint.supportsInputCluster("seMetering");
    const electricalMeasurements = endpoint.supportsInputCluster("haElectricalMeasurement");
    const args = {};
    if (!metering || !electricalMeasurements) {
        args.cluster = metering ? "metering" : "electrical";
    }
    return [new ExtendGenerator({ extend: m.electricityMeter, args, source: "electricityMeter" })];
}
async function extenderBinaryInput(device, endpoints) {
    const generated = [];
    for (const endpoint of endpoints) {
        const description = `binary_input_${endpoint.ID}`;
        const args = {
            name: await (0, utils_1.getClusterAttributeValue)(endpoint, "genBinaryInput", "description", description),
            cluster: "genBinaryInput",
            attribute: "presentValue",
            reporting: { attribute: "presentValue", min: "MIN", max: "MAX", change: 1 },
            valueOn: ["ON", 1],
            valueOff: ["OFF", 0],
            description: description,
            access: "STATE_GET",
            endpointName: `${endpoint.ID}`,
        };
        generated.push(new ExtendGenerator({ extend: m.binary, args, source: "binary" }));
    }
    return generated;
}
async function extenderBinaryOutput(device, endpoints) {
    const generated = [];
    for (const endpoint of endpoints) {
        const description = `binary_output_${endpoint.ID}`;
        const args = {
            name: await (0, utils_1.getClusterAttributeValue)(endpoint, "genBinaryOutput", "description", description),
            cluster: "genBinaryOutput",
            attribute: "presentValue",
            reporting: { attribute: "presentValue", min: "MIN", max: "MAX", change: 1 },
            valueOn: ["ON", 1],
            valueOff: ["OFF", 0],
            description: description,
            access: "ALL",
            endpointName: `${endpoint.ID}`,
        };
        generated.push(new ExtendGenerator({ extend: m.binary, args, source: "binary" }));
    }
    return generated;
}
//# sourceMappingURL=generateDefinition.js.map