"""Config flow for Remote Media Player integration.""" from __future__ import annotations import logging from typing import Any import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_PORT, CONF_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from .api_client import ( MediaServerClient, MediaServerConnectionError, MediaServerAuthError, ) from .const import ( DOMAIN, CONF_TOKEN, CONF_POLL_INTERVAL, DEFAULT_PORT, DEFAULT_POLL_INTERVAL, DEFAULT_NAME, ) _LOGGER = logging.getLogger(__name__) async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the user input allows us to connect. Args: hass: Home Assistant instance data: User input data Returns: Validated data with title Raises: CannotConnect: If connection fails InvalidAuth: If authentication fails """ client = MediaServerClient( host=data[CONF_HOST], port=data[CONF_PORT], token=data[CONF_TOKEN], ) try: health = await client.get_health() # Try authenticated endpoint await client.get_status() except MediaServerConnectionError as err: await client.close() raise CannotConnect(str(err)) from err except MediaServerAuthError as err: await client.close() raise InvalidAuth(str(err)) from err finally: await client.close() # Return info to store in the config entry return { "title": data.get(CONF_NAME, DEFAULT_NAME), "platform": health.get("platform", "Unknown"), } class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Remote Media Player.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle the initial step. Args: user_input: User provided configuration Returns: Flow result """ errors: dict[str, str] = {} if user_input is not None: try: info = await validate_input(self.hass, user_input) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: errors["base"] = "invalid_auth" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: # Check if already configured await self.async_set_unique_id( f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" ) self._abort_if_unique_id_configured() return self.async_create_entry( title=info["title"], data=user_input, ) # Show configuration form return self.async_show_form( step_id="user", data_schema=vol.Schema( { vol.Required(CONF_HOST): selector.TextSelector( selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT) ), vol.Required(CONF_PORT, default=DEFAULT_PORT): selector.NumberSelector( selector.NumberSelectorConfig( min=1, max=65535, mode=selector.NumberSelectorMode.BOX, ) ), vol.Required(CONF_TOKEN): selector.TextSelector( selector.TextSelectorConfig( type=selector.TextSelectorType.PASSWORD ) ), vol.Optional(CONF_NAME, default=DEFAULT_NAME): selector.TextSelector( selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT) ), vol.Optional( CONF_POLL_INTERVAL, default=DEFAULT_POLL_INTERVAL ): selector.NumberSelector( selector.NumberSelectorConfig( min=1, max=60, step=1, unit_of_measurement="seconds", mode=selector.NumberSelectorMode.SLIDER, ) ), } ), errors=errors, ) @staticmethod @callback def async_get_options_flow( config_entry: config_entries.ConfigEntry, ) -> config_entries.OptionsFlow: """Create the options flow. Args: config_entry: Config entry Returns: Options flow handler """ return OptionsFlowHandler(config_entry) class OptionsFlowHandler(config_entries.OptionsFlow): """Handle options flow for Remote Media Player.""" def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize options flow. Args: config_entry: Config entry """ self._config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Manage the options. Args: user_input: User provided options Returns: Flow result """ if user_input is not None: return self.async_create_entry(title="", data=user_input) return self.async_show_form( step_id="init", data_schema=vol.Schema( { vol.Optional( CONF_POLL_INTERVAL, default=self._config_entry.options.get( CONF_POLL_INTERVAL, self._config_entry.data.get( CONF_POLL_INTERVAL, DEFAULT_POLL_INTERVAL ), ), ): selector.NumberSelector( selector.NumberSelectorConfig( min=1, max=60, step=1, unit_of_measurement="seconds", mode=selector.NumberSelectorMode.SLIDER, ) ), } ), ) class CannotConnect(Exception): """Error to indicate we cannot connect.""" class InvalidAuth(Exception): """Error to indicate there is invalid auth."""