"""Plugwise Select component for Home Assistant."""

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import override

from plugwise import Smile

from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import STATE_ON, EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import (
    DHW_MODE,
    LOCATION,
    SELECT_DHW_MODE,
    SELECT_GATEWAY_MODE,
    SELECT_REGULATION_MODE,
    SELECT_SCHEDULE,
    SELECT_ZONE_PROFILE,
    SelectOptionsType,
    SelectType,
)
from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator
from .entity import PlugwiseEntity
from .util import plugwise_command

PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class PlugwiseSelectEntityDescription(SelectEntityDescription):
    """Class describing Plugwise Select entities."""

    key: SelectType
    options_key: SelectOptionsType
    set_value_fn: Callable[[Smile, str, str, str, int | str | None], Awaitable[None]]


SELECT_TYPES = (
    PlugwiseSelectEntityDescription(
        key=DHW_MODE,
        translation_key=SELECT_DHW_MODE,
        entity_category=EntityCategory.CONFIG,
        options_key="dhw_modes",
        set_value_fn=lambda api, key, appl_id, option, length: api.set_dhw_mode(
            key, appl_id, option, length
        ),
    ),
    PlugwiseSelectEntityDescription(
        key=SELECT_DHW_MODE,
        translation_key=SELECT_DHW_MODE,
        entity_category=EntityCategory.CONFIG,
        options_key="dhw_modes",
        set_value_fn=lambda api, key, appl_or_loc_id, option, state: api.set_select(
            key, appl_or_loc_id, option, state
        ),
    ),
    PlugwiseSelectEntityDescription(
        key=SELECT_GATEWAY_MODE,
        translation_key=SELECT_GATEWAY_MODE,
        entity_category=EntityCategory.CONFIG,
        options_key="gateway_modes",
        set_value_fn=lambda api, key, appl_or_loc_id, option, state: api.set_select(
            key, appl_or_loc_id, option, state
        ),
    ),
    PlugwiseSelectEntityDescription(
        key=SELECT_REGULATION_MODE,
        translation_key=SELECT_REGULATION_MODE,
        entity_category=EntityCategory.CONFIG,
        options_key="regulation_modes",
        set_value_fn=lambda api, key, appl_or_loc_id, option, state: api.set_select(
            key, appl_or_loc_id, option, state
        ),
    ),
    PlugwiseSelectEntityDescription(
        key=SELECT_SCHEDULE,
        translation_key=SELECT_SCHEDULE,
        options_key="available_schedules",
        set_value_fn=lambda api, key, appl_or_loc_id, option, state: api.set_select(
            key, appl_or_loc_id, option, state
        ),
    ),
    PlugwiseSelectEntityDescription(
        key=SELECT_ZONE_PROFILE,
        translation_key=SELECT_ZONE_PROFILE,
        entity_category=EntityCategory.CONFIG,
        options_key="zone_profiles",
        set_value_fn=lambda api, key, appl_or_loc_id, option, state: api.set_select(
            key, appl_or_loc_id, option, state
        ),
    ),
)


async def async_setup_entry(
    hass: HomeAssistant,
    entry: PlugwiseConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the Smile selector from a config entry."""
    coordinator = entry.runtime_data

    @callback
    def _add_entities() -> None:
        """Add entities."""
        if not coordinator.new_devices:
            return

        async_add_entities(
            PlugwiseSelectEntity(coordinator, device_id, description)
            for device_id in coordinator.new_devices
            for description in SELECT_TYPES
            if (device := coordinator.data[device_id]).get(description.key)
            and (
                description.key not in (DHW_MODE, SELECT_DHW_MODE)
                or len(device[description.options_key]) > 2
            )
        )

    _add_entities()
    entry.async_on_unload(coordinator.async_add_listener(_add_entities))


class PlugwiseSelectEntity(PlugwiseEntity, SelectEntity):
    """Represent Smile selector."""

    entity_description: PlugwiseSelectEntityDescription

    def __init__(
        self,
        coordinator: PlugwiseDataUpdateCoordinator,
        device_id: str,
        entity_description: PlugwiseSelectEntityDescription,
    ) -> None:
        """Initialise the selector."""
        super().__init__(coordinator, device_id)
        suffix = entity_description.key
        if entity_description.key == DHW_MODE:
            suffix = SELECT_DHW_MODE
        self._attr_unique_id = f"{device_id}-{suffix}"
        self.entity_description = entity_description

        self._device_or_location = device_id
        if (
            self.entity_description.key in (SELECT_SCHEDULE, SELECT_ZONE_PROFILE)
            and (location := self.device.get(LOCATION)) is not None
        ):
            self._device_or_location = location

    @property
    @override
    def current_option(self) -> str | None:
        """Return the selected entity option to represent the entity state."""
        return self.device[self.entity_description.key]

    @property
    @override
    def options(self) -> list[str]:
        """Return the available select-options."""
        return self.device[self.entity_description.options_key]

    @plugwise_command
    @override
    async def async_select_option(self, option: str) -> None:
        """Change to the selected entity option.

        The appliance ID (= device_id) is required for the dhw_mode select.
        The location ID is required for the thermostat schedule and zone_profile selects.
        STATE_ON is required for the thermostat schedule select.
        """
        options_count_or_state: int | str | None = len(self.options)
        if self.entity_description.key != DHW_MODE:
            options_count_or_state = STATE_ON
        await self.entity_description.set_value_fn(
            self.coordinator.api,
            self.entity_description.key,
            self._device_or_location,
            option,
            options_count_or_state,
        )
