"""Canonical notify-target discovery. Single source of truth for the "which notify targets can the user pick" list, shared by BOTH config surfaces: the global options flow (`config_flow_options_global.py`) and the custom panel Settings view (served through `_build_full_settings` in `websocket/dashboard.py`). Before this helper the same merge lived twice — once in Python, once in TypeScript — and the two had drifted (the panel excluded the ``notify.send_message`` *entity* and never injected the saved value; the options flow injected the saved value but only excluded the ``send_message`` *service*). Routing both through here keeps them in lockstep. """ from __future__ import annotations from homeassistant.core import HomeAssistant # The generic notify action, not a real target — never offered as a choice, # in either its service (``notify.send_message``) or entity form. _SEND_MESSAGE = "send_message" _SEND_MESSAGE_ENTITY = f"notify.{_SEND_MESSAGE}" def build_notify_targets(hass: HomeAssistant, *, current: str | None = None) -> list[str]: """Return the sorted set of pickable notify targets. Merges legacy notify *services* (mobile_app devices, notify groups) from the service registry with notify *entities* (the newer model) from the state machine — many single devices appear only as an entity. The generic ``send_message`` action is excluded in both forms. When ``current`` is a non-empty saved value it is always included, so an already-configured but currently-unavailable target still shows up as selected. """ targets: set[str] = {f"notify.{name}" for name in hass.services.async_services().get("notify", {}) if name != _SEND_MESSAGE} targets.update(entity_id for entity_id in hass.states.async_entity_ids("notify") if entity_id != _SEND_MESSAGE_ENTITY) if current: targets.add(current) return sorted(targets)