"""Config flow for Random helper."""
from collections.abc import Callable, Coroutine, Mapping
from enum import StrEnum
from typing import Any, cast

import voluptuous as vol

from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.sensor import DEVICE_CLASS_UNITS, SensorDeviceClass
from homeassistant.const import (
    CONF_DEVICE_CLASS,
    CONF_MAXIMUM,
    CONF_MINIMUM,
    CONF_NAME,
    CONF_UNIT_OF_MEASUREMENT,
    Platform,
)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.schema_config_entry_flow import (
    SchemaCommonFlowHandler,
    SchemaConfigFlowHandler,
    SchemaFlowFormStep,
    SchemaFlowMenuStep,
)
from homeassistant.helpers.selector import (
    SelectSelector,
    SelectSelectorConfig,
    SelectSelectorMode,
    TextSelector,
)

from .const import DOMAIN
from .sensor import DEFAULT_MAX, DEFAULT_MIN


class _FlowType(StrEnum):
    CONFIG = "config"
    OPTION = "option"


def _generate_schema(domain: str, flow_type: _FlowType) -> vol.Schema:
    """Generate schema."""
    schema: dict[vol.Marker, Any] = {}

    if flow_type == _FlowType.CONFIG:
        schema[vol.Required(CONF_NAME)] = TextSelector()

        if domain == Platform.BINARY_SENSOR:
            schema[vol.Optional(CONF_DEVICE_CLASS)] = SelectSelector(
                SelectSelectorConfig(
                    options=[cls.value for cls in BinarySensorDeviceClass],
                    sort=True,
                    mode=SelectSelectorMode.DROPDOWN,
                    translation_key="binary_sensor_device_class",
                ),
            )

    if domain == Platform.SENSOR:
        schema.update(
            {
                vol.Optional(CONF_MINIMUM, default=DEFAULT_MIN): cv.positive_int,
                vol.Optional(CONF_MAXIMUM, default=DEFAULT_MAX): cv.positive_int,
                vol.Optional(CONF_DEVICE_CLASS): SelectSelector(
                    SelectSelectorConfig(
                        options=[
                            cls.value
                            for cls in SensorDeviceClass
                            if cls != SensorDeviceClass.ENUM
                        ],
                        sort=True,
                        mode=SelectSelectorMode.DROPDOWN,
                        translation_key="sensor_device_class",
                    ),
                ),
                vol.Optional(CONF_UNIT_OF_MEASUREMENT): SelectSelector(
                    SelectSelectorConfig(
                        options=[
                            str(unit)
                            for units in DEVICE_CLASS_UNITS.values()
                            for unit in units
                            if unit is not None
                        ],
                        sort=True,
                        mode=SelectSelectorMode.DROPDOWN,
                        translation_key="sensor_unit_of_measurement",
                        custom_value=True,
                    ),
                ),
            }
        )

    return vol.Schema(schema)


async def choose_options_step(options: dict[str, Any]) -> str:
    """Return next step_id for options flow according to template_type."""
    return cast(str, options["entity_type"])


def _validate_unit(options: dict[str, Any]) -> None:
    """Validate unit of measurement."""
    if (
        (device_class := options.get(CONF_DEVICE_CLASS))
        and (units := DEVICE_CLASS_UNITS.get(device_class))
        and (unit := options.get(CONF_UNIT_OF_MEASUREMENT)) not in units
    ):
        sorted_units = sorted(
            [f"'{str(unit)}'" if unit else "no unit of measurement" for unit in units],
            key=str.casefold,
        )
        if len(sorted_units) == 1:
            units_string = sorted_units[0]
        else:
            units_string = f"one of {', '.join(sorted_units)}"

        raise vol.Invalid(
            f"'{unit}' is not a valid unit for device class '{device_class}'; "
            f"expected {units_string}"
        )


def validate_user_input(
    template_type: str,
) -> Callable[
    [SchemaCommonFlowHandler, dict[str, Any]],
    Coroutine[Any, Any, dict[str, Any]],
]:
    """Do post validation of user input.

    For sensors: Validate unit of measurement.
    """

    async def _validate_user_input(
        _: SchemaCommonFlowHandler,
        user_input: dict[str, Any],
    ) -> dict[str, Any]:
        """Add template type to user input."""
        if template_type == Platform.SENSOR:
            _validate_unit(user_input)
        return {"entity_type": template_type} | user_input

    return _validate_user_input


RANDOM_TYPES = [
    Platform.BINARY_SENSOR.value,
    Platform.SENSOR.value,
]

CONFIG_FLOW = {
    "user": SchemaFlowMenuStep(RANDOM_TYPES),
    Platform.BINARY_SENSOR: SchemaFlowFormStep(
        _generate_schema(Platform.BINARY_SENSOR, _FlowType.CONFIG),
        validate_user_input=validate_user_input(Platform.BINARY_SENSOR),
    ),
    Platform.SENSOR: SchemaFlowFormStep(
        _generate_schema(Platform.SENSOR, _FlowType.CONFIG),
        validate_user_input=validate_user_input(Platform.SENSOR),
    ),
}


OPTIONS_FLOW = {
    "init": SchemaFlowFormStep(next_step=choose_options_step),
    Platform.BINARY_SENSOR: SchemaFlowFormStep(
        _generate_schema(Platform.BINARY_SENSOR, _FlowType.OPTION),
        validate_user_input=validate_user_input(Platform.BINARY_SENSOR),
    ),
    Platform.SENSOR: SchemaFlowFormStep(
        _generate_schema(Platform.SENSOR, _FlowType.OPTION),
        validate_user_input=validate_user_input(Platform.SENSOR),
    ),
}


class RandomConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
    """Handle config flow for random helper."""

    config_flow = CONFIG_FLOW
    options_flow = OPTIONS_FLOW

    @callback
    def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
        """Return config entry title."""
        return cast(str, options["name"])