"""Data template classes for discovery used to generate additional data for setup."""

from collections.abc import Iterable, Mapping
from dataclasses import dataclass, field
from enum import Enum
import logging
from typing import Any, cast, override

from zwave_js_server.const import CommandClass
from zwave_js_server.const.command_class.energy_production import (
    EnergyProductionParameter,
    EnergyProductionScaleType,
    PowerScale,
    TodaysProductionScale,
    TotalProductionScale,
    TotalTimeScale,
)
from zwave_js_server.const.command_class.meter import (
    CURRENT_METER_TYPES,
    ENERGY_TOTAL_INCREASING_METER_TYPES,
    POWER_FACTOR_METER_TYPES,
    POWER_METER_TYPES,
    UNIT_AMPERE as METER_UNIT_AMPERE,
    UNIT_CUBIC_FEET,
    UNIT_CUBIC_METER as METER_UNIT_CUBIC_METER,
    UNIT_KILOWATT_HOUR,
    UNIT_US_GALLON,
    UNIT_VOLT as METER_UNIT_VOLT,
    UNIT_WATT as METER_UNIT_WATT,
    VOLTAGE_METER_TYPES,
    ElectricScale,
    MeterScaleType,
)
from zwave_js_server.const.command_class.multilevel_sensor import (
    CO2_SENSORS,
    CO_SENSORS,
    CURRENT_SENSORS,
    ENERGY_MEASUREMENT_SENSORS,
    HUMIDITY_SENSORS,
    ILLUMINANCE_SENSORS,
    POWER_SENSORS,
    PRESSURE_SENSORS,
    SIGNAL_STRENGTH_SENSORS,
    UNIT_A_WEIGHTED_DECIBELS,
    UNIT_AMPERE as SENSOR_UNIT_AMPERE,
    UNIT_BTU_H,
    UNIT_CELSIUS,
    UNIT_CENTIMETER,
    UNIT_CUBIC_FEET_PER_MINUTE,
    UNIT_CUBIC_METER as SENSOR_UNIT_CUBIC_METER,
    UNIT_CUBIC_METER_PER_HOUR,
    UNIT_DECIBEL,
    UNIT_DEGREES,
    UNIT_DENSITY,
    UNIT_FAHRENHEIT,
    UNIT_FEET,
    UNIT_GALLONS,
    UNIT_HERTZ,
    UNIT_INCHES_OF_MERCURY,
    UNIT_INCHES_PER_HOUR,
    UNIT_KILOGRAM,
    UNIT_KILOHERTZ,
    UNIT_KILOPASCAL,
    UNIT_LITER,
    UNIT_LUX,
    UNIT_M_S,
    UNIT_METER,
    UNIT_MICROGRAM_PER_CUBIC_METER,
    UNIT_MILLIAMPERE,
    UNIT_MILLIMETER_HOUR,
    UNIT_MILLIVOLT,
    UNIT_MPH,
    UNIT_PARTS_MILLION,
    UNIT_PERCENTAGE_VALUE,
    UNIT_POUND_PER_SQUARE_INCH,
    UNIT_POUNDS,
    UNIT_POWER_LEVEL,
    UNIT_RSSI,
    UNIT_SECOND,
    UNIT_SYSTOLIC,
    UNIT_UV_INDEX,
    UNIT_VOLT as SENSOR_UNIT_VOLT,
    UNIT_WATT as SENSOR_UNIT_WATT,
    UNIT_WATT_PER_SQUARE_METER,
    VOLTAGE_SENSORS,
    MultilevelSensorScaleType,
    MultilevelSensorType,
)
from zwave_js_server.exceptions import UnknownValueData
from zwave_js_server.model.value import (
    ConfigurationValue as ZwaveConfigurationValue,
    Value as ZwaveValue,
)
from zwave_js_server.util.command_class.energy_production import (
    get_energy_production_parameter,
    get_energy_production_scale_type,
)
from zwave_js_server.util.command_class.meter import get_meter_scale_type
from zwave_js_server.util.command_class.multilevel_sensor import (
    get_multilevel_sensor_scale_type,
    get_multilevel_sensor_type,
)

from homeassistant.const import (
    DEGREE,
    LIGHT_LUX,
    SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
    UV_INDEX,
    UnitOfDensity,
    UnitOfElectricCurrent,
    UnitOfElectricPotential,
    UnitOfEnergy,
    UnitOfFrequency,
    UnitOfIrradiance,
    UnitOfLength,
    UnitOfMass,
    UnitOfPower,
    UnitOfPressure,
    UnitOfRatio,
    UnitOfSoundPressure,
    UnitOfSpeed,
    UnitOfTemperature,
    UnitOfTime,
    UnitOfVolume,
    UnitOfVolumeFlowRate,
    UnitOfVolumetricFlux,
)

from .const import (
    ENTITY_DESC_KEY_BATTERY_LIST_STATE,
    ENTITY_DESC_KEY_BATTERY_MAXIMUM_CAPACITY,
    ENTITY_DESC_KEY_BATTERY_TEMPERATURE,
    ENTITY_DESC_KEY_CO,
    ENTITY_DESC_KEY_CO2,
    ENTITY_DESC_KEY_CURRENT,
    ENTITY_DESC_KEY_ENERGY_MEASUREMENT,
    ENTITY_DESC_KEY_ENERGY_PRODUCTION_TIME,
    ENTITY_DESC_KEY_ENERGY_PRODUCTION_TODAY,
    ENTITY_DESC_KEY_ENERGY_PRODUCTION_TOTAL,
    ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING,
    ENTITY_DESC_KEY_HUMIDITY,
    ENTITY_DESC_KEY_ILLUMINANCE,
    ENTITY_DESC_KEY_MEASUREMENT,
    ENTITY_DESC_KEY_POWER,
    ENTITY_DESC_KEY_POWER_FACTOR,
    ENTITY_DESC_KEY_PRESSURE,
    ENTITY_DESC_KEY_SIGNAL_STRENGTH,
    ENTITY_DESC_KEY_TARGET_TEMPERATURE,
    ENTITY_DESC_KEY_TOTAL_INCREASING,
    ENTITY_DESC_KEY_UV_INDEX,
    ENTITY_DESC_KEY_VOLTAGE,
)
from .models import BaseDiscoverySchemaDataTemplate, ZwaveValueID

ENERGY_PRODUCTION_DEVICE_CLASS_MAP: dict[str, list[EnergyProductionParameter]] = {
    ENTITY_DESC_KEY_ENERGY_PRODUCTION_TIME: [EnergyProductionParameter.TOTAL_TIME],
    ENTITY_DESC_KEY_ENERGY_PRODUCTION_TODAY: [
        EnergyProductionParameter.TODAYS_PRODUCTION
    ],
    ENTITY_DESC_KEY_ENERGY_PRODUCTION_TOTAL: [
        EnergyProductionParameter.TOTAL_PRODUCTION
    ],
}


METER_DEVICE_CLASS_MAP: dict[str, list[MeterScaleType]] = {
    ENTITY_DESC_KEY_CURRENT: CURRENT_METER_TYPES,
    ENTITY_DESC_KEY_VOLTAGE: VOLTAGE_METER_TYPES,
    ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING: ENERGY_TOTAL_INCREASING_METER_TYPES,
    ENTITY_DESC_KEY_POWER: POWER_METER_TYPES,
    ENTITY_DESC_KEY_POWER_FACTOR: POWER_FACTOR_METER_TYPES,
}

MULTILEVEL_SENSOR_DEVICE_CLASS_MAP: dict[str, list[MultilevelSensorType]] = {
    ENTITY_DESC_KEY_CO: CO_SENSORS,
    ENTITY_DESC_KEY_CO2: CO2_SENSORS,
    ENTITY_DESC_KEY_CURRENT: CURRENT_SENSORS,
    ENTITY_DESC_KEY_ENERGY_MEASUREMENT: ENERGY_MEASUREMENT_SENSORS,
    ENTITY_DESC_KEY_HUMIDITY: HUMIDITY_SENSORS,
    ENTITY_DESC_KEY_ILLUMINANCE: ILLUMINANCE_SENSORS,
    ENTITY_DESC_KEY_POWER: POWER_SENSORS,
    ENTITY_DESC_KEY_PRESSURE: PRESSURE_SENSORS,
    ENTITY_DESC_KEY_SIGNAL_STRENGTH: SIGNAL_STRENGTH_SENSORS,
    ENTITY_DESC_KEY_VOLTAGE: VOLTAGE_SENSORS,
    ENTITY_DESC_KEY_UV_INDEX: [MultilevelSensorType.ULTRAVIOLET],
}

ENERGY_PRODUCTION_UNIT_MAP: dict[str, list[EnergyProductionScaleType]] = {
    UnitOfEnergy.WATT_HOUR: [
        TotalProductionScale.WATT_HOURS,
        TodaysProductionScale.WATT_HOURS,
    ],
    UnitOfPower.WATT: [PowerScale.WATTS],
    UnitOfTime.SECONDS: [TotalTimeScale.SECONDS],
    UnitOfTime.HOURS: [TotalTimeScale.HOURS],
}

METER_UNIT_MAP: dict[str, list[MeterScaleType]] = {
    UnitOfElectricCurrent.AMPERE: METER_UNIT_AMPERE,
    UnitOfVolume.CUBIC_FEET: UNIT_CUBIC_FEET,
    UnitOfVolume.CUBIC_METERS: METER_UNIT_CUBIC_METER,
    UnitOfVolume.GALLONS: UNIT_US_GALLON,
    UnitOfEnergy.KILO_WATT_HOUR: UNIT_KILOWATT_HOUR,
    UnitOfElectricPotential.VOLT: METER_UNIT_VOLT,
    UnitOfPower.WATT: METER_UNIT_WATT,
}

MULTILEVEL_SENSOR_UNIT_MAP: dict[str, list[MultilevelSensorScaleType]] = {
    UnitOfElectricCurrent.AMPERE: SENSOR_UNIT_AMPERE,
    UnitOfPower.BTU_PER_HOUR: UNIT_BTU_H,
    UnitOfTemperature.CELSIUS: UNIT_CELSIUS,
    UnitOfLength.CENTIMETERS: UNIT_CENTIMETER,
    UnitOfVolumeFlowRate.CUBIC_FEET_PER_MINUTE: UNIT_CUBIC_FEET_PER_MINUTE,
    UnitOfVolume.CUBIC_METERS: SENSOR_UNIT_CUBIC_METER,
    UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR: UNIT_CUBIC_METER_PER_HOUR,
    UnitOfSoundPressure.DECIBEL: UNIT_DECIBEL,
    UnitOfSoundPressure.WEIGHTED_DECIBEL_A: UNIT_A_WEIGHTED_DECIBELS,
    DEGREE: UNIT_DEGREES,
    UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: [
        *UNIT_DENSITY,
        *UNIT_MICROGRAM_PER_CUBIC_METER,
    ],
    UnitOfTemperature.FAHRENHEIT: UNIT_FAHRENHEIT,
    UnitOfLength.FEET: UNIT_FEET,
    UnitOfVolume.GALLONS: UNIT_GALLONS,
    UnitOfFrequency.HERTZ: UNIT_HERTZ,
    UnitOfPressure.INHG: UNIT_INCHES_OF_MERCURY,
    UnitOfPressure.KPA: UNIT_KILOPASCAL,
    UnitOfVolumetricFlux.INCHES_PER_HOUR: UNIT_INCHES_PER_HOUR,
    UnitOfMass.KILOGRAMS: UNIT_KILOGRAM,
    UnitOfFrequency.KILOHERTZ: UNIT_KILOHERTZ,
    UnitOfVolume.LITERS: UNIT_LITER,
    LIGHT_LUX: UNIT_LUX,
    UnitOfLength.METERS: UNIT_METER,
    UnitOfElectricCurrent.MILLIAMPERE: UNIT_MILLIAMPERE,
    UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR: UNIT_MILLIMETER_HOUR,
    UnitOfElectricPotential.MILLIVOLT: UNIT_MILLIVOLT,
    UnitOfSpeed.MILES_PER_HOUR: UNIT_MPH,
    UnitOfSpeed.METERS_PER_SECOND: UNIT_M_S,
    UnitOfRatio.PARTS_PER_MILLION: UNIT_PARTS_MILLION,
    UnitOfRatio.PERCENTAGE: [*UNIT_PERCENTAGE_VALUE, *UNIT_RSSI],
    UnitOfMass.POUNDS: UNIT_POUNDS,
    UnitOfPressure.PSI: UNIT_POUND_PER_SQUARE_INCH,
    SIGNAL_STRENGTH_DECIBELS_MILLIWATT: UNIT_POWER_LEVEL,
    UnitOfTime.SECONDS: UNIT_SECOND,
    UnitOfPressure.MMHG: UNIT_SYSTOLIC,
    UnitOfElectricPotential.VOLT: SENSOR_UNIT_VOLT,
    UnitOfPower.WATT: SENSOR_UNIT_WATT,
    UnitOfIrradiance.WATTS_PER_SQUARE_METER: UNIT_WATT_PER_SQUARE_METER,
    UV_INDEX: UNIT_UV_INDEX,
}

_LOGGER = logging.getLogger(__name__)


@dataclass
class DynamicCurrentTempClimateDataTemplate(BaseDiscoverySchemaDataTemplate):
    """Data template class for Z-Wave JS Climate entities with dynamic current temps."""

    lookup_table: dict[str | int, ZwaveValueID] = field(default_factory=dict)
    dependent_value: ZwaveValueID | None = None

    @override
    def resolve_data(self, value: ZwaveValue) -> dict[str, Any]:
        """Resolve helper class data for a discovered value."""
        if not self.lookup_table or not self.dependent_value:
            raise ValueError("Invalid discovery data template")
        data: dict[str, Any] = {
            "lookup_table": {},
            "dependent_value": self._get_value_from_id(
                value.node, self.dependent_value
            ),
        }
        for key, value_id in self.lookup_table.items():
            data["lookup_table"][key] = self._get_value_from_id(value.node, value_id)

        return data

    @override
    def values_to_watch(
        self, resolved_data: dict[str, Any]
    ) -> Iterable[ZwaveValue | None]:
        """Return list of all ZwaveValues resolved by helper that should be watched."""
        return [
            *resolved_data["lookup_table"].values(),
            resolved_data["dependent_value"],
        ]

    @staticmethod
    def current_temperature_value(resolved_data: dict[str, Any]) -> ZwaveValue | None:
        """Get current temperature ZwaveValue from resolved data."""
        lookup_table: dict[str | int, ZwaveValue | None] = resolved_data["lookup_table"]
        dependent_value: ZwaveValue | None = resolved_data["dependent_value"]

        if dependent_value and dependent_value.value is not None:
            lookup_key = dependent_value.metadata.states[
                str(dependent_value.value)
            ].split("-")[0]
            return lookup_table.get(lookup_key)

        return None


@dataclass
class NumericSensorDataTemplateData:
    """Class to represent returned data from NumericSensorDataTemplate."""

    entity_description_key: str | None = None
    unit_of_measurement: str | None = None


class NumericSensorDataTemplate(BaseDiscoverySchemaDataTemplate):
    """Data template class for Z-Wave Sensor entities."""

    @staticmethod
    def find_key_from_matching_set[_T: Enum](
        enum_value: _T, set_map: Mapping[str, list[_T]]
    ) -> str | None:
        """Find a key in a set map that matches a given enum value."""
        for key, value_set in set_map.items():
            for value_in_set in value_set:
                # Since these are IntEnums and the different classes reuse the same
                # values, we need to match the class as well
                if (
                    value_in_set.__class__ == enum_value.__class__
                    and value_in_set == enum_value
                ):
                    return key
        return None

    @override
    def resolve_data(self, value: ZwaveValue) -> NumericSensorDataTemplateData:
        """Resolve helper class data for a discovered value."""

        if value.command_class == CommandClass.BATTERY and value.property_ in (
            "chargingStatus",
            "rechargeOrReplace",
        ):
            return NumericSensorDataTemplateData(
                ENTITY_DESC_KEY_BATTERY_LIST_STATE, None
            )
        if (
            value.command_class == CommandClass.BATTERY
            and value.property_ == "maximumCapacity"
        ):
            return NumericSensorDataTemplateData(
                ENTITY_DESC_KEY_BATTERY_MAXIMUM_CAPACITY, UnitOfRatio.PERCENTAGE
            )
        if (
            value.command_class == CommandClass.BATTERY
            and value.property_ == "temperature"
        ):
            return NumericSensorDataTemplateData(
                ENTITY_DESC_KEY_BATTERY_TEMPERATURE, UnitOfTemperature.CELSIUS
            )

        if value.command_class == CommandClass.METER:
            try:
                meter_scale_type = get_meter_scale_type(value)
            except UnknownValueData:
                return NumericSensorDataTemplateData()

            unit = self.find_key_from_matching_set(meter_scale_type, METER_UNIT_MAP)
            # We do this because even though these are energy scales, they don't meet
            # the unit requirements for the energy device class.
            if meter_scale_type in (
                ElectricScale.PULSE_COUNT,
                ElectricScale.KILOVOLT_AMPERE_HOUR,
                ElectricScale.KILOVOLT_AMPERE_REACTIVE_HOUR,
            ):
                return NumericSensorDataTemplateData(
                    ENTITY_DESC_KEY_TOTAL_INCREASING, unit
                )
            # We do this because even though these are power scales, they don't meet
            # the unit requirements for the power device class.
            if meter_scale_type == ElectricScale.KILOVOLT_AMPERE_REACTIVE:
                return NumericSensorDataTemplateData(ENTITY_DESC_KEY_MEASUREMENT, unit)

            return NumericSensorDataTemplateData(
                self.find_key_from_matching_set(
                    meter_scale_type, METER_DEVICE_CLASS_MAP
                ),
                unit,
            )

        if value.command_class == CommandClass.SENSOR_MULTILEVEL:
            try:
                sensor_type = get_multilevel_sensor_type(value)
                multilevel_sensor_scale_type = get_multilevel_sensor_scale_type(value)
            except UnknownValueData:
                return NumericSensorDataTemplateData()
            unit = self.find_key_from_matching_set(
                multilevel_sensor_scale_type, MULTILEVEL_SENSOR_UNIT_MAP
            )
            if sensor_type == MultilevelSensorType.TARGET_TEMPERATURE:
                return NumericSensorDataTemplateData(
                    ENTITY_DESC_KEY_TARGET_TEMPERATURE, unit
                )
            key = self.find_key_from_matching_set(
                sensor_type, MULTILEVEL_SENSOR_DEVICE_CLASS_MAP
            )
            if key:
                return NumericSensorDataTemplateData(key, unit)

        if value.command_class == CommandClass.ENERGY_PRODUCTION:
            energy_production_parameter = get_energy_production_parameter(value)
            energy_production_scale_type = get_energy_production_scale_type(value)
            unit = self.find_key_from_matching_set(
                energy_production_scale_type, ENERGY_PRODUCTION_UNIT_MAP
            )
            key = self.find_key_from_matching_set(
                energy_production_parameter, ENERGY_PRODUCTION_DEVICE_CLASS_MAP
            )
            if key:
                return NumericSensorDataTemplateData(key, unit)

        return NumericSensorDataTemplateData()


@dataclass
class TiltValueMix:
    """Mixin data class for the current_tilt_value and target_tilt_value."""

    current_tilt_value_id: ZwaveValueID
    target_tilt_value_id: ZwaveValueID


@dataclass
class CoverTiltDataTemplate(BaseDiscoverySchemaDataTemplate, TiltValueMix):
    """Tilt data template class for Z-Wave Cover entities."""

    @override
    def resolve_data(self, value: ZwaveValue) -> dict[str, ZwaveValue]:
        """Resolve helper class data for a discovered value."""
        current_tilt_value = self._get_value_from_id(
            value.node, self.current_tilt_value_id
        )
        assert current_tilt_value
        target_tilt_value = self._get_value_from_id(
            value.node, self.target_tilt_value_id
        )
        assert target_tilt_value
        return {
            "current_tilt_value": current_tilt_value,
            "target_tilt_value": target_tilt_value,
        }

    @override
    def values_to_watch(
        self, resolved_data: dict[str, Any]
    ) -> Iterable[ZwaveValue | None]:
        """Return list of all ZwaveValues resolved by helper that should be watched."""
        return [resolved_data["current_tilt_value"], resolved_data["target_tilt_value"]]

    @staticmethod
    def current_tilt_value(resolved_data: dict[str, ZwaveValue]) -> ZwaveValue:
        """Get current tilt ZwaveValue from resolved data."""
        return resolved_data["current_tilt_value"]

    @staticmethod
    def target_tilt_value(resolved_data: dict[str, ZwaveValue]) -> ZwaveValue:
        """Get target tilt ZwaveValue from resolved data."""
        return resolved_data["target_tilt_value"]


@dataclass
class FanValueMapping:
    """Data class to represent how a fan's values map to features."""

    presets: dict[int, str] = field(default_factory=dict)
    speeds: list[tuple[int, int]] = field(default_factory=list)

    def __post_init__(self) -> None:
        """Validate inputs.

        These inputs are hardcoded in `discovery.py`, so these checks should
        only fail due to developer error.
        """
        assert len(self.speeds) > 0, "At least one speed must be specified"
        for speed_range in self.speeds:
            (low, high) = speed_range
            assert high >= low, "Speed range values must be ordered"


@dataclass
class FanValueMappingDataTemplate:
    """Mixin to define `get_fan_value_mapping`."""

    def get_fan_value_mapping(
        self, resolved_data: dict[str, Any]
    ) -> FanValueMapping | None:
        """Get the value mappings for this device."""
        raise NotImplementedError


@dataclass
class ConfigurableFanValueMappingValueMix:
    """Mixin for fan properties that change based on device config."""

    configuration_option: ZwaveValueID
    configuration_value_to_fan_value_mapping: dict[int, FanValueMapping]


@dataclass
class ConfigurableFanValueMappingDataTemplate(
    BaseDiscoverySchemaDataTemplate,
    FanValueMappingDataTemplate,
    ConfigurableFanValueMappingValueMix,
):
    """Gets fan speeds based on a configuration value.

    Example:
      ZWaveDiscoverySchema(
          platform="fan",
          hint="has_fan_value_mapping",
          ...
          data_template=ConfigurableFanValueMappingDataTemplate(
            configuration_option=ZwaveValueID(
                property_=5, command_class=CommandClass.CONFIGURATION, endpoint=0
            ),
            configuration_value_to_fan_value_mapping={
                0: FanValueMapping(speeds=[(1,33), (34,66), (67,99)]),
                1: FanValueMapping(speeds=[(1,24), (25,49), (50,74), (75,99)]),
            },
          ),

    `configuration_option` is a reference to the setting that determines which
    value mapping to use (e.g., 3 speeds or 4 speeds).

    `configuration_value_to_fan_value_mapping` maps the values from
    `configuration_option` to the value mapping object.

    """

    @override
    def resolve_data(
        self, value: ZwaveValue
    ) -> dict[str, ZwaveConfigurationValue | None]:
        """Resolve helper class data for a discovered value."""
        zwave_value = cast(
            ZwaveConfigurationValue | None,
            self._get_value_from_id(value.node, self.configuration_option),
        )
        return {"configuration_value": zwave_value}

    @override
    def values_to_watch(
        self, resolved_data: dict[str, ZwaveConfigurationValue | None]
    ) -> Iterable[ZwaveConfigurationValue | None]:
        """Return list of all ZwaveValues that should be watched."""
        return [
            resolved_data["configuration_value"],
        ]

    @override
    def get_fan_value_mapping(
        self, resolved_data: dict[str, ZwaveConfigurationValue | None]
    ) -> FanValueMapping | None:
        """Get current fan properties from resolved data."""
        zwave_value = resolved_data["configuration_value"]

        if zwave_value is None:
            _LOGGER.warning("Unable to read device configuration value")
            return None

        if zwave_value.value is None:
            _LOGGER.warning("Fan configuration value is missing")
            return None

        fan_value_mapping = self.configuration_value_to_fan_value_mapping.get(
            zwave_value.value
        )
        if fan_value_mapping is None:
            _LOGGER.warning("Unrecognized fan configuration value")
            return None

        return fan_value_mapping


@dataclass
class FixedFanValueMappingValueMix:
    """Mixin data class for defining supported fan speeds."""

    fan_value_mapping: FanValueMapping


@dataclass
class FixedFanValueMappingDataTemplate(
    BaseDiscoverySchemaDataTemplate,
    FanValueMappingDataTemplate,
    FixedFanValueMappingValueMix,
):
    """Specifies a fixed set of properties for a fan.

    Example:
      ZWaveDiscoverySchema(
          platform="fan",
          hint="has_fan_value_mapping",
          ...
          data_template=FixedFanValueMappingDataTemplate(
              config=FanValueMapping(
                speeds=[(1, 32), (33, 65), (66, 99)]
              )
          ),
      ),

    """

    @override
    def get_fan_value_mapping(
        self, resolved_data: dict[str, ZwaveConfigurationValue]
    ) -> FanValueMapping:
        """Get the fan properties for this device."""
        return self.fan_value_mapping
