"""Template platform that aggregates meteorological data."""
from __future__ import annotations

from dataclasses import asdict, dataclass
from functools import partial
from typing import Any, Literal, Self

import voluptuous as vol

from homeassistant.components.weather import (
    ATTR_CONDITION_CLEAR_NIGHT,
    ATTR_CONDITION_CLOUDY,
    ATTR_CONDITION_EXCEPTIONAL,
    ATTR_CONDITION_FOG,
    ATTR_CONDITION_HAIL,
    ATTR_CONDITION_LIGHTNING,
    ATTR_CONDITION_LIGHTNING_RAINY,
    ATTR_CONDITION_PARTLYCLOUDY,
    ATTR_CONDITION_POURING,
    ATTR_CONDITION_RAINY,
    ATTR_CONDITION_SNOWY,
    ATTR_CONDITION_SNOWY_RAINY,
    ATTR_CONDITION_SUNNY,
    ATTR_CONDITION_WINDY,
    ATTR_CONDITION_WINDY_VARIANT,
    DOMAIN as WEATHER_DOMAIN,
    ENTITY_ID_FORMAT,
    Forecast,
    WeatherEntity,
    WeatherEntityFeature,
)
from homeassistant.const import (
    CONF_NAME,
    CONF_TEMPERATURE_UNIT,
    CONF_UNIQUE_ID,
    STATE_UNAVAILABLE,
    STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.unit_conversion import (
    DistanceConverter,
    PressureConverter,
    SpeedConverter,
    TemperatureConverter,
)

from .coordinator import TriggerUpdateCoordinator
from .template_entity import TemplateEntity, rewrite_common_legacy_to_modern_conf
from .trigger_entity import TriggerEntity

CHECK_FORECAST_KEYS = (
    set()
    .union(Forecast.__annotations__.keys())
    # Manually add the forecast resulting attributes that only exists
    #  as native_* in the Forecast definition
    .union(("apparent_temperature", "wind_gust_speed", "dew_point"))
)

CONDITION_CLASSES = {
    ATTR_CONDITION_CLEAR_NIGHT,
    ATTR_CONDITION_CLOUDY,
    ATTR_CONDITION_FOG,
    ATTR_CONDITION_HAIL,
    ATTR_CONDITION_LIGHTNING,
    ATTR_CONDITION_LIGHTNING_RAINY,
    ATTR_CONDITION_PARTLYCLOUDY,
    ATTR_CONDITION_POURING,
    ATTR_CONDITION_RAINY,
    ATTR_CONDITION_SNOWY,
    ATTR_CONDITION_SNOWY_RAINY,
    ATTR_CONDITION_SUNNY,
    ATTR_CONDITION_WINDY,
    ATTR_CONDITION_WINDY_VARIANT,
    ATTR_CONDITION_EXCEPTIONAL,
}

CONF_WEATHER = "weather"
CONF_TEMPERATURE_TEMPLATE = "temperature_template"
CONF_HUMIDITY_TEMPLATE = "humidity_template"
CONF_CONDITION_TEMPLATE = "condition_template"
CONF_ATTRIBUTION_TEMPLATE = "attribution_template"
CONF_PRESSURE_TEMPLATE = "pressure_template"
CONF_WIND_SPEED_TEMPLATE = "wind_speed_template"
CONF_WIND_BEARING_TEMPLATE = "wind_bearing_template"
CONF_OZONE_TEMPLATE = "ozone_template"
CONF_VISIBILITY_TEMPLATE = "visibility_template"
CONF_FORECAST_TEMPLATE = "forecast_template"
CONF_FORECAST_DAILY_TEMPLATE = "forecast_daily_template"
CONF_FORECAST_HOURLY_TEMPLATE = "forecast_hourly_template"
CONF_FORECAST_TWICE_DAILY_TEMPLATE = "forecast_twice_daily_template"
CONF_PRESSURE_UNIT = "pressure_unit"
CONF_WIND_SPEED_UNIT = "wind_speed_unit"
CONF_VISIBILITY_UNIT = "visibility_unit"
CONF_PRECIPITATION_UNIT = "precipitation_unit"
CONF_WIND_GUST_SPEED_TEMPLATE = "wind_gust_speed_template"
CONF_CLOUD_COVERAGE_TEMPLATE = "cloud_coverage_template"
CONF_DEW_POINT_TEMPLATE = "dew_point_template"
CONF_APPARENT_TEMPERATURE_TEMPLATE = "apparent_temperature_template"

WEATHER_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_NAME): cv.template,
        vol.Required(CONF_CONDITION_TEMPLATE): cv.template,
        vol.Required(CONF_TEMPERATURE_TEMPLATE): cv.template,
        vol.Required(CONF_HUMIDITY_TEMPLATE): cv.template,
        vol.Optional(CONF_ATTRIBUTION_TEMPLATE): cv.template,
        vol.Optional(CONF_PRESSURE_TEMPLATE): cv.template,
        vol.Optional(CONF_WIND_SPEED_TEMPLATE): cv.template,
        vol.Optional(CONF_WIND_BEARING_TEMPLATE): cv.template,
        vol.Optional(CONF_OZONE_TEMPLATE): cv.template,
        vol.Optional(CONF_VISIBILITY_TEMPLATE): cv.template,
        vol.Optional(CONF_FORECAST_TEMPLATE): cv.template,
        vol.Optional(CONF_FORECAST_DAILY_TEMPLATE): cv.template,
        vol.Optional(CONF_FORECAST_HOURLY_TEMPLATE): cv.template,
        vol.Optional(CONF_FORECAST_TWICE_DAILY_TEMPLATE): cv.template,
        vol.Optional(CONF_UNIQUE_ID): cv.string,
        vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(TemperatureConverter.VALID_UNITS),
        vol.Optional(CONF_PRESSURE_UNIT): vol.In(PressureConverter.VALID_UNITS),
        vol.Optional(CONF_WIND_SPEED_UNIT): vol.In(SpeedConverter.VALID_UNITS),
        vol.Optional(CONF_VISIBILITY_UNIT): vol.In(DistanceConverter.VALID_UNITS),
        vol.Optional(CONF_PRECIPITATION_UNIT): vol.In(DistanceConverter.VALID_UNITS),
        vol.Optional(CONF_WIND_GUST_SPEED_TEMPLATE): cv.template,
        vol.Optional(CONF_CLOUD_COVERAGE_TEMPLATE): cv.template,
        vol.Optional(CONF_DEW_POINT_TEMPLATE): cv.template,
        vol.Optional(CONF_APPARENT_TEMPERATURE_TEMPLATE): cv.template,
    }
)

PLATFORM_SCHEMA = vol.All(
    cv.deprecated(CONF_FORECAST_TEMPLATE),
    PLATFORM_SCHEMA.extend(WEATHER_SCHEMA.schema),
)


async def async_setup_platform(
    hass: HomeAssistant,
    config: ConfigType,
    async_add_entities: AddEntitiesCallback,
    discovery_info: DiscoveryInfoType | None = None,
) -> None:
    """Set up the Template weather."""
    if discovery_info and "coordinator" in discovery_info:
        async_add_entities(
            TriggerWeatherEntity(hass, discovery_info["coordinator"], config)
            for config in discovery_info["entities"]
        )
        return

    config = rewrite_common_legacy_to_modern_conf(config)
    unique_id = config.get(CONF_UNIQUE_ID)

    async_add_entities(
        [
            WeatherTemplate(
                hass,
                config,
                unique_id,
            )
        ]
    )


class WeatherTemplate(TemplateEntity, WeatherEntity):
    """Representation of a weather condition."""

    _attr_should_poll = False

    def __init__(
        self,
        hass: HomeAssistant,
        config: ConfigType,
        unique_id: str | None,
    ) -> None:
        """Initialize the Template weather."""
        super().__init__(hass, config=config, unique_id=unique_id)

        name = self._attr_name
        self._condition_template = config[CONF_CONDITION_TEMPLATE]
        self._temperature_template = config[CONF_TEMPERATURE_TEMPLATE]
        self._humidity_template = config[CONF_HUMIDITY_TEMPLATE]
        self._attribution_template = config.get(CONF_ATTRIBUTION_TEMPLATE)
        self._pressure_template = config.get(CONF_PRESSURE_TEMPLATE)
        self._wind_speed_template = config.get(CONF_WIND_SPEED_TEMPLATE)
        self._wind_bearing_template = config.get(CONF_WIND_BEARING_TEMPLATE)
        self._ozone_template = config.get(CONF_OZONE_TEMPLATE)
        self._visibility_template = config.get(CONF_VISIBILITY_TEMPLATE)
        self._forecast_template = config.get(CONF_FORECAST_TEMPLATE)
        self._forecast_daily_template = config.get(CONF_FORECAST_DAILY_TEMPLATE)
        self._forecast_hourly_template = config.get(CONF_FORECAST_HOURLY_TEMPLATE)
        self._forecast_twice_daily_template = config.get(
            CONF_FORECAST_TWICE_DAILY_TEMPLATE
        )
        self._wind_gust_speed_template = config.get(CONF_WIND_GUST_SPEED_TEMPLATE)
        self._cloud_coverage_template = config.get(CONF_CLOUD_COVERAGE_TEMPLATE)
        self._dew_point_template = config.get(CONF_DEW_POINT_TEMPLATE)
        self._apparent_temperature_template = config.get(
            CONF_APPARENT_TEMPERATURE_TEMPLATE
        )

        self._attr_native_precipitation_unit = config.get(CONF_PRECIPITATION_UNIT)
        self._attr_native_pressure_unit = config.get(CONF_PRESSURE_UNIT)
        self._attr_native_temperature_unit = config.get(CONF_TEMPERATURE_UNIT)
        self._attr_native_visibility_unit = config.get(CONF_VISIBILITY_UNIT)
        self._attr_native_wind_speed_unit = config.get(CONF_WIND_SPEED_UNIT)

        self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, name, hass=hass)

        self._condition = None
        self._temperature = None
        self._humidity = None
        self._attribution = None
        self._pressure = None
        self._wind_speed = None
        self._wind_bearing = None
        self._ozone = None
        self._visibility = None
        self._wind_gust_speed = None
        self._cloud_coverage = None
        self._dew_point = None
        self._apparent_temperature = None
        self._forecast: list[Forecast] = []
        self._forecast_daily: list[Forecast] = []
        self._forecast_hourly: list[Forecast] = []
        self._forecast_twice_daily: list[Forecast] = []

        self._attr_supported_features = 0
        if self._forecast_daily_template:
            self._attr_supported_features |= WeatherEntityFeature.FORECAST_DAILY
        if self._forecast_hourly_template:
            self._attr_supported_features |= WeatherEntityFeature.FORECAST_HOURLY
        if self._forecast_twice_daily_template:
            self._attr_supported_features |= WeatherEntityFeature.FORECAST_TWICE_DAILY

    @property
    def condition(self) -> str | None:
        """Return the current condition."""
        return self._condition

    @property
    def native_temperature(self) -> float | None:
        """Return the temperature."""
        return self._temperature

    @property
    def humidity(self) -> float | None:
        """Return the humidity."""
        return self._humidity

    @property
    def native_wind_speed(self) -> float | None:
        """Return the wind speed."""
        return self._wind_speed

    @property
    def wind_bearing(self) -> float | str | None:
        """Return the wind bearing."""
        return self._wind_bearing

    @property
    def ozone(self) -> float | None:
        """Return the ozone level."""
        return self._ozone

    @property
    def native_visibility(self) -> float | None:
        """Return the visibility."""
        return self._visibility

    @property
    def native_pressure(self) -> float | None:
        """Return the air pressure."""
        return self._pressure

    @property
    def native_wind_gust_speed(self) -> float | None:
        """Return the wind gust speed."""
        return self._wind_gust_speed

    @property
    def cloud_coverage(self) -> float | None:
        """Return the cloud coverage."""
        return self._cloud_coverage

    @property
    def native_dew_point(self) -> float | None:
        """Return the dew point."""
        return self._dew_point

    @property
    def native_apparent_temperature(self) -> float | None:
        """Return the apparent temperature."""
        return self._apparent_temperature

    @property
    def forecast(self) -> list[Forecast]:
        """Return the forecast."""
        return self._forecast

    async def async_forecast_daily(self) -> list[Forecast]:
        """Return the daily forecast in native units."""
        return self._forecast_daily

    async def async_forecast_hourly(self) -> list[Forecast]:
        """Return the daily forecast in native units."""
        return self._forecast_hourly

    async def async_forecast_twice_daily(self) -> list[Forecast]:
        """Return the daily forecast in native units."""
        return self._forecast_twice_daily

    @property
    def attribution(self) -> str | None:
        """Return the attribution."""
        if self._attribution is None:
            return "Powered by Home Assistant"
        return self._attribution

    @callback
    def _async_setup_templates(self) -> None:
        """Set up templates."""

        if self._condition_template:
            self.add_template_attribute(
                "_condition",
                self._condition_template,
                lambda condition: condition if condition in CONDITION_CLASSES else None,
            )
        if self._temperature_template:
            self.add_template_attribute(
                "_temperature",
                self._temperature_template,
            )
        if self._humidity_template:
            self.add_template_attribute(
                "_humidity",
                self._humidity_template,
            )
        if self._attribution_template:
            self.add_template_attribute(
                "_attribution",
                self._attribution_template,
            )
        if self._pressure_template:
            self.add_template_attribute(
                "_pressure",
                self._pressure_template,
            )
        if self._wind_speed_template:
            self.add_template_attribute(
                "_wind_speed",
                self._wind_speed_template,
            )
        if self._wind_bearing_template:
            self.add_template_attribute(
                "_wind_bearing",
                self._wind_bearing_template,
            )
        if self._ozone_template:
            self.add_template_attribute(
                "_ozone",
                self._ozone_template,
            )
        if self._visibility_template:
            self.add_template_attribute(
                "_visibility",
                self._visibility_template,
            )
        if self._wind_gust_speed_template:
            self.add_template_attribute(
                "_wind_gust_speed",
                self._wind_gust_speed_template,
            )
        if self._cloud_coverage_template:
            self.add_template_attribute(
                "_cloud_coverage",
                self._cloud_coverage_template,
            )
        if self._dew_point_template:
            self.add_template_attribute(
                "_dew_point",
                self._dew_point_template,
            )
        if self._apparent_temperature_template:
            self.add_template_attribute(
                "_apparent_temperature",
                self._apparent_temperature_template,
            )
        if self._forecast_template:
            self.add_template_attribute(
                "_forecast",
                self._forecast_template,
            )

        if self._forecast_daily_template:
            self.add_template_attribute(
                "_forecast_daily",
                self._forecast_daily_template,
                on_update=partial(self._update_forecast, "daily"),
                validator=partial(self._validate_forecast, "daily"),
            )
        if self._forecast_hourly_template:
            self.add_template_attribute(
                "_forecast_hourly",
                self._forecast_hourly_template,
                on_update=partial(self._update_forecast, "hourly"),
                validator=partial(self._validate_forecast, "hourly"),
            )
        if self._forecast_twice_daily_template:
            self.add_template_attribute(
                "_forecast_twice_daily",
                self._forecast_twice_daily_template,
                on_update=partial(self._update_forecast, "twice_daily"),
                validator=partial(self._validate_forecast, "twice_daily"),
            )

        super()._async_setup_templates()

    @callback
    def _update_forecast(
        self,
        forecast_type: Literal["daily", "hourly", "twice_daily"],
        result: list[Forecast] | TemplateError,
    ) -> None:
        """Save template result and trigger forecast listener."""
        attr_result = None if isinstance(result, TemplateError) else result
        setattr(self, f"_forecast_{forecast_type}", attr_result)
        self.hass.create_task(self.async_update_listeners([forecast_type]))

    @callback
    def _validate_forecast(
        self,
        forecast_type: Literal["daily", "hourly", "twice_daily"],
        result: Any,
    ) -> list[Forecast] | None:
        """Validate the forecasts."""
        if result is None:
            return None

        if not isinstance(result, list):
            raise vol.Invalid(
                "Forecasts is not a list, see Weather documentation https://www.home-assistant.io/integrations/weather/"
            )
        for forecast in result:
            if not isinstance(forecast, dict):
                raise vol.Invalid(
                    "Forecast in list is not a dict, see Weather documentation https://www.home-assistant.io/integrations/weather/"
                )
            diff_result = set().union(forecast.keys()).difference(CHECK_FORECAST_KEYS)
            if diff_result:
                raise vol.Invalid(
                    f"Only valid keys in Forecast are allowed, unallowed keys: ({diff_result}), "
                    "see Weather documentation https://www.home-assistant.io/integrations/weather/"
                )
            if forecast_type == "twice_daily" and "is_daytime" not in forecast:
                raise vol.Invalid(
                    "`is_daytime` is missing in twice_daily forecast, see Weather documentation https://www.home-assistant.io/integrations/weather/"
                )
            if "datetime" not in forecast:
                raise vol.Invalid(
                    "`datetime` is required in forecasts, see Weather documentation https://www.home-assistant.io/integrations/weather/"
                )
            continue
        return result


@dataclass(kw_only=True)
class WeatherExtraStoredData(ExtraStoredData):
    """Object to hold extra stored data."""

    last_apparent_temperature: float | None
    last_cloud_coverage: int | None
    last_dew_point: float | None
    last_humidity: float | None
    last_ozone: float | None
    last_pressure: float | None
    last_temperature: float | None
    last_visibility: float | None
    last_wind_bearing: float | str | None
    last_wind_gust_speed: float | None
    last_wind_speed: float | None

    def as_dict(self) -> dict[str, Any]:
        """Return a dict representation of the event data."""
        return asdict(self)

    @classmethod
    def from_dict(cls, restored: dict[str, Any]) -> Self | None:
        """Initialize a stored event state from a dict."""
        try:
            return cls(
                last_apparent_temperature=restored["last_apparent_temperature"],
                last_cloud_coverage=restored["last_cloud_coverage"],
                last_dew_point=restored["last_dew_point"],
                last_humidity=restored["last_humidity"],
                last_ozone=restored["last_ozone"],
                last_pressure=restored["last_pressure"],
                last_temperature=restored["last_temperature"],
                last_visibility=restored["last_visibility"],
                last_wind_bearing=restored["last_wind_bearing"],
                last_wind_gust_speed=restored["last_wind_gust_speed"],
                last_wind_speed=restored["last_wind_speed"],
            )
        except KeyError:
            return None


class TriggerWeatherEntity(TriggerEntity, WeatherEntity, RestoreEntity):
    """Sensor entity based on trigger data."""

    domain = WEATHER_DOMAIN
    extra_template_keys = (
        CONF_CONDITION_TEMPLATE,
        CONF_TEMPERATURE_TEMPLATE,
        CONF_HUMIDITY_TEMPLATE,
    )

    def __init__(
        self,
        hass: HomeAssistant,
        coordinator: TriggerUpdateCoordinator,
        config: ConfigType,
    ) -> None:
        """Initialize."""
        super().__init__(hass, coordinator, config)
        self._attr_native_precipitation_unit = config.get(CONF_PRECIPITATION_UNIT)
        self._attr_native_pressure_unit = config.get(CONF_PRESSURE_UNIT)
        self._attr_native_temperature_unit = config.get(CONF_TEMPERATURE_UNIT)
        self._attr_native_visibility_unit = config.get(CONF_VISIBILITY_UNIT)
        self._attr_native_wind_speed_unit = config.get(CONF_WIND_SPEED_UNIT)

        self._attr_supported_features = 0
        if config.get(CONF_FORECAST_DAILY_TEMPLATE):
            self._attr_supported_features |= WeatherEntityFeature.FORECAST_DAILY
        if config.get(CONF_FORECAST_HOURLY_TEMPLATE):
            self._attr_supported_features |= WeatherEntityFeature.FORECAST_HOURLY
        if config.get(CONF_FORECAST_TWICE_DAILY_TEMPLATE):
            self._attr_supported_features |= WeatherEntityFeature.FORECAST_TWICE_DAILY

        for key in (
            CONF_APPARENT_TEMPERATURE_TEMPLATE,
            CONF_CLOUD_COVERAGE_TEMPLATE,
            CONF_DEW_POINT_TEMPLATE,
            CONF_FORECAST_DAILY_TEMPLATE,
            CONF_FORECAST_HOURLY_TEMPLATE,
            CONF_FORECAST_TWICE_DAILY_TEMPLATE,
            CONF_OZONE_TEMPLATE,
            CONF_PRESSURE_TEMPLATE,
            CONF_VISIBILITY_TEMPLATE,
            CONF_WIND_BEARING_TEMPLATE,
            CONF_WIND_GUST_SPEED_TEMPLATE,
            CONF_WIND_SPEED_TEMPLATE,
        ):
            if isinstance(config.get(key), template.Template):
                self._to_render_simple.append(key)
                self._parse_result.add(key)

    async def async_added_to_hass(self) -> None:
        """Restore last state."""
        await super().async_added_to_hass()
        if (
            (state := await self.async_get_last_state())
            and state.state is not None
            and state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
            and (weather_data := await self.async_get_last_weather_data())
        ):
            self._rendered[
                CONF_APPARENT_TEMPERATURE_TEMPLATE
            ] = weather_data.last_apparent_temperature
            self._rendered[
                CONF_CLOUD_COVERAGE_TEMPLATE
            ] = weather_data.last_cloud_coverage
            self._rendered[CONF_CONDITION_TEMPLATE] = state.state
            self._rendered[CONF_DEW_POINT_TEMPLATE] = weather_data.last_dew_point
            self._rendered[CONF_HUMIDITY_TEMPLATE] = weather_data.last_humidity
            self._rendered[CONF_OZONE_TEMPLATE] = weather_data.last_ozone
            self._rendered[CONF_PRESSURE_TEMPLATE] = weather_data.last_pressure
            self._rendered[CONF_TEMPERATURE_TEMPLATE] = weather_data.last_temperature
            self._rendered[CONF_VISIBILITY_TEMPLATE] = weather_data.last_visibility
            self._rendered[CONF_WIND_BEARING_TEMPLATE] = weather_data.last_wind_bearing
            self._rendered[
                CONF_WIND_GUST_SPEED_TEMPLATE
            ] = weather_data.last_wind_gust_speed
            self._rendered[CONF_WIND_SPEED_TEMPLATE] = weather_data.last_wind_speed

    @property
    def condition(self) -> str | None:
        """Return the current condition."""
        return self._rendered.get(CONF_CONDITION_TEMPLATE)

    @property
    def native_temperature(self) -> float | None:
        """Return the temperature."""
        return vol.Any(vol.Coerce(float), None)(
            self._rendered.get(CONF_TEMPERATURE_TEMPLATE)
        )

    @property
    def humidity(self) -> float | None:
        """Return the humidity."""
        return vol.Any(vol.Coerce(float), None)(
            self._rendered.get(CONF_HUMIDITY_TEMPLATE)
        )

    @property
    def native_wind_speed(self) -> float | None:
        """Return the wind speed."""
        return vol.Any(vol.Coerce(float), None)(
            self._rendered.get(CONF_WIND_SPEED_TEMPLATE)
        )

    @property
    def wind_bearing(self) -> float | str | None:
        """Return the wind bearing."""
        return vol.Any(vol.Coerce(float), vol.Coerce(str), None)(
            self._rendered.get(CONF_WIND_BEARING_TEMPLATE)
        )

    @property
    def ozone(self) -> float | None:
        """Return the ozone level."""
        return vol.Any(vol.Coerce(float), None)(
            self._rendered.get(CONF_OZONE_TEMPLATE),
        )

    @property
    def native_visibility(self) -> float | None:
        """Return the visibility."""
        return vol.Any(vol.Coerce(float), None)(
            self._rendered.get(CONF_VISIBILITY_TEMPLATE)
        )

    @property
    def native_pressure(self) -> float | None:
        """Return the air pressure."""
        return vol.Any(vol.Coerce(float), None)(
            self._rendered.get(CONF_PRESSURE_TEMPLATE)
        )

    @property
    def native_wind_gust_speed(self) -> float | None:
        """Return the wind gust speed."""
        return vol.Any(vol.Coerce(float), None)(
            self._rendered.get(CONF_WIND_GUST_SPEED_TEMPLATE)
        )

    @property
    def cloud_coverage(self) -> float | None:
        """Return the cloud coverage."""
        return vol.Any(vol.Coerce(float), None)(
            self._rendered.get(CONF_CLOUD_COVERAGE_TEMPLATE)
        )

    @property
    def native_dew_point(self) -> float | None:
        """Return the dew point."""
        return vol.Any(vol.Coerce(float), None)(
            self._rendered.get(CONF_DEW_POINT_TEMPLATE)
        )

    @property
    def native_apparent_temperature(self) -> float | None:
        """Return the apparent temperature."""
        return vol.Any(vol.Coerce(float), None)(
            self._rendered.get(CONF_APPARENT_TEMPERATURE_TEMPLATE)
        )

    async def async_forecast_daily(self) -> list[Forecast]:
        """Return the daily forecast in native units."""
        return vol.Any(vol.Coerce(list), None)(
            self._rendered.get(CONF_FORECAST_DAILY_TEMPLATE)
        )

    async def async_forecast_hourly(self) -> list[Forecast]:
        """Return the daily forecast in native units."""
        return vol.Any(vol.Coerce(list), None)(
            self._rendered.get(CONF_FORECAST_HOURLY_TEMPLATE)
        )

    async def async_forecast_twice_daily(self) -> list[Forecast]:
        """Return the daily forecast in native units."""
        return vol.Any(vol.Coerce(list), None)(
            self._rendered.get(CONF_FORECAST_TWICE_DAILY_TEMPLATE)
        )

    @property
    def extra_restore_state_data(self) -> WeatherExtraStoredData:
        """Return weather specific state data to be restored."""
        return WeatherExtraStoredData(
            last_apparent_temperature=self._rendered.get(
                CONF_APPARENT_TEMPERATURE_TEMPLATE
            ),
            last_cloud_coverage=self._rendered.get(CONF_CLOUD_COVERAGE_TEMPLATE),
            last_dew_point=self._rendered.get(CONF_DEW_POINT_TEMPLATE),
            last_humidity=self._rendered.get(CONF_HUMIDITY_TEMPLATE),
            last_ozone=self._rendered.get(CONF_OZONE_TEMPLATE),
            last_pressure=self._rendered.get(CONF_PRESSURE_TEMPLATE),
            last_temperature=self._rendered.get(CONF_TEMPERATURE_TEMPLATE),
            last_visibility=self._rendered.get(CONF_VISIBILITY_TEMPLATE),
            last_wind_bearing=self._rendered.get(CONF_WIND_BEARING_TEMPLATE),
            last_wind_gust_speed=self._rendered.get(CONF_WIND_GUST_SPEED_TEMPLATE),
            last_wind_speed=self._rendered.get(CONF_WIND_SPEED_TEMPLATE),
        )

    async def async_get_last_weather_data(self) -> WeatherExtraStoredData | None:
        """Restore weather specific state data."""
        if (restored_last_extra_data := await self.async_get_last_extra_data()) is None:
            return None
        return WeatherExtraStoredData.from_dict(restored_last_extra_data.as_dict())