"""Sensors flow for Withings."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from typing import Any

from aiowithings import (
    Activity,
    Device,
    Goals,
    MeasurementPosition,
    MeasurementType,
    SleepSummary,
    Workout,
    WorkoutCategory,
)

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
    PERCENTAGE,
    Platform,
    UnitOfLength,
    UnitOfMass,
    UnitOfPressure,
    UnitOfSpeed,
    UnitOfTemperature,
    UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util import dt as dt_util

from . import WithingsConfigEntry
from .const import (
    DOMAIN,
    LOGGER,
    SCORE_POINTS,
    UOM_BEATS_PER_MINUTE,
    UOM_BREATHS_PER_MINUTE,
    UOM_FREQUENCY,
)
from .coordinator import (
    WithingsActivityDataUpdateCoordinator,
    WithingsDataUpdateCoordinator,
    WithingsDeviceDataUpdateCoordinator,
    WithingsGoalsDataUpdateCoordinator,
    WithingsMeasurementDataUpdateCoordinator,
    WithingsSleepDataUpdateCoordinator,
    WithingsWorkoutDataUpdateCoordinator,
)
from .entity import WithingsDeviceEntity, WithingsEntity


@dataclass(frozen=True, kw_only=True)
class WithingsMeasurementSensorEntityDescription(SensorEntityDescription):
    """Immutable class for describing withings data."""

    measurement_type: MeasurementType
    measurement_position: MeasurementPosition | None = None


MEASUREMENT_SENSORS: dict[
    MeasurementType, WithingsMeasurementSensorEntityDescription
] = {
    MeasurementType.WEIGHT: WithingsMeasurementSensorEntityDescription(
        key="weight_kg",
        measurement_type=MeasurementType.WEIGHT,
        native_unit_of_measurement=UnitOfMass.KILOGRAMS,
        suggested_display_precision=2,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.FAT_MASS_WEIGHT: WithingsMeasurementSensorEntityDescription(
        key="fat_mass_kg",
        measurement_type=MeasurementType.FAT_MASS_WEIGHT,
        translation_key="fat_mass",
        native_unit_of_measurement=UnitOfMass.KILOGRAMS,
        suggested_display_precision=2,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.FAT_FREE_MASS: WithingsMeasurementSensorEntityDescription(
        key="fat_free_mass_kg",
        measurement_type=MeasurementType.FAT_FREE_MASS,
        translation_key="fat_free_mass",
        native_unit_of_measurement=UnitOfMass.KILOGRAMS,
        suggested_display_precision=2,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.MUSCLE_MASS: WithingsMeasurementSensorEntityDescription(
        key="muscle_mass_kg",
        measurement_type=MeasurementType.MUSCLE_MASS,
        translation_key="muscle_mass",
        native_unit_of_measurement=UnitOfMass.KILOGRAMS,
        suggested_display_precision=2,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.BONE_MASS: WithingsMeasurementSensorEntityDescription(
        key="bone_mass_kg",
        measurement_type=MeasurementType.BONE_MASS,
        translation_key="bone_mass",
        native_unit_of_measurement=UnitOfMass.KILOGRAMS,
        suggested_display_precision=2,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.HEIGHT: WithingsMeasurementSensorEntityDescription(
        key="height_m",
        measurement_type=MeasurementType.HEIGHT,
        translation_key="height",
        native_unit_of_measurement=UnitOfLength.METERS,
        suggested_display_precision=2,
        device_class=SensorDeviceClass.DISTANCE,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    MeasurementType.TEMPERATURE: WithingsMeasurementSensorEntityDescription(
        key="temperature_c",
        measurement_type=MeasurementType.TEMPERATURE,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.BODY_TEMPERATURE: WithingsMeasurementSensorEntityDescription(
        key="body_temperature_c",
        measurement_type=MeasurementType.BODY_TEMPERATURE,
        translation_key="body_temperature",
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.SKIN_TEMPERATURE: WithingsMeasurementSensorEntityDescription(
        key="skin_temperature_c",
        measurement_type=MeasurementType.SKIN_TEMPERATURE,
        translation_key="skin_temperature",
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.FAT_RATIO: WithingsMeasurementSensorEntityDescription(
        key="fat_ratio_pct",
        measurement_type=MeasurementType.FAT_RATIO,
        translation_key="fat_ratio",
        native_unit_of_measurement=PERCENTAGE,
        suggested_display_precision=2,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.DIASTOLIC_BLOOD_PRESSURE: WithingsMeasurementSensorEntityDescription(
        key="diastolic_blood_pressure_mmhg",
        measurement_type=MeasurementType.DIASTOLIC_BLOOD_PRESSURE,
        translation_key="diastolic_blood_pressure",
        native_unit_of_measurement=UnitOfPressure.MMHG,
        device_class=SensorDeviceClass.PRESSURE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.SYSTOLIC_BLOOD_PRESSURE: WithingsMeasurementSensorEntityDescription(
        key="systolic_blood_pressure_mmhg",
        measurement_type=MeasurementType.SYSTOLIC_BLOOD_PRESSURE,
        translation_key="systolic_blood_pressure",
        native_unit_of_measurement=UnitOfPressure.MMHG,
        device_class=SensorDeviceClass.PRESSURE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.HEART_RATE: WithingsMeasurementSensorEntityDescription(
        key="heart_pulse_bpm",
        measurement_type=MeasurementType.HEART_RATE,
        translation_key="heart_pulse",
        native_unit_of_measurement=UOM_BEATS_PER_MINUTE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.SP02: WithingsMeasurementSensorEntityDescription(
        key="spo2_pct",
        measurement_type=MeasurementType.SP02,
        translation_key="spo2",
        native_unit_of_measurement=PERCENTAGE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.HYDRATION: WithingsMeasurementSensorEntityDescription(
        key="hydration",
        measurement_type=MeasurementType.HYDRATION,
        translation_key="hydration",
        native_unit_of_measurement=UnitOfMass.KILOGRAMS,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    MeasurementType.PULSE_WAVE_VELOCITY: WithingsMeasurementSensorEntityDescription(
        key="pulse_wave_velocity",
        measurement_type=MeasurementType.PULSE_WAVE_VELOCITY,
        translation_key="pulse_wave_velocity",
        native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
        device_class=SensorDeviceClass.SPEED,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    MeasurementType.VO2: WithingsMeasurementSensorEntityDescription(
        key="vo2_max",
        measurement_type=MeasurementType.VO2,
        translation_key="vo2_max",
        native_unit_of_measurement="ml/min/kg",
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    MeasurementType.EXTRACELLULAR_WATER: WithingsMeasurementSensorEntityDescription(
        key="extracellular_water",
        measurement_type=MeasurementType.EXTRACELLULAR_WATER,
        translation_key="extracellular_water",
        native_unit_of_measurement=UnitOfMass.KILOGRAMS,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    MeasurementType.INTRACELLULAR_WATER: WithingsMeasurementSensorEntityDescription(
        key="intracellular_water",
        measurement_type=MeasurementType.INTRACELLULAR_WATER,
        translation_key="intracellular_water",
        native_unit_of_measurement=UnitOfMass.KILOGRAMS,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    MeasurementType.VASCULAR_AGE: WithingsMeasurementSensorEntityDescription(
        key="vascular_age",
        measurement_type=MeasurementType.VASCULAR_AGE,
        translation_key="vascular_age",
        entity_registry_enabled_default=False,
    ),
    MeasurementType.VISCERAL_FAT: WithingsMeasurementSensorEntityDescription(
        key="visceral_fat",
        measurement_type=MeasurementType.VISCERAL_FAT,
        translation_key="visceral_fat_index",
        entity_registry_enabled_default=False,
    ),
    MeasurementType.ELECTRODERMAL_ACTIVITY_FEET: WithingsMeasurementSensorEntityDescription(
        key="electrodermal_activity_feet",
        measurement_type=MeasurementType.ELECTRODERMAL_ACTIVITY_FEET,
        translation_key="electrodermal_activity_feet",
        native_unit_of_measurement=PERCENTAGE,
        entity_registry_enabled_default=False,
    ),
    MeasurementType.ELECTRODERMAL_ACTIVITY_LEFT_FOOT: WithingsMeasurementSensorEntityDescription(
        key="electrodermal_activity_left_foot",
        measurement_type=MeasurementType.ELECTRODERMAL_ACTIVITY_LEFT_FOOT,
        translation_key="electrodermal_activity_left_foot",
        native_unit_of_measurement=PERCENTAGE,
        entity_registry_enabled_default=False,
    ),
    MeasurementType.ELECTRODERMAL_ACTIVITY_RIGHT_FOOT: WithingsMeasurementSensorEntityDescription(
        key="electrodermal_activity_right_foot",
        measurement_type=MeasurementType.ELECTRODERMAL_ACTIVITY_RIGHT_FOOT,
        translation_key="electrodermal_activity_right_foot",
        native_unit_of_measurement=PERCENTAGE,
        entity_registry_enabled_default=False,
    ),
}


def get_positional_measurement_description(
    measurement_type: MeasurementType, measurement_position: MeasurementPosition
) -> WithingsMeasurementSensorEntityDescription | None:
    """Get the sensor description for a measurement type."""
    if measurement_position not in (
        MeasurementPosition.TORSO,
        MeasurementPosition.LEFT_ARM,
        MeasurementPosition.RIGHT_ARM,
        MeasurementPosition.LEFT_LEG,
        MeasurementPosition.RIGHT_LEG,
    ) or measurement_type not in (
        MeasurementType.MUSCLE_MASS_FOR_SEGMENTS,
        MeasurementType.FAT_FREE_MASS_FOR_SEGMENTS,
        MeasurementType.FAT_MASS_FOR_SEGMENTS,
    ):
        return None
    return WithingsMeasurementSensorEntityDescription(
        key=f"{measurement_type.name.lower()}_{measurement_position.name.lower()}",
        measurement_type=measurement_type,
        measurement_position=measurement_position,
        translation_key=f"{measurement_type.name.lower()}_{measurement_position.name.lower()}",
        native_unit_of_measurement=UnitOfMass.KILOGRAMS,
        suggested_display_precision=2,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    )


def get_measurement_description(
    measurement: tuple[MeasurementType, MeasurementPosition | None],
) -> WithingsMeasurementSensorEntityDescription | None:
    """Get the sensor description for a measurement type."""
    measurement_type, measurement_position = measurement
    if measurement_position is not None:
        return get_positional_measurement_description(
            measurement_type, measurement_position
        )
    return MEASUREMENT_SENSORS.get(measurement_type)


@dataclass(frozen=True, kw_only=True)
class WithingsSleepSensorEntityDescription(SensorEntityDescription):
    """Immutable class for describing withings data."""

    value_fn: Callable[[SleepSummary], StateType]


SLEEP_SENSORS = [
    WithingsSleepSensorEntityDescription(
        key="sleep_breathing_disturbances_intensity",
        value_fn=lambda sleep_summary: sleep_summary.breathing_disturbances_intensity,
        translation_key="breathing_disturbances_intensity",
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_deep_duration_seconds",
        value_fn=lambda sleep_summary: sleep_summary.deep_sleep_duration,
        translation_key="deep_sleep",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.HOURS,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_tosleep_duration_seconds",
        value_fn=lambda sleep_summary: sleep_summary.sleep_latency,
        translation_key="time_to_sleep",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.HOURS,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_towakeup_duration_seconds",
        value_fn=lambda sleep_summary: sleep_summary.wake_up_latency,
        translation_key="time_to_wakeup",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.HOURS,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_heart_rate_average_bpm",
        value_fn=lambda sleep_summary: sleep_summary.average_heart_rate,
        translation_key="average_heart_rate",
        native_unit_of_measurement=UOM_BEATS_PER_MINUTE,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_heart_rate_max_bpm",
        value_fn=lambda sleep_summary: sleep_summary.max_heart_rate,
        translation_key="maximum_heart_rate",
        native_unit_of_measurement=UOM_BEATS_PER_MINUTE,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_heart_rate_min_bpm",
        value_fn=lambda sleep_summary: sleep_summary.min_heart_rate,
        translation_key="minimum_heart_rate",
        native_unit_of_measurement=UOM_BEATS_PER_MINUTE,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_light_duration_seconds",
        value_fn=lambda sleep_summary: sleep_summary.light_sleep_duration,
        translation_key="light_sleep",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.HOURS,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_rem_duration_seconds",
        value_fn=lambda sleep_summary: sleep_summary.rem_sleep_duration,
        translation_key="rem_sleep",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.HOURS,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_respiratory_average_bpm",
        value_fn=lambda sleep_summary: sleep_summary.average_respiration_rate,
        translation_key="average_respiratory_rate",
        native_unit_of_measurement=UOM_BREATHS_PER_MINUTE,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_respiratory_max_bpm",
        value_fn=lambda sleep_summary: sleep_summary.max_respiration_rate,
        translation_key="maximum_respiratory_rate",
        native_unit_of_measurement=UOM_BREATHS_PER_MINUTE,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_respiratory_min_bpm",
        value_fn=lambda sleep_summary: sleep_summary.min_respiration_rate,
        translation_key="minimum_respiratory_rate",
        native_unit_of_measurement=UOM_BREATHS_PER_MINUTE,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_score",
        value_fn=lambda sleep_summary: sleep_summary.sleep_score,
        translation_key="sleep_score",
        native_unit_of_measurement=SCORE_POINTS,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_snoring",
        value_fn=lambda sleep_summary: sleep_summary.snoring,
        translation_key="snoring",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.MINUTES,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_snoring_eposode_count",
        value_fn=lambda sleep_summary: sleep_summary.snoring_count,
        translation_key="snoring_episode_count",
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_wakeup_count",
        value_fn=lambda sleep_summary: sleep_summary.wake_up_count,
        translation_key="wakeup_count",
        native_unit_of_measurement=UOM_FREQUENCY,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
    WithingsSleepSensorEntityDescription(
        key="sleep_wakeup_duration_seconds",
        value_fn=lambda sleep_summary: sleep_summary.total_time_awake,
        translation_key="wakeup_time",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.HOURS,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.MEASUREMENT,
        entity_registry_enabled_default=False,
    ),
]


@dataclass(frozen=True, kw_only=True)
class WithingsActivitySensorEntityDescription(SensorEntityDescription):
    """Immutable class for describing withings data."""

    value_fn: Callable[[Activity], StateType]


ACTIVITY_SENSORS = [
    WithingsActivitySensorEntityDescription(
        key="activity_steps_today",
        value_fn=lambda activity: activity.steps,
        translation_key="activity_steps_today",
        native_unit_of_measurement="steps",
        state_class=SensorStateClass.TOTAL,
    ),
    WithingsActivitySensorEntityDescription(
        key="activity_distance_today",
        value_fn=lambda activity: activity.distance,
        translation_key="activity_distance_today",
        suggested_display_precision=0,
        native_unit_of_measurement=UnitOfLength.METERS,
        device_class=SensorDeviceClass.DISTANCE,
        state_class=SensorStateClass.TOTAL,
    ),
    WithingsActivitySensorEntityDescription(
        key="activity_floors_climbed_today",
        value_fn=lambda activity: activity.elevation,
        translation_key="activity_elevation_today",
        native_unit_of_measurement=UnitOfLength.METERS,
        device_class=SensorDeviceClass.DISTANCE,
        state_class=SensorStateClass.TOTAL,
    ),
    WithingsActivitySensorEntityDescription(
        key="activity_soft_duration_today",
        value_fn=lambda activity: activity.soft_activity,
        translation_key="activity_soft_duration_today",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.MINUTES,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.TOTAL,
        entity_registry_enabled_default=False,
    ),
    WithingsActivitySensorEntityDescription(
        key="activity_moderate_duration_today",
        value_fn=lambda activity: activity.moderate_activity,
        translation_key="activity_moderate_duration_today",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.MINUTES,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.TOTAL,
        entity_registry_enabled_default=False,
    ),
    WithingsActivitySensorEntityDescription(
        key="activity_intense_duration_today",
        value_fn=lambda activity: activity.intense_activity,
        translation_key="activity_intense_duration_today",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.MINUTES,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.TOTAL,
        entity_registry_enabled_default=False,
    ),
    WithingsActivitySensorEntityDescription(
        key="activity_active_duration_today",
        value_fn=lambda activity: activity.total_time_active,
        translation_key="activity_active_duration_today",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.HOURS,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.TOTAL,
    ),
    WithingsActivitySensorEntityDescription(
        key="activity_active_calories_burnt_today",
        value_fn=lambda activity: activity.active_calories_burnt,
        suggested_display_precision=1,
        translation_key="activity_active_calories_burnt_today",
        native_unit_of_measurement="calories",
        state_class=SensorStateClass.TOTAL,
    ),
    WithingsActivitySensorEntityDescription(
        key="activity_total_calories_burnt_today",
        value_fn=lambda activity: activity.total_calories_burnt,
        suggested_display_precision=1,
        translation_key="activity_total_calories_burnt_today",
        native_unit_of_measurement="calories",
        state_class=SensorStateClass.TOTAL,
    ),
]


STEP_GOAL = "steps"
SLEEP_GOAL = "sleep"
WEIGHT_GOAL = "weight"


@dataclass(frozen=True, kw_only=True)
class WithingsGoalsSensorEntityDescription(SensorEntityDescription):
    """Immutable class for describing withings data."""

    value_fn: Callable[[Goals], StateType]


GOALS_SENSORS: dict[str, WithingsGoalsSensorEntityDescription] = {
    STEP_GOAL: WithingsGoalsSensorEntityDescription(
        key="step_goal",
        value_fn=lambda goals: goals.steps,
        translation_key="step_goal",
        native_unit_of_measurement="steps",
        state_class=SensorStateClass.MEASUREMENT,
    ),
    SLEEP_GOAL: WithingsGoalsSensorEntityDescription(
        key="sleep_goal",
        value_fn=lambda goals: goals.sleep,
        translation_key="sleep_goal",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.HOURS,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    WEIGHT_GOAL: WithingsGoalsSensorEntityDescription(
        key="weight_goal",
        value_fn=lambda goals: goals.weight,
        translation_key="weight_goal",
        native_unit_of_measurement=UnitOfMass.KILOGRAMS,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
    ),
}


@dataclass(frozen=True, kw_only=True)
class WithingsWorkoutSensorEntityDescription(SensorEntityDescription):
    """Immutable class for describing withings data."""

    value_fn: Callable[[Workout], StateType]


_WORKOUT_CATEGORY = [
    workout_category.name.lower() for workout_category in WorkoutCategory
]


WORKOUT_SENSORS = [
    WithingsWorkoutSensorEntityDescription(
        key="workout_type",
        value_fn=lambda workout: workout.category.name.lower(),
        device_class=SensorDeviceClass.ENUM,
        translation_key="workout_type",
        options=_WORKOUT_CATEGORY,
    ),
    WithingsWorkoutSensorEntityDescription(
        key="workout_active_calories_burnt",
        value_fn=lambda workout: workout.active_calories_burnt,
        translation_key="workout_active_calories_burnt",
        suggested_display_precision=1,
        native_unit_of_measurement="calories",
    ),
    WithingsWorkoutSensorEntityDescription(
        key="workout_distance",
        value_fn=lambda workout: workout.distance,
        translation_key="workout_distance",
        device_class=SensorDeviceClass.DISTANCE,
        native_unit_of_measurement=UnitOfLength.METERS,
        suggested_display_precision=0,
    ),
    WithingsWorkoutSensorEntityDescription(
        key="workout_floors_climbed",
        value_fn=lambda workout: workout.elevation,
        translation_key="workout_elevation",
        native_unit_of_measurement=UnitOfLength.METERS,
        device_class=SensorDeviceClass.DISTANCE,
    ),
    WithingsWorkoutSensorEntityDescription(
        key="workout_intensity",
        value_fn=lambda workout: workout.intensity,
        translation_key="workout_intensity",
    ),
    WithingsWorkoutSensorEntityDescription(
        key="workout_pause_duration",
        value_fn=lambda workout: workout.pause_duration or 0,
        translation_key="workout_pause_duration",
        device_class=SensorDeviceClass.DURATION,
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.MINUTES,
    ),
    WithingsWorkoutSensorEntityDescription(
        key="workout_duration",
        value_fn=lambda workout: (
            workout.end_date - workout.start_date
        ).total_seconds(),
        translation_key="workout_duration",
        device_class=SensorDeviceClass.DURATION,
        native_unit_of_measurement=UnitOfTime.SECONDS,
        suggested_unit_of_measurement=UnitOfTime.MINUTES,
    ),
]


@dataclass(frozen=True, kw_only=True)
class WithingsDeviceSensorEntityDescription(SensorEntityDescription):
    """Immutable class for describing withings data."""

    value_fn: Callable[[Device], StateType]


DEVICE_SENSORS = [
    WithingsDeviceSensorEntityDescription(
        key="battery",
        translation_key="battery",
        options=["low", "medium", "high"],
        device_class=SensorDeviceClass.ENUM,
        value_fn=lambda device: device.battery,
    )
]


def get_current_goals(goals: Goals) -> set[str]:
    """Return a list of present goals."""
    result = set()
    for goal in (STEP_GOAL, SLEEP_GOAL, WEIGHT_GOAL):
        if getattr(goals, goal):
            result.add(goal)
    return result


async def async_setup_entry(
    hass: HomeAssistant,
    entry: WithingsConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the sensor config entry."""
    ent_reg = er.async_get(hass)

    withings_data = entry.runtime_data

    measurement_coordinator = withings_data.measurement_coordinator

    entities: list[SensorEntity] = []
    entities.extend(
        WithingsMeasurementSensor(measurement_coordinator, description)
        for measurement_type in measurement_coordinator.data
        if (description := get_measurement_description(measurement_type)) is not None
    )

    current_measurement_types = set(measurement_coordinator.data)

    def _async_measurement_listener() -> None:
        """Listen for new measurements and add sensors if they did not exist."""
        received_measurement_types = set(measurement_coordinator.data)
        new_measurement_types = received_measurement_types - current_measurement_types
        if new_measurement_types:
            current_measurement_types.update(new_measurement_types)
            async_add_entities(
                WithingsMeasurementSensor(measurement_coordinator, description)
                for measurement_type in new_measurement_types
                if (description := get_measurement_description(measurement_type))
                is not None
            )

    measurement_coordinator.async_add_listener(_async_measurement_listener)

    goals_coordinator = withings_data.goals_coordinator

    current_goals = get_current_goals(goals_coordinator.data)

    entities.extend(
        WithingsGoalsSensor(goals_coordinator, GOALS_SENSORS[goal])
        for goal in current_goals
    )

    def _async_goals_listener() -> None:
        """Listen for new goals and add sensors if they did not exist."""
        received_goals = get_current_goals(goals_coordinator.data)
        new_goals = received_goals - current_goals
        if new_goals:
            current_goals.update(new_goals)
            async_add_entities(
                WithingsGoalsSensor(goals_coordinator, GOALS_SENSORS[goal])
                for goal in new_goals
            )

    goals_coordinator.async_add_listener(_async_goals_listener)

    activity_coordinator = withings_data.activity_coordinator

    activity_entities_setup_before = ent_reg.async_get_entity_id(
        Platform.SENSOR, DOMAIN, f"withings_{entry.unique_id}_activity_steps_today"
    )

    if activity_coordinator.data is not None or activity_entities_setup_before:
        entities.extend(
            WithingsActivitySensor(activity_coordinator, attribute)
            for attribute in ACTIVITY_SENSORS
        )
    else:
        remove_activity_listener: Callable[[], None]

        def _async_add_activity_entities() -> None:
            """Add activity entities."""
            if activity_coordinator.data is not None:
                async_add_entities(
                    WithingsActivitySensor(activity_coordinator, attribute)
                    for attribute in ACTIVITY_SENSORS
                )
                remove_activity_listener()

        remove_activity_listener = activity_coordinator.async_add_listener(
            _async_add_activity_entities
        )

    sleep_coordinator = withings_data.sleep_coordinator

    sleep_entities_setup_before = ent_reg.async_get_entity_id(
        Platform.SENSOR,
        DOMAIN,
        f"withings_{entry.unique_id}_sleep_deep_duration_seconds",
    )

    if sleep_coordinator.data is not None or sleep_entities_setup_before:
        entities.extend(
            WithingsSleepSensor(sleep_coordinator, attribute)
            for attribute in SLEEP_SENSORS
        )
    else:
        remove_sleep_listener: Callable[[], None]

        def _async_add_sleep_entities() -> None:
            """Add sleep entities."""
            if sleep_coordinator.data is not None:
                async_add_entities(
                    WithingsSleepSensor(sleep_coordinator, attribute)
                    for attribute in SLEEP_SENSORS
                )
                remove_sleep_listener()

        remove_sleep_listener = sleep_coordinator.async_add_listener(
            _async_add_sleep_entities
        )

    workout_coordinator = withings_data.workout_coordinator

    workout_entities_setup_before = ent_reg.async_get_entity_id(
        Platform.SENSOR, DOMAIN, f"withings_{entry.unique_id}_workout_type"
    )

    if workout_coordinator.data is not None or workout_entities_setup_before:
        entities.extend(
            WithingsWorkoutSensor(workout_coordinator, attribute)
            for attribute in WORKOUT_SENSORS
        )
    else:
        remove_workout_listener: Callable[[], None]

        def _async_add_workout_entities() -> None:
            """Add workout entities."""
            if workout_coordinator.data is not None:
                async_add_entities(
                    WithingsWorkoutSensor(workout_coordinator, attribute)
                    for attribute in WORKOUT_SENSORS
                )
                remove_workout_listener()

        remove_workout_listener = workout_coordinator.async_add_listener(
            _async_add_workout_entities
        )

    device_coordinator = withings_data.device_coordinator

    current_devices: set[str] = set()

    def _async_device_listener() -> None:
        """Add device entities."""
        received_devices = set(device_coordinator.data)
        new_devices = received_devices - current_devices
        old_devices = current_devices - received_devices
        if new_devices:
            device_registry = dr.async_get(hass)
            for device_id in new_devices:
                if device := device_registry.async_get_device({(DOMAIN, device_id)}):
                    if any(
                        (
                            config_entry := hass.config_entries.async_get_entry(
                                config_entry_id
                            )
                        )
                        and config_entry.state == ConfigEntryState.LOADED
                        for config_entry_id in device.config_entries
                    ):
                        continue
                async_add_entities(
                    WithingsDeviceSensor(device_coordinator, description, device_id)
                    for description in DEVICE_SENSORS
                )
                current_devices.add(device_id)

        if old_devices:
            device_registry = dr.async_get(hass)
            for device_id in old_devices:
                if device := device_registry.async_get_device({(DOMAIN, device_id)}):
                    device_registry.async_update_device(
                        device.id, remove_config_entry_id=entry.entry_id
                    )
                    current_devices.remove(device_id)

    device_coordinator.async_add_listener(_async_device_listener)

    _async_device_listener()

    if not entities:
        LOGGER.warning(
            "No data found for Withings entry %s, sensors will be added when new data is available",
            entry.title,
        )

    async_add_entities(entities)


class WithingsSensor[
    _T: WithingsDataUpdateCoordinator[Any],
    _ED: SensorEntityDescription,
](WithingsEntity[_T], SensorEntity):
    """Implementation of a Withings sensor."""

    entity_description: _ED

    def __init__(
        self,
        coordinator: _T,
        entity_description: _ED,
    ) -> None:
        """Initialize sensor."""
        super().__init__(coordinator, entity_description.key)
        self.entity_description = entity_description


class WithingsMeasurementSensor(
    WithingsSensor[
        WithingsMeasurementDataUpdateCoordinator,
        WithingsMeasurementSensorEntityDescription,
    ]
):
    """Implementation of a Withings measurement sensor."""

    @property
    def native_value(self) -> float:
        """Return the state of the entity."""
        return self.coordinator.data[
            (
                self.entity_description.measurement_type,
                self.entity_description.measurement_position,
            )
        ]

    @property
    def available(self) -> bool:
        """Return if the sensor is available."""
        return (
            super().available
            and (
                self.entity_description.measurement_type,
                self.entity_description.measurement_position,
            )
            in self.coordinator.data
        )


class WithingsSleepSensor(
    WithingsSensor[
        WithingsSleepDataUpdateCoordinator,
        WithingsSleepSensorEntityDescription,
    ]
):
    """Implementation of a Withings sleep sensor."""

    @property
    def native_value(self) -> StateType:
        """Return the state of the entity."""
        if not self.coordinator.data:
            return None
        return self.entity_description.value_fn(self.coordinator.data)


class WithingsGoalsSensor(
    WithingsSensor[
        WithingsGoalsDataUpdateCoordinator,
        WithingsGoalsSensorEntityDescription,
    ]
):
    """Implementation of a Withings goals sensor."""

    @property
    def native_value(self) -> StateType:
        """Return the state of the entity."""
        assert self.coordinator.data
        return self.entity_description.value_fn(self.coordinator.data)


class WithingsActivitySensor(
    WithingsSensor[
        WithingsActivityDataUpdateCoordinator,
        WithingsActivitySensorEntityDescription,
    ]
):
    """Implementation of a Withings activity sensor."""

    @property
    def native_value(self) -> StateType:
        """Return the state of the entity."""
        if not self.coordinator.data:
            return None
        return self.entity_description.value_fn(self.coordinator.data)

    @property
    def last_reset(self) -> datetime:
        """These values reset every day."""
        return dt_util.start_of_local_day()


class WithingsWorkoutSensor(
    WithingsSensor[
        WithingsWorkoutDataUpdateCoordinator,
        WithingsWorkoutSensorEntityDescription,
    ]
):
    """Implementation of a Withings workout sensor."""

    @property
    def native_value(self) -> StateType:
        """Return the state of the entity."""
        if not self.coordinator.data:
            return None
        return self.entity_description.value_fn(self.coordinator.data)


class WithingsDeviceSensor(WithingsDeviceEntity, SensorEntity):
    """Implementation of a Withings workout sensor."""

    entity_description: WithingsDeviceSensorEntityDescription

    def __init__(
        self,
        coordinator: WithingsDeviceDataUpdateCoordinator,
        entity_description: WithingsDeviceSensorEntityDescription,
        device_id: str,
    ) -> None:
        """Initialize sensor."""
        super().__init__(coordinator, device_id, entity_description.key)
        self.entity_description = entity_description

    @property
    def native_value(self) -> StateType:
        """Return the state of the entity."""
        return self.entity_description.value_fn(self.device)
