"""Support for OctoPrint number entities."""

import logging
from typing import override

from pyoctoprintapi import OctoprintClient

from homeassistant.components.number import NumberDeviceClass, NumberEntity, NumberMode
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN
from .coordinator import OctoprintConfigEntry, OctoprintDataUpdateCoordinator

_LOGGER = logging.getLogger(__name__)


def is_bed(tool_name: str) -> bool:
    """Return True if the tool name indicates a bed."""
    return tool_name == "bed"


def is_extruder(tool_name: str) -> bool:
    """Return True if the tool name indicates an extruder."""
    return tool_name.startswith("tool") and tool_name[4:].isdigit()


def is_first_extruder(tool_name: str) -> bool:
    """Return True if the tool name indicates the first extruder."""
    return tool_name == "tool0"


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: OctoprintConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the OctoPrint number entities."""
    coordinator = config_entry.runtime_data
    client = coordinator.octoprint
    device_id = config_entry.unique_id

    assert device_id is not None

    known_tools = set()

    @callback
    def async_add_tool_numbers() -> None:
        if not coordinator.data["printer"]:
            return

        new_numbers: list[OctoPrintTemperatureNumber] = []
        for tool in coordinator.data["printer"].temperatures:
            if (
                is_extruder(tool.name) or is_bed(tool.name)
            ) and tool.name not in known_tools:
                assert device_id is not None
                known_tools.add(tool.name)
                new_numbers.append(
                    OctoPrintTemperatureNumber(
                        coordinator,
                        client,
                        tool.name,
                        device_id,
                    )
                )
        async_add_entities(new_numbers)

    config_entry.async_on_unload(coordinator.async_add_listener(async_add_tool_numbers))

    if coordinator.data["printer"]:
        async_add_tool_numbers()


class OctoPrintTemperatureNumber(
    CoordinatorEntity[OctoprintDataUpdateCoordinator], NumberEntity
):
    """Representation of an OctoPrint temperature setter entity."""

    _attr_has_entity_name = True
    _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
    _attr_native_min_value = 0
    _attr_native_max_value = 300
    _attr_native_step = 1
    _attr_mode = NumberMode.BOX
    _attr_device_class = NumberDeviceClass.TEMPERATURE

    def __init__(
        self,
        coordinator: OctoprintDataUpdateCoordinator,
        client: OctoprintClient,
        tool: str,
        device_id: str,
    ) -> None:
        """Initialize a new OctoPrint temperature number entity."""
        super().__init__(coordinator)
        self._api_tool = tool
        self._attr_device_info = coordinator.device_info
        self._attr_unique_id = f"{device_id}_{tool}_temperature"
        self._client = client
        self._device_id = device_id
        if is_bed(tool):
            self._attr_translation_key = "bed_temperature"
        elif is_first_extruder(tool):
            self._attr_translation_key = "extruder_temperature"
        else:
            self._attr_translation_key = "extruder_n_temperature"
            self._attr_translation_placeholders = {"n": tool[4:]}

    @property
    @override
    def native_value(self) -> float | None:
        """Return the current target temperature."""
        if not self.coordinator.data["printer"]:
            return None
        for tool in self.coordinator.data["printer"].temperatures:
            if tool.name == self._api_tool and tool.target_temp is not None:
                return tool.target_temp

        return None

    @override
    async def async_set_native_value(self, value: float) -> None:
        """Set the target temperature."""

        try:
            if is_bed(self._api_tool):
                await self._client.set_bed_temperature(int(value))
            elif is_extruder(self._api_tool):
                await self._client.set_tool_temperature(self._api_tool, int(value))
        except Exception as err:
            raise HomeAssistantError(
                translation_domain=DOMAIN,
                translation_key="error_setting_temperature",
                translation_placeholders={
                    "tool": self._api_tool,
                },
            ) from err

        # Request coordinator update to reflect the change
        await self.coordinator.async_request_refresh()
