import logging
from abc import abstractmethod
from collections.abc import Iterable
from typing import Callable, List, Optional, Union

# pylint: disable=no-name-in-module
from aioesphomeapi.api_pb2 import (  # type: ignore[attr-defined]
    EventResponse,
    LightCommandRequest,
    LightStateResponse,
    ListEntitiesEventResponse,
    ListEntitiesLightResponse,
    ListEntitiesMediaPlayerResponse,
    ListEntitiesNumberResponse,
    ListEntitiesRequest,
    ListEntitiesSelectResponse,
    ListEntitiesSwitchResponse,
    MediaPlayerCommandRequest,
    MediaPlayerStateResponse,
    NumberCommandRequest,
    NumberStateResponse,
    SelectCommandRequest,
    SelectStateResponse,
    SubscribeHomeAssistantStatesRequest,
    SwitchCommandRequest,
    SwitchStateResponse,
)
from aioesphomeapi.model import (
    ColorMode,
    EntityCategory,
    MediaPlayerCommand,
    MediaPlayerEntityFeature,
    MediaPlayerState,
    NumberMode,
)
from google.protobuf import message

from .api_server import APIServer
from .mpv_player import MpvMediaPlayer
from .util import call_all

SUPPORTED_MEDIA_PLAYER_FEATURES = (
    MediaPlayerEntityFeature.PLAY
    | MediaPlayerEntityFeature.PAUSE
    | MediaPlayerEntityFeature.STOP
    | MediaPlayerEntityFeature.PLAY_MEDIA
    | MediaPlayerEntityFeature.VOLUME_SET
    | MediaPlayerEntityFeature.VOLUME_MUTE
    | MediaPlayerEntityFeature.MEDIA_ANNOUNCE
)


class ESPHomeEntity:
    def __init__(self, server: APIServer) -> None:
        self.server = server

    @abstractmethod
    def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
        pass


# -----------------------------------------------------------------------------


class MediaPlayerEntity(ESPHomeEntity):
    def __init__(
        self,
        server: APIServer,
        key: int,
        name: str,
        object_id: str,
        music_player: MpvMediaPlayer,
        announce_player: MpvMediaPlayer,
        initial_volume: float = 1.0,
        on_volume_changed: Optional[Callable[[float], None]] = None,
    ) -> None:
        ESPHomeEntity.__init__(self, server)

        self.key = key
        self.name = name
        self.object_id = object_id
        self.state = MediaPlayerState.IDLE
        self.volume = max(0.0, min(1.0, initial_volume))
        self.muted = False
        self.previous_volume = 1.0
        self.music_player = music_player
        self.announce_player = announce_player
        self._on_volume_changed = on_volume_changed
        self.apply_volume_from_state(initial_volume)
        self._log = logging.getLogger(f"{self.__class__.__name__}[{self.key}]")

    def _broadcast_state(self, msgs: Iterable[message.Message]) -> None:
        """Push an asynchronous state change to all connected clients.

        Playback-completion callbacks fire outside any request, so the update
        must reach every subscribed client rather than the single connection in
        ``self.server`` (which may belong to another client, or be closed).
        """
        state = getattr(self.server, "state", None)
        if state is not None:
            state.broadcast(msgs)
        else:  # pragma: no cover - no ServerState (e.g. a bare APIServer)
            self.server.send_messages(msgs)

    def play(
        self,
        url: Union[str, List[str]],
        announcement: bool = False,
        done_callback: Optional[Callable[[], None]] = None,
    ) -> Iterable[message.Message]:
        if announcement:
            self._log.debug("PLAY: announcement true")
            if self.music_player.is_playing:
                # Announce, resume music
                self.music_player.pause()
                self.announce_player.play(
                    url,
                    done_callback=lambda: call_all(self.music_player.resume, done_callback),
                )
            else:
                # Announce, idle
                self.announce_player.play(
                    url,
                    done_callback=lambda: call_all(
                        lambda: self._broadcast_state([self._update_state(MediaPlayerState.IDLE)]),
                        done_callback,
                    ),
                )
        else:
            self._log.debug("PLAY: announcement false")
            # Music
            self.music_player.play(
                url,
                done_callback=lambda: call_all(
                    lambda: self._broadcast_state([self._update_state(MediaPlayerState.IDLE)]),
                    done_callback,
                ),
            )

        yield self._update_state(MediaPlayerState.PLAYING)

    def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
        self._log.debug("handle_message called with msg: %s", msg)

        # Suppress warning for irrelevant NumberCommandRequest
        if isinstance(msg, (NumberCommandRequest, SelectCommandRequest)):
            return

        if isinstance(msg, MediaPlayerCommandRequest) and (msg.key == self.key):
            self._log.debug("MediaPlayerCommandRequest matched for this key")

            if msg.has_media_url:
                self._log.debug("Executing PLAY")
                self._log.debug("Message has media URL: %s", msg.media_url)
                announcement = msg.has_announcement and msg.announcement
                yield from self.play(msg.media_url, announcement=announcement)

            elif msg.has_command:
                self._log.debug("Message has command: %s", msg.command)
                command = MediaPlayerCommand(msg.command)

                if msg.command == MediaPlayerCommand.PAUSE:
                    self._log.debug("Executing PAUSE")
                    self.music_player.pause()
                    yield self._update_state(MediaPlayerState.PAUSED)

                elif msg.command == MediaPlayerCommand.PLAY:
                    self._log.debug("Executing PLAY / RESUME")
                    self.music_player.resume()
                    yield self._update_state(MediaPlayerState.PLAYING)

                elif command == MediaPlayerCommand.STOP:
                    self._log.debug("Executing STOP")
                    self.music_player.stop()
                    yield self._update_state(MediaPlayerState.IDLE)

                elif command == MediaPlayerCommand.MUTE:
                    self._log.debug("Executing MUTE")
                    if not self.muted:
                        self.previous_volume = self.volume
                        self.volume = 0
                        self.music_player.set_volume(0)
                        self.announce_player.set_volume(0)
                        self.muted = True
                    yield self._update_state(self.state)

                elif command == MediaPlayerCommand.UNMUTE:
                    self._log.debug("Executing UNMUTE")
                    if self.muted:
                        self.volume = self.previous_volume
                        self.music_player.set_volume(int(self.volume * 100))
                        self.announce_player.set_volume(int(self.volume * 100))
                        self.muted = False
                    yield self._update_state(self.state)

            elif msg.has_volume:
                self._log.debug("Message has volume: %.2f", msg.volume)
                self._apply_volume(msg.volume, persist=True)
                if hasattr(self.server, "state") and getattr(self.server, "state", None) is not None:
                    self._log.debug("Persisting volume to preferences")
                    self.server.state.persist_volume(self.volume)
                else:
                    self._log.warning("Cannot persist volume - server.state not available")
                yield self._update_state(self.state)

        elif isinstance(msg, ListEntitiesRequest):
            self._log.debug("ListEntitiesRequest received")
            yield ListEntitiesMediaPlayerResponse(
                object_id=self.object_id,
                key=self.key,
                name=self.name,
                supports_pause=True,
                feature_flags=SUPPORTED_MEDIA_PLAYER_FEATURES,
            )
        elif isinstance(msg, SubscribeHomeAssistantStatesRequest):
            self._log.debug("SubscribeHomeAssistantStatesRequest received")
            yield self._get_state_message()
        else:
            self._log.warning("Unknown message type received: %s", type(msg))

    def _update_state(self, new_state: MediaPlayerState) -> MediaPlayerStateResponse:
        self._log.debug("SET NEW STATE: %s => %s", self.state, new_state)
        self._log.debug("SET NEW STATE: %s => %s", self.state.name, new_state.name)
        self.state = new_state
        return self._get_state_message()

    def _get_state_message(self) -> MediaPlayerStateResponse:
        return MediaPlayerStateResponse(
            key=self.key,
            state=self.state,
            volume=self.volume,
            muted=self.muted,
        )

    def apply_volume_from_state(self, volume: float) -> None:
        """Synchronize the local volume with the stored state without persisting."""

        clamped = max(0.0, min(1.0, float(volume)))

        if self.muted:
            self.previous_volume = clamped
            return

        self._apply_volume(clamped, persist=False)

    def set_volume_callback(self, callback: Optional[Callable[[float], None]]) -> None:
        """Update the callback invoked when the volume changes."""

        self._on_volume_changed = callback

    def _apply_volume(
        self,
        volume: float,
        *,
        persist: bool,
        remember: bool = True,
    ) -> None:
        normalized = max(0.0, min(1.0, float(volume)))
        volume_percent = int(round(normalized * 100))

        self.music_player.set_volume(volume_percent)
        self.announce_player.set_volume(volume_percent)

        self.volume = normalized

        if remember:
            self.previous_volume = normalized

        if self._on_volume_changed and persist:
            self._on_volume_changed(normalized)


# -----------------------------------------------------------------------------


class MuteSwitchEntity(ESPHomeEntity):
    def __init__(
        self,
        server: APIServer,
        key: int,
        name: str,
        object_id: str,
        get_muted: Callable[[], bool],
        set_muted: Callable[[bool], None],
    ) -> None:
        ESPHomeEntity.__init__(self, server)

        self.key = key
        self.name = name
        self.object_id = object_id
        self._get_muted = get_muted
        self._set_muted = set_muted
        self._switch_state = self._get_muted()  # Sync internal state with actual muted value on init

    def update_set_muted(self, set_muted: Callable[[bool], None]) -> None:
        # Update the callback used to change the mute state.
        self._set_muted = set_muted

    def update_get_muted(self, get_muted: Callable[[], bool]) -> None:
        # Update the callback used to read the mute state.
        self._get_muted = get_muted

    def sync_with_state(self) -> None:
        # Sync internal switch state with the actual mute state.
        self._switch_state = self._get_muted()

    def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
        if isinstance(msg, SwitchCommandRequest) and (msg.key == self.key):
            # User toggled the switch - update our internal state and trigger actions
            new_state = bool(msg.state)
            self._switch_state = new_state
            self._set_muted(new_state)
            # Return the new state immediately
            yield SwitchStateResponse(key=self.key, state=self._switch_state)
        elif isinstance(msg, ListEntitiesRequest):
            yield ListEntitiesSwitchResponse(
                object_id=self.object_id,
                key=self.key,
                name=self.name,
                entity_category=EntityCategory.CONFIG,
                icon="mdi:microphone-off",
            )
        elif isinstance(msg, SubscribeHomeAssistantStatesRequest):
            # Always return our internal switch state
            self.sync_with_state()
            yield SwitchStateResponse(key=self.key, state=self._switch_state)


class ThinkingSoundEntity(ESPHomeEntity):
    def __init__(
        self,
        server: APIServer,
        key: int,
        name: str,
        object_id: str,
        get_thinking_sound_enabled: Callable[[], bool],
        set_thinking_sound_enabled: Callable[[bool], None],
    ) -> None:
        ESPHomeEntity.__init__(self, server)

        self.key = key
        self.name = name
        self.object_id = object_id
        self._get_thinking_sound_enabled = get_thinking_sound_enabled
        self._set_thinking_sound_enabled = set_thinking_sound_enabled
        self._switch_state = self._get_thinking_sound_enabled()  # Sync internal state

    def update_get_thinking_sound_enabled(self, get_thinking_sound_enabled: Callable[[], bool]) -> None:
        # Update the callback used to read the thinking sound enabled state.
        self._get_thinking_sound_enabled = get_thinking_sound_enabled

    def update_set_thinking_sound_enabled(self, set_thinking_sound_enabled: Callable[[bool], None]) -> None:
        # Update the callback used to change the thinking sound enabled state.
        self._set_thinking_sound_enabled = set_thinking_sound_enabled

    def sync_with_state(self) -> None:
        # Sync internal switch state with the actual thinking sound enabled state.
        self._switch_state = self._get_thinking_sound_enabled()

    def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
        if isinstance(msg, SwitchCommandRequest) and (msg.key == self.key):
            # User toggled the switch - update our internal state and trigger actions
            new_state = bool(msg.state)
            self._switch_state = new_state
            self._set_thinking_sound_enabled(new_state)
            # Return the new state immediately
            yield SwitchStateResponse(key=self.key, state=self._switch_state)
        elif isinstance(msg, ListEntitiesRequest):
            yield ListEntitiesSwitchResponse(
                object_id=self.object_id,
                key=self.key,
                name=self.name,
                entity_category=EntityCategory.CONFIG,
                icon="mdi:music-note",
            )
        elif isinstance(msg, SubscribeHomeAssistantStatesRequest):
            # Always return our internal switch state
            self.sync_with_state()
            yield SwitchStateResponse(key=self.key, state=self._switch_state)


class MicSettingEntity(ESPHomeEntity):
    def __init__(
        self,
        server: APIServer,
        key: int,
        name: str,
        object_id: str,
        get_value: Callable[[], Union[float, str]],
        set_value: Callable[[Union[float, str]], None],
        min_value: float = 0.0,
        max_value: float = 1.0,
        options: Optional[List[str]] = None,
        icon: str = "mdi:microphone",
    ) -> None:
        ESPHomeEntity.__init__(self, server)
        self.key = key
        self.name = name
        self.object_id = object_id
        self.options = options  # If present, this behaves as a Dropdown
        self.min_value = min_value
        self.max_value = max_value
        self._get_value = get_value
        self._set_value = set_value
        self._state = self._get_value()
        self.icon = icon

    def sync_with_state(self) -> None:
        """Sync internal state with the actual value."""
        self._state = self._get_value()

    def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
        # --- 1. HANDLE COMMANDS FROM HOME ASSISTANT ---
        if self.options:
            if isinstance(msg, SelectCommandRequest) and (msg.key == self.key):
                new_val = msg.state
                self._state = new_val
                self._set_value(new_val)
                yield SelectStateResponse(key=self.key, state=new_val)
        else:
            if isinstance(msg, NumberCommandRequest) and (msg.key == self.key):
                new_val = msg.state
                self._state = new_val
                self._set_value(new_val)
                yield NumberStateResponse(key=self.key, state=new_val)

        # --- 2. DISCOVERY (TELL HA WHAT TYPE TO SHOW) ---
        if isinstance(msg, ListEntitiesRequest):
            if self.options:
                yield ListEntitiesSelectResponse(
                    object_id=self.object_id,
                    key=self.key,
                    name=self.name,
                    options=self.options,
                    entity_category=EntityCategory.CONFIG,
                    icon=self.icon,
                )
            else:
                yield ListEntitiesNumberResponse(
                    object_id=self.object_id,
                    key=self.key,
                    name=self.name,
                    min_value=self.min_value,
                    max_value=self.max_value,
                    step=1.0,
                    entity_category=EntityCategory.CONFIG,
                    icon=self.icon,
                )

        # --- 3. INITIAL SYNC / STATE UPDATES ---
        elif isinstance(msg, SubscribeHomeAssistantStatesRequest):
            self.sync_with_state()
            if self.options:
                yield SelectStateResponse(key=self.key, state=str(self._state))
            else:
                yield NumberStateResponse(key=self.key, state=float(self._state))

    def update_get_value(self, get_value: Callable[[], Union[float, str]]) -> None:
        self._get_value = get_value

    def update_set_value(self, set_value: Callable[[Union[float, str]], None]) -> None:
        self._set_value = set_value


# -----------------------------------------------------------------------------


class WakeWord1SensitivityNumberEntity(ESPHomeEntity):
    def __init__(
        self,
        server: APIServer,
        key: int,
        name: str,
        object_id: str,
        get_sensitivity: Callable[[], float],
        set_sensitivity: Callable[[float], None],
        initial_value: float = 0.5,
    ) -> None:
        ESPHomeEntity.__init__(self, server)

        self.key = key
        self.name = name
        self.object_id = object_id
        self._get_sensitivity = get_sensitivity
        self._set_sensitivity = set_sensitivity
        self.value = initial_value
        self._log = logging.getLogger(f"{self.__class__.__name__}[{self.key}]")

    def update_get_sensitivity(self, get_sensitivity: Callable[[], float]) -> None:
        self._get_sensitivity = get_sensitivity

    def update_set_sensitivity(self, set_sensitivity: Callable[[float], None]) -> None:
        self._set_sensitivity = set_sensitivity

    def sync_with_state(self) -> None:
        old_value = self.value
        self.value = self._get_sensitivity()
        self._log.debug("Entity synchronized: old=%.3f new=%.3f", old_value, self.value)

    def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
        if isinstance(msg, NumberCommandRequest) and (msg.key == self.key):
            new_value = float(msg.state)
            self._log.debug("Sensitivity value changed: %s => %s", self.value, new_value)
            self.value = new_value
            self._set_sensitivity(new_value)
            yield NumberStateResponse(key=self.key, state=self.value)
        elif isinstance(msg, ListEntitiesRequest):
            yield ListEntitiesNumberResponse(
                object_id=self.object_id,
                key=self.key,
                name=self.name,
                entity_category=EntityCategory.CONFIG,
                min_value=0.0,
                max_value=1.0,
                step=0.001,
                mode=NumberMode.BOX,
            )
        elif isinstance(msg, SubscribeHomeAssistantStatesRequest):
            self.sync_with_state()
            yield NumberStateResponse(key=self.key, state=self.value)


class WakeWord2SensitivityNumberEntity(ESPHomeEntity):
    def __init__(
        self,
        server: APIServer,
        key: int,
        name: str,
        object_id: str,
        get_sensitivity: Callable[[], float],
        set_sensitivity: Callable[[float], None],
        initial_value: float = 0.5,
    ) -> None:
        ESPHomeEntity.__init__(self, server)

        self.key = key
        self.name = name
        self.object_id = object_id
        self._get_sensitivity = get_sensitivity
        self._set_sensitivity = set_sensitivity
        self.value = initial_value
        self._log = logging.getLogger(f"{self.__class__.__name__}[{self.key}]")

    def update_get_sensitivity(self, get_sensitivity: Callable[[], float]) -> None:
        self._get_sensitivity = get_sensitivity

    def update_set_sensitivity(self, set_sensitivity: Callable[[float], None]) -> None:
        self._set_sensitivity = set_sensitivity

    def sync_with_state(self) -> None:
        old_value = self.value
        self.value = self._get_sensitivity()
        self._log.debug("Entity synchronized: old=%.3f new=%.3f", old_value, self.value)

    def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
        if isinstance(msg, NumberCommandRequest) and (msg.key == self.key):
            new_value = float(msg.state)
            self._log.debug("Second wake word sensitivity value changed: %s => %s", self.value, new_value)
            self.value = new_value
            self._set_sensitivity(new_value)
            yield NumberStateResponse(key=self.key, state=self.value)
        elif isinstance(msg, ListEntitiesRequest):
            yield ListEntitiesNumberResponse(
                object_id=self.object_id,
                key=self.key,
                name=self.name,
                entity_category=EntityCategory.CONFIG,
                min_value=0.0,
                max_value=1.0,
                step=0.001,
                mode=NumberMode.BOX,
            )
        elif isinstance(msg, SubscribeHomeAssistantStatesRequest):
            self.sync_with_state()
            yield NumberStateResponse(key=self.key, state=self.value)


class StopWordSensitivityNumberEntity(ESPHomeEntity):
    def __init__(
        self,
        server: APIServer,
        key: int,
        name: str,
        object_id: str,
        get_sensitivity: Callable[[], float],
        set_sensitivity: Callable[[float], None],
        initial_value: float = 0.5,
    ) -> None:
        ESPHomeEntity.__init__(self, server)

        self.key = key
        self.name = name
        self.object_id = object_id
        self._get_sensitivity = get_sensitivity
        self._set_sensitivity = set_sensitivity
        self.value = initial_value
        self._log = logging.getLogger(f"{self.__class__.__name__}[{self.key}]")

    def update_get_sensitivity(self, get_sensitivity: Callable[[], float]) -> None:
        self._get_sensitivity = get_sensitivity

    def update_set_sensitivity(self, set_sensitivity: Callable[[float], None]) -> None:
        self._set_sensitivity = set_sensitivity

    def sync_with_state(self) -> None:
        old_value = self.value
        self.value = self._get_sensitivity()
        self._log.debug("Entity synchronized: old=%.3f new=%.3f", old_value, self.value)

    def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
        if isinstance(msg, NumberCommandRequest) and (msg.key == self.key):
            new_value = float(msg.state)
            self._log.debug("Stop word sensitivity value changed: %s => %s", self.value, new_value)
            self.value = new_value
            self._set_sensitivity(new_value)
            yield NumberStateResponse(key=self.key, state=self.value)
        elif isinstance(msg, ListEntitiesRequest):
            yield ListEntitiesNumberResponse(
                object_id=self.object_id,
                key=self.key,
                name=self.name,
                entity_category=EntityCategory.CONFIG,
                icon="mdi:hand-back-left",
                min_value=0.0,
                max_value=1.0,
                step=0.001,
                mode=NumberMode.BOX,
            )
        elif isinstance(msg, SubscribeHomeAssistantStatesRequest):
            self.sync_with_state()
            yield NumberStateResponse(key=self.key, state=self.value)


class LEDLightEntity(ESPHomeEntity):
    """RGB Light entity for peripheral LEDs.

    The peripheral declares its capabilities (effects, RGB, brightness)
    via the register_light command. When Home Assistant changes the
    entity, on_changed fires so the peripheral API server can broadcast
    a light_command event back to the peripheral, which applies the
    new state to its hardware.
    """

    def __init__(
        self,
        server: APIServer,
        key: int,
        name: str,
        object_id: str,
        effects: Optional[List[str]] = None,
        supports_rgb: bool = True,
        supports_brightness: bool = True,
        on_changed: Optional[Callable[[], None]] = None,
        icon: str = "mdi:led-strip-variant",
    ) -> None:
        ESPHomeEntity.__init__(self, server)
        self.key = key
        self.name = name
        self.object_id = object_id
        self.icon = icon
        self._on_changed = on_changed
        self.effects_list: List[str] = list(effects) if effects else []
        self._supports_rgb = supports_rgb
        self._supports_brightness = supports_brightness

        # Off by default, matching the HA Voice PE LED Ring
        # (restore_mode RESTORE_DEFAULT_OFF): the resting LEDs stay dark
        # until the user turns the light on. Voice animations are driven
        # separately by the peripheral and play regardless.
        self.is_on: bool = False
        # Match the HA Voice PE LED Ring initial state: a light blue at 66%
        # brightness (red 9.4%, green 73.3%, blue 94.9%).
        self.brightness: float = 0.66
        self.red: float = 0.094
        self.green: float = 0.733
        self.blue: float = 0.949
        # Default effect: first declared, or empty if none.
        self.effect: str = self.effects_list[0] if self.effects_list else ""

    def update_on_changed(self, on_changed: Optional[Callable[[], None]]) -> None:
        self._on_changed = on_changed

    def _color_mode(self) -> ColorMode:
        if self._supports_rgb:
            return ColorMode.RGB
        if self._supports_brightness:
            return ColorMode.BRIGHTNESS
        return ColorMode.ON_OFF

    def state_dict(self) -> dict:
        """Payload for the light_command event.

        Includes object_id so a peripheral that registered more than one
        Light can route the event to the right hardware.
        """
        return {
            "object_id": self.object_id,
            "state": self.is_on,
            "brightness": self.brightness,
            "red": self.red,
            "green": self.green,
            "blue": self.blue,
            "effect": self.effect,
        }

    def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
        if isinstance(msg, LightCommandRequest) and msg.key == self.key:
            changed = False
            if msg.has_state:
                self.is_on = bool(msg.state)
                changed = True
            if msg.has_brightness and self._supports_brightness:
                self.brightness = max(0.0, min(1.0, float(msg.brightness)))
                changed = True
            if msg.has_rgb and self._supports_rgb:
                self.red = max(0.0, min(1.0, float(msg.red)))
                self.green = max(0.0, min(1.0, float(msg.green)))
                self.blue = max(0.0, min(1.0, float(msg.blue)))
                changed = True
            if msg.has_effect:
                requested = str(msg.effect)
                if requested in self.effects_list:
                    self.effect = requested
                    changed = True
            if changed and self._on_changed is not None:
                self._on_changed()
            yield self._state_response()
        elif isinstance(msg, ListEntitiesRequest):
            yield ListEntitiesLightResponse(
                object_id=self.object_id,
                key=self.key,
                name=self.name,
                supported_color_modes=[int(self._color_mode())],
                effects=self.effects_list,
                icon=self.icon,
                entity_category=EntityCategory.CONFIG,
            )
        elif isinstance(msg, SubscribeHomeAssistantStatesRequest):
            yield self._state_response()

    def _state_response(self) -> LightStateResponse:
        return LightStateResponse(
            key=self.key,
            state=self.is_on,
            brightness=self.brightness,
            color_mode=int(self._color_mode()),
            color_brightness=self.brightness,
            red=self.red,
            green=self.green,
            blue=self.blue,
            effect=self.effect,
        )


class ButtonEventSensorEntity(ESPHomeEntity):
    def __init__(
        self,
        server: APIServer,
        key: int,
        name: str,
        object_id: str,
    ) -> None:
        ESPHomeEntity.__init__(self, server)

        self.key = key
        self.name = name
        self.object_id = object_id
        self.event_types = ["single_press", "double_press", "triple_press", "long_press"]
        self._current_event: Optional[str] = None
        self._log = logging.getLogger(f"{self.__class__.__name__}[{self.key}]")

    def update_state(self, event_type: str) -> None:
        """Update the event state with a button press event."""
        self._current_event = event_type
        self._log.debug("Button event state updated: %s", event_type)

    def handle_message(self, msg: message.Message) -> Iterable[message.Message]:
        if isinstance(msg, ListEntitiesRequest):
            yield ListEntitiesEventResponse(
                object_id=self.object_id,
                key=self.key,
                name=self.name,
                device_class="button",
                event_types=self.event_types,
            )
        elif isinstance(msg, SubscribeHomeAssistantStatesRequest):
            # Wait until a press fires: yielding with an empty
            # event_type makes HA reject the state and fail the
            # whole ESPHome config entry to load.
            if self._current_event:
                yield self._get_state_message()

    def _get_state_message(self) -> EventResponse:
        return EventResponse(
            key=self.key,
            event_type=self._current_event or "",
        )


# Backward compatibility export aliases
__all__ = [
    "ESPHomeEntity",
    "MediaPlayerEntity",
    "MuteSwitchEntity",
    "ThinkingSoundEntity",
    "LEDLightEntity",
    "ButtonEventSensorEntity",
    "WakeWord1SensitivityNumberEntity",
    "WakeWord2SensitivityNumberEntity",
    "StopWordSensitivityNumberEntity",
    # Old class names for backward compatibility
    "WakeWordSensitivityNumberEntity",
    "SecondWakeWordSensitivityNumberEntity",
]

WakeWordSensitivityNumberEntity = WakeWord1SensitivityNumberEntity
SecondWakeWordSensitivityNumberEntity = WakeWord2SensitivityNumberEntity


# -----------------------------------------------------------------------------
