import { InterviewStage, NodeStatus, NodeType, getEnumMemberName, } from "zwave-js";
import { BasicDeviceClass, CommandClasses, dskToString, } from "@zwave-js/core";
import { numberFromLogLevel } from "../util/logger.js";
export function getNodeValues(node, schemaVersion) {
    if (!node.ready) {
        // guard: do not request all values (and their metadata) while the node is still being interviewed.
        // once the node hits ready state, all values will be sent in the 'node ready' event.
        return [];
    }
    return Array.from(node.getDefinedValueIDs(), (valueId) => dumpValue(node, valueId, schemaVersion));
}
export const dumpValue = (node, valueArgs, schemaVersion) => {
    // get CC Version for this endpoint, fallback to CC version of the node itself
    let ccVersion;
    if (valueArgs.endpoint !== undefined) {
        ccVersion = node
            .getEndpoint(valueArgs.endpoint)
            ?.getCCVersion(valueArgs.commandClass);
    }
    if (ccVersion === undefined) {
        ccVersion = node.getEndpoint(0).getCCVersion(valueArgs.commandClass);
    }
    let metadata;
    if (valueArgs.commandClass === CommandClasses.Configuration) {
        metadata = dumpConfigurationMetadata(node.getValueMetadata(valueArgs), schemaVersion);
    }
    else {
        metadata = dumpMetadata(node.getValueMetadata(valueArgs), schemaVersion);
    }
    return {
        endpoint: valueArgs.endpoint,
        commandClass: valueArgs.commandClass,
        commandClassName: valueArgs.commandClassName,
        property: valueArgs.property,
        propertyKey: valueArgs.propertyKey,
        propertyName: valueArgs.propertyName,
        propertyKeyName: valueArgs.propertyKeyName,
        ccVersion,
        // append metadata
        metadata,
        // append actual value
        value: node.getValue(valueArgs),
    };
};
export const dumpConfigurationMetadata = (metadata, schemaVersion) => {
    const base = {
        type: metadata.type,
        readable: metadata.readable,
        writeable: metadata.writeable,
        description: metadata.description,
        label: metadata.label,
        ccSpecific: metadata.ccSpecific,
        valueChangeOptions: metadata.valueChangeOptions,
        default: metadata.default,
        min: metadata.min,
        max: metadata.max,
        states: metadata.states,
        unit: metadata.unit,
        valueSize: metadata.valueSize,
        format: metadata.format,
        isAdvanced: metadata.isAdvanced,
        requiresReInclusion: metadata.requiresReInclusion,
        allowManualEntry: metadata.allowManualEntry,
        isFromConfig: metadata.isFromConfig,
    };
    if (schemaVersion < 2 && base.type === "buffer") {
        base.type = "string";
    }
    if (schemaVersion < 29) {
        base.name = metadata.label;
        base.info = metadata.description;
        return base;
    }
    if (schemaVersion < 46) {
        return base;
    }
    const metadata46 = base;
    metadata46.allowed = metadata.allowed;
    metadata46.purpose = metadata.purpose;
    return metadata46;
};
export const dumpMetadata = (metadata, schemaVersion) => {
    const base = {
        type: metadata.type,
        default: metadata.default,
        readable: metadata.readable,
        writeable: metadata.writeable,
        description: metadata.description,
        label: metadata.label,
        ccSpecific: metadata.ccSpecific,
        valueChangeOptions: metadata.valueChangeOptions,
    };
    if ("min" in metadata) {
        base.min = metadata.min;
    }
    if ("max" in metadata) {
        base.max = metadata.max;
    }
    if ("minLength" in metadata) {
        base.minLength = metadata.minLength;
    }
    if ("maxLength" in metadata) {
        base.maxLength = metadata.maxLength;
    }
    if ("steps" in metadata) {
        base.steps = metadata.steps;
    }
    if ("states" in metadata) {
        base.states = { ...metadata.states };
    }
    if ("unit" in metadata) {
        base.unit = metadata.unit;
    }
    if (schemaVersion < 2 && base.type === "buffer") {
        base.type = "string";
    }
    if (schemaVersion < 27) {
        return base;
    }
    const metadata28 = base;
    metadata28.stateful = metadata.stateful;
    metadata28.secret = metadata.secret;
    if (schemaVersion < 46) {
        return metadata28;
    }
    const metadata46 = metadata28;
    if ("allowed" in metadata) {
        metadata46.allowed = metadata.allowed;
    }
    return metadata46;
};
export const dumpNode = (node, schemaVersion) => {
    const base = {
        nodeId: node.nodeId,
        index: node.index,
        installerIcon: node.installerIcon,
        userIcon: node.userIcon,
        status: node.status,
        ready: node.ready,
        isListening: node.isListening,
        isRouting: node.isRouting,
        isSecure: node.isSecure,
        manufacturerId: node.manufacturerId,
        productId: node.productId,
        productType: node.productType,
        firmwareVersion: node.firmwareVersion,
        zwavePlusVersion: node.zwavePlusVersion,
        name: node.name,
        location: node.location,
        deviceConfig: node.deviceConfig,
        label: node.label,
        endpointCountIsDynamic: node.endpointCountIsDynamic,
        endpointsHaveIdenticalCapabilities: node.endpointsHaveIdenticalCapabilities,
        individualEndpointCount: node.individualEndpointCount,
        aggregatedEndpointCount: node.aggregatedEndpointCount,
        interviewAttempts: node.interviewAttempts,
        endpoints: Array.from(node.getAllEndpoints(), (endpoint) => dumpEndpoint(endpoint, schemaVersion)),
        values: getNodeValues(node, schemaVersion),
    };
    // In schema 4 we started using the interview stage string instead of the enum number
    if (schemaVersion <= 3)
        base.interviewStage = node.interviewStage;
    // Handle schema 3 changes by transforming them into the properties that schema < 3 expects.
    if (schemaVersion < 3) {
        base.isFrequentListening = node.isFrequentListening
            ? Boolean(node.isFrequentListening)
            : null;
        base.maxBaudRate = node.maxDataRate;
        base.version = node.protocolVersion ? node.protocolVersion + 1 : null;
        base.isBeaming = node.supportsBeaming;
        base.nodeType = node.zwavePlusNodeType;
        base.roleType = node.zwavePlusRoleType;
    }
    if (schemaVersion == 0) {
        const node0 = base;
        node0.deviceClass = node.deviceClass || null;
        return node0;
    }
    // All schemas >= 1
    if (schemaVersion <= 2) {
        const node1 = base;
        node1.deviceClass = node.deviceClass
            ? dumpDeviceClass(node.deviceClass, schemaVersion)
            : null;
        node1.commandClasses = Array.from(node.getSupportedCCInstances(), (cc) => dumpCommandClass(node, cc));
        return node1;
    }
    if (schemaVersion == 3) {
        const node3 = base;
        // Add or update changed keys
        node3.isFrequentListening = node.isFrequentListening;
        node3.maxDataRate = node.maxDataRate;
        node3.supportedDataRates = node.supportedDataRates;
        node3.protocolVersion = node.protocolVersion;
        node3.supportsBeaming = node.supportsBeaming;
        node3.supportsSecurity = node.supportsSecurity;
        node3.nodeType = node.nodeType;
        node3.zwavePlusNodeType = node.zwavePlusNodeType;
        node3.zwavePlusRoleType = node.zwavePlusRoleType;
        node3.deviceClass = node.deviceClass
            ? dumpDeviceClass(node.deviceClass, schemaVersion)
            : null;
        node3.commandClasses = Array.from(node.getSupportedCCInstances(), (cc) => dumpCommandClass(node, cc));
        return node3;
    }
    const node4 = base;
    node4.isFrequentListening = node.isFrequentListening;
    node4.maxDataRate = node.maxDataRate;
    node4.supportedDataRates = node.supportedDataRates;
    node4.protocolVersion = node.protocolVersion;
    node4.supportsBeaming = node.supportsBeaming;
    node4.supportsSecurity = node.supportsSecurity;
    node4.nodeType = node.nodeType;
    node4.zwavePlusNodeType = node.zwavePlusNodeType;
    node4.zwavePlusRoleType = node.zwavePlusRoleType;
    node4.deviceClass = node.deviceClass
        ? dumpDeviceClass(node.deviceClass, schemaVersion)
        : null;
    if (schemaVersion <= 14) {
        node4.commandClasses = Array.from(node.getSupportedCCInstances(), (cc) => dumpCommandClass(node, cc));
    }
    node4.interviewStage = InterviewStage[node.interviewStage];
    if (schemaVersion == 4) {
        return node4;
    }
    const node5 = node4;
    node5.deviceDatabaseUrl = node.deviceDatabaseUrl;
    if (schemaVersion <= 6) {
        return node5;
    }
    const node7 = node5;
    node7.statistics = node.statistics;
    if (schemaVersion <= 9) {
        return node7;
    }
    const node10 = node7;
    node10.highestSecurityClass = node.getHighestSecurityClass();
    if (schemaVersion <= 13) {
        return node10;
    }
    const node14 = node10;
    node14.isControllerNode = node.isControllerNode;
    node14.keepAwake = node.keepAwake;
    if (schemaVersion == 14) {
        return node14;
    }
    if (schemaVersion <= 29) {
        return node14;
    }
    const node30 = node14;
    node30.lastSeen = node.lastSeen;
    if (schemaVersion <= 30) {
        return node30;
    }
    const node31 = node30;
    node31.defaultVolume = node.defaultVolume;
    node31.defaultTransitionDuration = node.defaultTransitionDuration;
    if (schemaVersion <= 34) {
        return node31;
    }
    const node35 = node31;
    node35.protocol = node.protocol;
    if (schemaVersion <= 41) {
        return node35;
    }
    const node42 = node35;
    node42.sdkVersion = node.sdkVersion;
    if (schemaVersion < 47) {
        return node42;
    }
    const node47 = node42;
    node47.canSleep = node.canSleep;
    node47.supportsWakeUpOnDemand = node.supportsWakeUpOnDemand;
    node47.hardwareVersion = node.hardwareVersion;
    node47.hasSUCReturnRoute = node.hasSUCReturnRoute;
    node47.manufacturer = node.manufacturer;
    if (node.dsk) {
        node47.dsk = dskToString(node.dsk);
    }
    return node47;
};
export const dumpFoundNode = (foundNode, schemaVersion) => {
    const base = {
        nodeId: foundNode.id,
        deviceClass: foundNode.deviceClass
            ? dumpDeviceClass(foundNode.deviceClass, schemaVersion)
            : null,
    };
    if (schemaVersion < 22) {
        base.status = NodeStatus.Unknown;
        return base;
    }
    if (schemaVersion < 23) {
        const node22 = base;
        return node22;
    }
    const node23 = base;
    node23.controlledCCs = foundNode.controlledCCs;
    node23.supportedCCs = foundNode.supportedCCs;
    return node23;
};
export const dumpEndpoint = (endpoint, schemaVersion) => {
    const base = {
        nodeId: endpoint.nodeId,
        index: endpoint.index,
        installerIcon: endpoint.installerIcon,
        userIcon: endpoint.userIcon,
    };
    if (schemaVersion < 3) {
        return base;
    }
    const endpoint3 = base;
    endpoint3.deviceClass = endpoint.deviceClass
        ? dumpDeviceClass(endpoint.deviceClass, schemaVersion)
        : null;
    if (schemaVersion < 15) {
        return endpoint3;
    }
    const endpoint15 = endpoint3;
    endpoint15.commandClasses = Array.from(endpoint.getSupportedCCInstances(), (cc) => dumpCommandClass(endpoint, cc));
    if (schemaVersion < 26) {
        return endpoint15;
    }
    const endpoint26 = endpoint15;
    endpoint26.endpointLabel = endpoint.endpointLabel;
    return endpoint26;
};
export const dumpDeviceClass = (deviceClass, schemaVersion) => {
    const base = {
        basic: {
            key: deviceClass.basic,
            label: getEnumMemberName(BasicDeviceClass, deviceClass.basic),
        },
        generic: {
            key: deviceClass.generic.key,
            label: deviceClass.generic.label,
        },
        specific: {
            key: deviceClass.specific.key,
            label: deviceClass.specific.label,
        },
    };
    if (schemaVersion < 36) {
        base.mandatoryControlledCCs = [];
        base.mandatorySupportedCCs = [];
        return base;
    }
    const deviceClass36 = base;
    return deviceClass36;
};
export const dumpCommandClass = (endpoint, commandClass) => ({
    id: commandClass.ccId,
    name: CommandClasses[commandClass.ccId],
    version: endpoint.getCCVersion(commandClass.ccId),
    isSecure: endpoint.isCCSecure(commandClass.ccId),
});
export const dumpLogConfig = (driver, schemaVersion) => {
    // transports is used to remove fields from partialLogConfig
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const { transports, ...partialLogConfig } = driver.getLogConfig();
    if (schemaVersion < 3 && typeof partialLogConfig.level === "string") {
        const levelNum = numberFromLogLevel(partialLogConfig.level);
        if (levelNum != undefined) {
            partialLogConfig.level = levelNum;
        }
    }
    return partialLogConfig;
};
export const dumpDriver = (driver, schemaVersion) => {
    const base = {
        logConfig: dumpLogConfig(driver, schemaVersion),
        statisticsEnabled: driver.statisticsEnabled,
    };
    if (schemaVersion < 47) {
        return base;
    }
    const driver47 = base;
    driver47.ready = driver.ready;
    driver47.allNodesReady = driver.allNodesReady;
    driver47.configVersion = driver.configVersion;
    return driver47;
};
export const dumpController = (driver, schemaVersion) => {
    const controller = driver.controller;
    const base = {
        type: controller.type,
        homeId: controller.homeId,
        ownNodeId: controller.ownNodeId,
        isUsingHomeIdFromOtherNetwork: controller.isUsingHomeIdFromOtherNetwork,
        isSISPresent: controller.isSISPresent,
        wasRealPrimary: controller.wasRealPrimary,
        manufacturerId: controller.manufacturerId,
        productType: controller.productType,
        productId: controller.productId,
        supportedFunctionTypes: controller.supportedFunctionTypes,
        sucNodeId: controller.sucNodeId,
        supportsTimers: controller.supportsTimers,
        statistics: controller.statistics,
        inclusionState: controller.inclusionState,
    };
    if (schemaVersion < 22) {
        if (controller.isPrimary !== undefined) {
            base.isSecondary = !controller.isPrimary;
        }
        base.isStaticUpdateController = controller.isSUC;
        base.isSlave = controller.nodeType === NodeType["End Node"];
    }
    if (schemaVersion <= 31) {
        base.isHealNetworkActive = controller.isRebuildingRoutes;
    }
    if (schemaVersion < 16) {
        const controller0 = base;
        controller0.libraryVersion = controller.sdkVersion;
        controller0.serialApiVersion = controller.firmwareVersion;
        return controller0;
    }
    const controller16 = base;
    controller16.sdkVersion = controller.sdkVersion;
    controller16.firmwareVersion = controller.firmwareVersion;
    if (schemaVersion < 22) {
        return controller16;
    }
    const controller22 = controller16;
    controller22.isPrimary = controller.isPrimary;
    controller22.isSUC = controller.isSUC;
    controller22.nodeType = controller.nodeType;
    if (schemaVersion < 25) {
        return controller22;
    }
    const controller25 = controller22;
    controller25.rfRegion = controller.rfRegion;
    if (schemaVersion < 31) {
        return controller25;
    }
    const controller31 = controller25;
    controller31.status = controller.status;
    if (schemaVersion <= 31) {
        return controller31;
    }
    const controller32 = controller31;
    controller32.isRebuildingRoutes = controller.isRebuildingRoutes;
    if (schemaVersion < 34) {
        return controller32;
    }
    const controller34 = controller32;
    controller34.rebuildRoutesProgress = controller.rebuildRoutesProgress;
    if (schemaVersion < 35) {
        return controller34;
    }
    const controller35 = controller34;
    controller35.supportsLongRange = controller.supportsLongRange;
    if (schemaVersion < 36) {
        return controller35;
    }
    const controller36 = controller35;
    controller36.maxLongRangePowerlevel = controller.maxLongRangePowerlevel;
    controller36.longRangeChannel = controller.longRangeChannel;
    controller36.supportsLongRangeAutoChannelSelection =
        controller.supportsLongRangeAutoChannelSelection;
    if (schemaVersion < 47) {
        return controller36;
    }
    const controller47 = controller36;
    controller47.isSIS = controller.isSIS;
    controller47.maxPayloadSize = controller.maxPayloadSize;
    controller47.maxPayloadSizeLR = controller.maxPayloadSizeLR;
    controller47.zwaveApiVersion = controller.zwaveApiVersion;
    controller47.zwaveChipType = controller.zwaveChipType;
    return controller47;
};
export const dumpState = (driver, schemaVersion) => {
    const controller = driver.controller;
    return {
        driver: dumpDriver(driver, schemaVersion),
        controller: dumpController(driver, schemaVersion),
        nodes: Array.from(controller.nodes.values(), (node) => dumpNode(node, schemaVersion)),
    };
};
