import { CommandClass, EntryControlCCValues, NotificationCCValues, entryControlEventTypeLabels, } from "@zwave-js/cc";
import { MultiChannelCCValues } from "@zwave-js/cc/MultiChannelCC";
import { CommandClasses, ZWaveError, ZWaveErrorCodes, allCCs, applicationCCs, getCCName, getNotification, } from "@zwave-js/core";
function getValue(ctx, nodeId, valueId) {
    return ctx.getValueDB(nodeId).getValue(valueId);
}
function setValue(ctx, nodeId, valueId, value, options) {
    return ctx.getValueDB(nodeId).setValue(valueId, value, options);
}
export function endpointCountIsDynamic(ctx, nodeId) {
    return getValue(ctx, nodeId, MultiChannelCCValues.endpointCountIsDynamic.id);
}
export function endpointsHaveIdenticalCapabilities(ctx, nodeId) {
    return getValue(ctx, nodeId, MultiChannelCCValues.endpointsHaveIdenticalCapabilities.id);
}
export function getIndividualEndpointCount(ctx, nodeId) {
    return getValue(ctx, nodeId, MultiChannelCCValues.individualEndpointCount.id);
}
export function getAggregatedEndpointCount(ctx, nodeId) {
    return getValue(ctx, nodeId, MultiChannelCCValues.aggregatedEndpointCount.id);
}
export function getEndpointCount(ctx, nodeId) {
    return ((getIndividualEndpointCount(ctx, nodeId) || 0)
        + (getAggregatedEndpointCount(ctx, nodeId) || 0));
}
export function setIndividualEndpointCount(ctx, nodeId, count) {
    setValue(ctx, nodeId, MultiChannelCCValues.individualEndpointCount.id, count);
}
export function setAggregatedEndpointCount(ctx, nodeId, count) {
    setValue(ctx, nodeId, MultiChannelCCValues.aggregatedEndpointCount.id, count);
}
export function getEndpointIndizes(ctx, nodeId) {
    let ret = getValue(ctx, nodeId, MultiChannelCCValues.endpointIndizes.id);
    if (!ret) {
        // Endpoint indizes not stored, assume sequential endpoints
        ret = [];
        for (let i = 1; i <= getEndpointCount(ctx, nodeId); i++) {
            ret.push(i);
        }
    }
    return ret;
}
export function setEndpointIndizes(ctx, nodeId, indizes) {
    setValue(ctx, nodeId, MultiChannelCCValues.endpointIndizes.id, indizes);
}
export function isMultiChannelInterviewComplete(ctx, nodeId) {
    return !!getValue(ctx, nodeId, {
        commandClass: CommandClasses["Multi Channel"],
        endpoint: 0,
        property: "interviewComplete",
    });
}
export function setMultiChannelInterviewComplete(ctx, nodeId, complete) {
    setValue(ctx, nodeId, {
        commandClass: CommandClasses["Multi Channel"],
        endpoint: 0,
        property: "interviewComplete",
    }, complete);
}
export function getAllEndpoints(ctx, node) {
    const ret = [node];
    // Check if the Multi Channel CC interview for this node is completed,
    // because we don't have all the endpoint information before that
    if (isMultiChannelInterviewComplete(ctx, node.nodeId)) {
        for (const i of getEndpointIndizes(ctx, node.nodeId)) {
            const endpoint = node.getEndpoint(i);
            if (endpoint)
                ret.push(endpoint);
        }
    }
    return ret;
}
/** Determines whether the root application CC values should be hidden in favor of endpoint values */
export function shouldHideRootApplicationCCValues(ctx, nodeId) {
    // This is not the case when the root values should explicitly be preserved
    const compatConfig = ctx.getDeviceConfig?.(nodeId)?.compat;
    if (compatConfig?.preserveRootApplicationCCValueIDs)
        return false;
    // This is not the case when there are no endpoints
    const endpointIndizes = getEndpointIndizes(ctx, nodeId);
    if (endpointIndizes.length === 0)
        return false;
    // This is not the case when only individual endpoints should be preserved in addition to the root
    const preserveEndpoints = compatConfig?.preserveEndpoints;
    if (preserveEndpoints != undefined
        && preserveEndpoints !== "*"
        && preserveEndpoints.length !== endpointIndizes.length) {
        return false;
    }
    // Otherwise they should be hidden
    return true;
}
/**
 * Enhances a value id so it can be consumed better by applications
 */
export function translateValueID(ctx, endpoint, valueId) {
    // Try to retrieve the speaking CC name
    const commandClassName = getCCName(valueId.commandClass);
    const ret = {
        commandClassName,
        ...valueId,
    };
    const ccInstance = CommandClass.createInstanceUnchecked(endpoint, valueId.commandClass);
    if (!ccInstance) {
        throw new ZWaveError(`Cannot translate a value ID for the non-implemented CC ${getCCName(valueId.commandClass)}`, ZWaveErrorCodes.CC_NotImplemented);
    }
    // Retrieve the speaking property name
    ret.propertyName = ccInstance.translateProperty(ctx, valueId.property, valueId.propertyKey);
    // Try to retrieve the speaking property key
    if (valueId.propertyKey != undefined) {
        const propertyKey = ccInstance.translatePropertyKey(ctx, valueId.property, valueId.propertyKey);
        ret.propertyKeyName = propertyKey;
    }
    return ret;
}
/**
 * Removes all Value IDs from an array that belong to a root endpoint and have a corresponding
 * Value ID on a non-root endpoint
 */
export function filterRootApplicationCCValueIDs(allValueIds) {
    const shouldHideRootValueID = (valueId, allValueIds) => {
        // Non-root endpoint values don't need to be filtered
        if (!!valueId.endpoint)
            return false;
        // Non-application CCs don't need to be filtered
        if (!applicationCCs.includes(valueId.commandClass))
            return false;
        // Filter out root values if an identical value ID exists for another endpoint
        const valueExistsOnAnotherEndpoint = allValueIds.some((other) => 
        // same CC
        other.commandClass === valueId.commandClass
            // non-root endpoint
            && !!other.endpoint
            // same property and key
            && other.property === valueId.property
            && other.propertyKey === valueId.propertyKey);
        return valueExistsOnAnotherEndpoint;
    };
    return allValueIds.filter((vid) => !shouldHideRootValueID(vid, allValueIds));
}
/** Returns a list of all value names that are defined on all endpoints of this node */
export function getDefinedValueIDs(ctx, node) {
    return getDefinedValueIDsInternal(ctx, node, false);
}
/**
 * @internal
 * Returns a list of all value names that are defined on all endpoints of this node
 */
export function getDefinedValueIDsInternal(ctx, node, includeInternal = false) {
    // The controller has no values. Even if some ended up in the cache somehow, do not return any.
    if (node.id === ctx.ownNodeId)
        return [];
    let ret = [];
    const allowControlled = [
        CommandClasses["Scene Activation"],
    ];
    for (const endpoint of getAllEndpoints(ctx, node)) {
        for (const cc of allCCs) {
            if (
            // Create values only for supported CCs
            endpoint.supportsCC(cc)
                // ...and some controlled CCs
                || (endpoint.controlsCC(cc) && allowControlled.includes(cc))
                // ...and possibly Basic CC, which has some extra checks to know
                // whether values should be exposed
                || cc === CommandClasses.Basic) {
                const ccInstance = CommandClass.createInstanceUnchecked(endpoint, cc);
                if (ccInstance) {
                    ret.push(...ccInstance.getDefinedValueIDs(ctx, includeInternal));
                }
            }
        }
    }
    // Application command classes of the Root Device capabilities that are also advertised by at
    // least one End Point SHOULD be filtered out by controlling nodes before presenting the functionalities
    // via service discovery mechanisms like mDNS or to users in a GUI.
    // We do this when there are endpoints that were explicitly preserved
    if (shouldHideRootApplicationCCValues(ctx, node.id)) {
        ret = filterRootApplicationCCValueIDs(ret);
    }
    // Translate the remaining value IDs before exposing them to applications
    return ret.map((id) => translateValueID(ctx, node, id));
}
export function getSupportedNotificationEvents(ctx, node) {
    const ret = [];
    const valueDB = ctx.getValueDB(node.id);
    for (const endpoint of getAllEndpoints(ctx, node)) {
        // This list is hardcoded since there is just a small list of CCs
        // that can send notifications
        if (endpoint.supportsCC(CommandClasses["Entry Control"])) {
            const eventTypes = valueDB.getValue(EntryControlCCValues.supportedEventTypes.endpoint(endpoint.index));
            if (eventTypes) {
                const capability = {
                    commandClass: CommandClasses["Entry Control"],
                    endpoint: endpoint.index,
                    supportedEventTypes: Object.fromEntries(eventTypes.map((et) => [
                        et,
                        entryControlEventTypeLabels[et],
                    ])),
                };
                ret.push(capability);
            }
        }
        if (endpoint.supportsCC(CommandClasses.Notification)) {
            const notificationTypes = valueDB.getValue(NotificationCCValues.supportedNotificationTypes.endpoint(endpoint.index)) ?? [];
            const capability = {
                commandClass: CommandClasses.Notification,
                endpoint: endpoint.index,
                supportedNotificationTypes: {},
            };
            let hasEvent = false;
            for (const notificationType of notificationTypes) {
                const notification = getNotification(notificationType);
                if (!notification)
                    continue;
                const notificationEvents = valueDB.getValue(NotificationCCValues
                    .supportedNotificationEvents(notificationType)
                    .endpoint(endpoint.index))
                    ?.map((e) => notification.events.get(e))
                    .filter((e) => e != undefined);
                if (!notificationEvents || notificationEvents.length === 0) {
                    continue;
                }
                capability.supportedNotificationTypes[notificationType] = {
                    label: notification.name,
                    supportedEvents: Object.fromEntries(notificationEvents.map((e) => [e.value, e.label])),
                };
                hasEvent = true;
            }
            if (hasEvent)
                ret.push(capability);
        }
    }
    return ret;
}
//# sourceMappingURL=utils.js.map