"""Config flow for the Maintenance Supporter integration."""

from __future__ import annotations

import logging
from typing import Any
from uuid import uuid4

import voluptuous as vol
from homeassistant.config_entries import (
    ConfigEntry,
    ConfigFlow,
    ConfigFlowResult,
    OptionsFlow,
)
from homeassistant.core import HomeAssistant, State, callback
from homeassistant.helpers import selector

from .config_flow_options_global import validate_notify_service
from .config_flow_schedule import ScheduleStepsMixin
from .config_flow_trigger import TriggerConfigMixin
from .const import (
    CONF_DEFAULT_WARNING_DAYS,
    CONF_NOTIFICATIONS_ENABLED,
    CONF_NOTIFY_SERVICE,
    CONF_OBJECT,
    CONF_OBJECT_AREA,
    CONF_OBJECT_DOCUMENTATION_URL,
    CONF_OBJECT_INSTALLATION_DATE,
    CONF_OBJECT_MANUFACTURER,
    CONF_OBJECT_MODEL,
    CONF_OBJECT_NAME,
    CONF_OBJECT_NOTES,
    CONF_OBJECT_SERIAL_NUMBER,
    CONF_OBJECT_WARRANTY_EXPIRY,
    CONF_TASKS,
    DEFAULT_WARNING_DAYS,
    DOMAIN,
    GLOBAL_UNIQUE_ID,
    slugify_object_name,
)
from .helpers.i18n import normalize_language
from .helpers.schedule import normalize_task_storage
from .helpers.task_fields import WARNING_DAYS_RANGE
from .templates import (
    TEMPLATE_CATEGORIES,
    ObjectTemplate,
    get_template_by_id,
    get_templates_by_category,
)

_LOGGER = logging.getLogger(__name__)


def _localized_template_default_name(template: ObjectTemplate, hass: HomeAssistant) -> str:
    """Localized prefill for the template-customize name field (v2.21.1)."""
    from .templates import localize_template_text

    return localize_template_text(template.name, normalize_language(hass)) or template.name


class MaintenanceSupporterConfigFlow(ScheduleStepsMixin, TriggerConfigMixin, ConfigFlow, domain=DOMAIN):
    """Handle a config flow for Maintenance Supporter."""

    VERSION = 1
    MINOR_VERSION = 5

    def __init__(self) -> None:
        """Initialize the config flow."""
        self._object_data: dict[str, Any] = {}
        self._tasks: dict[str, dict[str, Any]] = {}
        self._current_task: dict[str, Any] = {}
        self._trigger_entity_id: str | None = None
        self._trigger_entity_state: State | None = None
        self._template_category: str = ""
        self._selected_template: ObjectTemplate | None = None

    async def async_step_user(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Handle the initial step."""
        # Check if global entry exists
        global_exists = any(entry.unique_id == GLOBAL_UNIQUE_ID for entry in self.hass.config_entries.async_entries(DOMAIN))

        if not global_exists:
            return await self.async_step_global_setup()

        return self.async_show_menu(
            step_id="user",
            menu_options=["create_object", "create_from_template"],
        )

    async def async_step_global_setup(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Set up global configuration."""
        errors: dict[str, str] = {}

        if user_input is not None:
            # Validate notify service format (no hass check — services may not be loaded yet)
            raw_service = user_input.get(CONF_NOTIFY_SERVICE, "")
            normalized, error = validate_notify_service(raw_service)
            if error:
                errors[CONF_NOTIFY_SERVICE] = error

            if not errors:
                await self.async_set_unique_id(GLOBAL_UNIQUE_ID)
                self._abort_if_unique_id_configured()
                return self.async_create_entry(
                    title="Maintenance Supporter",
                    data={
                        CONF_DEFAULT_WARNING_DAYS: user_input.get(CONF_DEFAULT_WARNING_DAYS, DEFAULT_WARNING_DAYS),
                        CONF_NOTIFICATIONS_ENABLED: user_input.get(CONF_NOTIFICATIONS_ENABLED, False),
                        CONF_NOTIFY_SERVICE: normalized,
                    },
                )

        # Offer notify targets as a dropdown: legacy notify *services* (mobile_app
        # devices, notify groups) plus notify *entities* (newer model) — many
        # single devices appear only as an entity. send_message is the generic
        # action, not a target → excluded. custom_value keeps it free-text (the
        # format-only validation above never blocks on existence). Matches the
        # picker in the options flow + panel.
        notify_targets = {
            f"notify.{name}" for name in self.hass.services.async_services().get("notify", {}) if name != "send_message"
        }
        notify_targets.update(self.hass.states.async_entity_ids("notify"))
        notify_services = sorted(notify_targets)

        return self.async_show_form(
            step_id="global_setup",
            data_schema=vol.Schema(
                {
                    vol.Optional(CONF_DEFAULT_WARNING_DAYS, default=DEFAULT_WARNING_DAYS): selector.NumberSelector(
                        selector.NumberSelectorConfig(
                            min=1, max=WARNING_DAYS_RANGE[1], step=1, mode=selector.NumberSelectorMode.BOX
                        )
                    ),
                    vol.Optional(CONF_NOTIFICATIONS_ENABLED, default=False): selector.BooleanSelector(),
                    vol.Optional(CONF_NOTIFY_SERVICE, default=""): selector.SelectSelector(
                        selector.SelectSelectorConfig(
                            options=notify_services,
                            mode=selector.SelectSelectorMode.DROPDOWN,
                            custom_value=True,
                        )
                    ),
                }
            ),
            errors=errors,
        )

    async def async_step_import(self, import_data: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Programmatically (re)create the global entry with default settings.

        Used by the missing-global-entry repair flow to restore the global
        "Maintenance Supporter" configuration after it was deleted while object
        entries remained (which strips the summary sensors + panel). Aborts if a
        global entry already exists, so it's safe to trigger unconditionally.
        """
        await self.async_set_unique_id(GLOBAL_UNIQUE_ID)
        self._abort_if_unique_id_configured()
        return self.async_create_entry(
            title="Maintenance Supporter",
            data={
                CONF_DEFAULT_WARNING_DAYS: DEFAULT_WARNING_DAYS,
                CONF_NOTIFICATIONS_ENABLED: False,
                CONF_NOTIFY_SERVICE: "",
            },
        )

    async def async_step_create_from_template(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Step 1: Select a template category."""
        if user_input is not None:
            if user_input.get("go_back"):
                return await self.async_step_user()
            self._template_category = user_input["template_category"]
            return await self.async_step_template_select()

        lang = normalize_language(self.hass)
        options = [
            selector.SelectOptionDict(
                value=cat_id,
                label=cat.get(f"name_{lang}", cat["name_en"]),
            )
            for cat_id, cat in TEMPLATE_CATEGORIES.items()
        ]

        return self.async_show_form(
            step_id="create_from_template",
            data_schema=vol.Schema(
                {
                    vol.Required("template_category"): selector.SelectSelector(
                        selector.SelectSelectorConfig(
                            options=options,
                            mode=selector.SelectSelectorMode.LIST,
                        )
                    ),
                    vol.Optional("go_back", default=False): selector.BooleanSelector(),
                }
            ),
        )

    async def async_step_template_select(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Step 2: Select a template from the chosen category."""
        if user_input is not None:
            if user_input.get("go_back"):
                return await self.async_step_create_from_template()
            template = get_template_by_id(user_input["template_id"])
            if template is None:
                return self.async_abort(reason="template_not_found")
            self._selected_template = template
            return await self.async_step_template_customize()

        # v2.21: admin-hidden templates stay out of the picker.
        from .templates import get_disabled_template_ids, localize_template_text

        lang = normalize_language(self.hass)
        disabled = get_disabled_template_ids(self.hass)
        templates = [t for t in get_templates_by_category(self._template_category) if t.id not in disabled]
        options = [
            selector.SelectOptionDict(
                value=t.id,
                label=localize_template_text(t.name, lang) or t.name,
            )
            for t in templates
        ]

        return self.async_show_form(
            step_id="template_select",
            data_schema=vol.Schema(
                {
                    vol.Required("template_id"): selector.SelectSelector(
                        selector.SelectSelectorConfig(
                            options=options,
                            mode=selector.SelectSelectorMode.LIST,
                        )
                    ),
                    vol.Optional("go_back", default=False): selector.BooleanSelector(),
                }
            ),
        )

    async def async_step_template_customize(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Step 3: Customize the template before creating the entry."""
        errors: dict[str, str] = {}
        template = self._selected_template
        if template is None:
            return self.async_abort(reason="unknown")

        if user_input is not None:
            if user_input.get("go_back"):
                return await self.async_step_template_select()

            name = user_input[CONF_OBJECT_NAME]

            # Validate unique name (case-insensitive to match slug-based unique_id)
            existing_names = [
                entry.data.get(CONF_OBJECT, {}).get(CONF_OBJECT_NAME, "").lower()
                for entry in self.hass.config_entries.async_entries(DOMAIN)
                if entry.unique_id != GLOBAL_UNIQUE_ID
            ]
            if name.lower() in existing_names:
                errors[CONF_OBJECT_NAME] = "name_exists"
            else:
                # Build object data
                self._object_data = {
                    "id": uuid4().hex,
                    CONF_OBJECT_NAME: name,
                    CONF_OBJECT_AREA: user_input.get(CONF_OBJECT_AREA),
                    CONF_OBJECT_MANUFACTURER: user_input.get(CONF_OBJECT_MANUFACTURER),
                    CONF_OBJECT_MODEL: user_input.get(CONF_OBJECT_MODEL),
                    CONF_OBJECT_SERIAL_NUMBER: user_input.get(CONF_OBJECT_SERIAL_NUMBER),
                }

                # Build tasks from template
                from homeassistant.util import dt as dt_util

                from .helpers.sanitize import cap_object_fields, cap_task_fields

                today_iso = dt_util.now().date().isoformat()
                from .templates import localize_template_text

                create_lang = normalize_language(self.hass)
                self._tasks = {}
                for tt in template.tasks:
                    task_id = uuid4().hex
                    task_data = {
                        "id": task_id,
                        "object_id": self._object_data["id"],
                        "name": localize_template_text(tt.name, create_lang),
                        "type": tt.type,
                        "enabled": True,
                        "schedule_type": tt.schedule_type,
                        "warning_days": tt.warning_days,
                        "history": [],
                        "created_at": today_iso,
                    }
                    if tt.interval_days is not None:
                        task_data["interval_days"] = tt.interval_days
                    if tt.notes:
                        task_data["notes"] = localize_template_text(tt.notes, create_lang)
                    cap_task_fields(task_data)
                    self._tasks[task_id] = task_data
                cap_object_fields(self._object_data)

                self._object_data["task_ids"] = list(self._tasks.keys())

                return await self.async_step_finish()

        return self.async_show_form(
            step_id="template_customize",
            data_schema=vol.Schema(
                {
                    vol.Required(
                        CONF_OBJECT_NAME,
                        default=_localized_template_default_name(template, self.hass),
                    ): selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)),
                    vol.Optional(CONF_OBJECT_AREA): selector.AreaSelector(),
                    vol.Optional(CONF_OBJECT_MANUFACTURER): selector.TextSelector(
                        selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
                    ),
                    vol.Optional(CONF_OBJECT_MODEL): selector.TextSelector(
                        selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
                    ),
                    vol.Optional(CONF_OBJECT_SERIAL_NUMBER): selector.TextSelector(
                        selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
                    ),
                    vol.Optional("go_back", default=False): selector.BooleanSelector(),
                }
            ),
            errors=errors,
            description_placeholders={
                "template_name": template.name,
                "task_count": str(len(template.tasks)),
                "task_list": ", ".join(t.name for t in template.tasks),
            },
        )

    async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Allow user to reconfigure object settings."""
        entry = self._get_reconfigure_entry()
        obj_data = dict(entry.data.get(CONF_OBJECT, {}))
        errors: dict[str, str] = {}

        if user_input is not None:
            name = user_input[CONF_OBJECT_NAME]
            # Validate unique name (skip self)
            for other in self.hass.config_entries.async_entries(DOMAIN):
                if other.entry_id != entry.entry_id and other.unique_id != GLOBAL_UNIQUE_ID:
                    if other.data.get(CONF_OBJECT, {}).get("name", "").lower() == name.lower():
                        errors["base"] = "name_exists"
                        break

            if not errors:
                # Migrate name-slug-based unique_ids BEFORE overwriting the
                # name (see helpers.entity_rename.migrate_object_unique_ids).
                from .helpers.entity_rename import migrate_object_unique_ids

                migrate_object_unique_ids(self.hass, entry, obj_data.get("name"), name)
                obj_data["name"] = name
                obj_data["area_id"] = user_input.get(CONF_OBJECT_AREA)
                obj_data["manufacturer"] = user_input.get(CONF_OBJECT_MANUFACTURER)
                obj_data["model"] = user_input.get(CONF_OBJECT_MODEL)
                obj_data["serial_number"] = user_input.get(CONF_OBJECT_SERIAL_NUMBER)
                obj_data["installation_date"] = user_input.get(CONF_OBJECT_INSTALLATION_DATE)
                obj_data["warranty_expiry"] = user_input.get(CONF_OBJECT_WARRANTY_EXPIRY)
                # v1.4.0 (#43)
                obj_data["documentation_url"] = user_input.get(CONF_OBJECT_DOCUMENTATION_URL) or None
                # v1.4.10 (#46)
                obj_data["notes"] = (user_input.get(CONF_OBJECT_NOTES) or "").strip() or None

                new_data = dict(entry.data)
                new_data[CONF_OBJECT] = obj_data
                return self.async_update_reload_and_abort(entry, data=new_data, title=name)

        suggested: dict[str, Any] = {
            CONF_OBJECT_NAME: obj_data.get("name", ""),
            CONF_OBJECT_MANUFACTURER: obj_data.get("manufacturer", ""),
            CONF_OBJECT_MODEL: obj_data.get("model", ""),
            CONF_OBJECT_SERIAL_NUMBER: obj_data.get("serial_number", ""),
            CONF_OBJECT_DOCUMENTATION_URL: obj_data.get("documentation_url", ""),
            CONF_OBJECT_NOTES: obj_data.get("notes", ""),
        }
        if obj_data.get("area_id"):
            suggested[CONF_OBJECT_AREA] = obj_data["area_id"]
        if obj_data.get("installation_date"):
            suggested[CONF_OBJECT_INSTALLATION_DATE] = obj_data["installation_date"]
        if obj_data.get("warranty_expiry"):
            suggested[CONF_OBJECT_WARRANTY_EXPIRY] = obj_data["warranty_expiry"]

        schema = self.add_suggested_values_to_schema(
            vol.Schema(
                {
                    vol.Required(CONF_OBJECT_NAME): str,
                    vol.Optional(CONF_OBJECT_AREA): selector.AreaSelector(),
                    vol.Optional(CONF_OBJECT_MANUFACTURER): str,
                    vol.Optional(CONF_OBJECT_MODEL): str,
                    vol.Optional(CONF_OBJECT_SERIAL_NUMBER): str,
                    vol.Optional(
                        CONF_OBJECT_INSTALLATION_DATE,
                    ): selector.DateSelector(),
                    vol.Optional(
                        CONF_OBJECT_WARRANTY_EXPIRY,
                    ): selector.DateSelector(),
                    # v1.4.0 (#43): place under serial_number per the request
                    vol.Optional(CONF_OBJECT_DOCUMENTATION_URL): selector.TextSelector(
                        selector.TextSelectorConfig(type=selector.TextSelectorType.URL)
                    ),
                    # v1.4.10 (#46): free-form notes (multiline)
                    vol.Optional(CONF_OBJECT_NOTES): selector.TextSelector(
                        selector.TextSelectorConfig(
                            type=selector.TextSelectorType.TEXT,
                            multiline=True,
                        )
                    ),
                }
            ),
            suggested,
        )

        return self.async_show_form(
            step_id="reconfigure",
            data_schema=schema,
            errors=errors,
            description_placeholders={"name": entry.title},
        )

    async def async_step_websocket(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Handle object creation from the WebSocket API (no UI)."""
        from homeassistant.util import dt as dt_util

        from .helpers.sanitize import cap_object_fields, cap_task_fields

        if user_input is None:
            return self.async_abort(reason="missing_data")

        obj_data = dict(user_input.get(CONF_OBJECT, {}))
        cap_object_fields(obj_data)
        object_name = obj_data.get(CONF_OBJECT_NAME, "Unknown")
        object_slug = slugify_object_name(object_name)

        await self.async_set_unique_id(f"maintenance_supporter_{object_slug}")
        self._abort_if_unique_id_configured()

        obj_data.setdefault("task_ids", [])

        # Stamp `created_at` on imported tasks that lack it so next_due has a
        # stable anchor (issue #30). Imports from CSV/JSON go through this
        # chokepoint regardless of format. Cap every task's strings so
        # imports can't bypass the WS-schema length limits.
        today_iso = dt_util.now().date().isoformat()
        tasks = dict(user_input.get(CONF_TASKS, {}))
        for task_id, td in list(tasks.items()):
            if not isinstance(td, dict):
                continue
            new_td = dict(td)
            if "created_at" not in new_td:
                new_td["created_at"] = today_iso
            cap_task_fields(new_td)
            # Store recurrence in the canonical nested `schedule` shape — this is
            # the CSV/JSON import chokepoint (schedule-model v2).
            tasks[task_id] = normalize_task_storage(new_td)

        # Spare parts: re-validate each imported definition through the same
        # normalizer the WS CRUD uses (bad entries are dropped, not fatal).
        parts_in = user_input.get("parts")
        parts: dict[str, dict[str, Any]] = {}
        if isinstance(parts_in, dict):
            from .helpers.parts import PartValidationError, normalize_part

            for pid, praw in parts_in.items():
                try:
                    part = normalize_part({**praw, "id": pid})
                except (PartValidationError, TypeError):
                    continue
                parts[part["id"]] = part

        data: dict[str, Any] = {
            CONF_OBJECT: obj_data,
            CONF_TASKS: tasks,
        }
        if parts:
            data["parts"] = parts
        return self.async_create_entry(title=object_name, data=data)

    async def async_step_create_object(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Create a new maintenance object."""
        errors: dict[str, str] = {}

        if user_input is not None:
            if user_input.get("go_back"):
                return await self.async_step_user()

            name = user_input[CONF_OBJECT_NAME]

            # Validate unique name (case-insensitive to match slug-based unique_id)
            existing_names = [
                entry.data.get(CONF_OBJECT, {}).get(CONF_OBJECT_NAME, "").lower()
                for entry in self.hass.config_entries.async_entries(DOMAIN)
                if entry.unique_id != GLOBAL_UNIQUE_ID
            ]
            if name.lower() in existing_names:
                errors[CONF_OBJECT_NAME] = "name_exists"
            else:
                from .helpers.sanitize import cap_object_fields

                self._object_data = {
                    "id": uuid4().hex,
                    CONF_OBJECT_NAME: name,
                    CONF_OBJECT_AREA: user_input.get(CONF_OBJECT_AREA),
                    CONF_OBJECT_MANUFACTURER: user_input.get(CONF_OBJECT_MANUFACTURER),
                    CONF_OBJECT_MODEL: user_input.get(CONF_OBJECT_MODEL),
                    CONF_OBJECT_SERIAL_NUMBER: user_input.get(CONF_OBJECT_SERIAL_NUMBER),
                    CONF_OBJECT_INSTALLATION_DATE: user_input.get(CONF_OBJECT_INSTALLATION_DATE),
                    CONF_OBJECT_WARRANTY_EXPIRY: user_input.get(CONF_OBJECT_WARRANTY_EXPIRY),
                    # v1.4.0 (#43)
                    CONF_OBJECT_DOCUMENTATION_URL: user_input.get(CONF_OBJECT_DOCUMENTATION_URL) or None,
                    # v1.4.10 (#46)
                    CONF_OBJECT_NOTES: ((user_input.get(CONF_OBJECT_NOTES) or "").strip() or None),
                }
                cap_object_fields(self._object_data)
                self._tasks = {}
                return await self.async_step_task_menu()

        return self.async_show_form(
            step_id="create_object",
            data_schema=vol.Schema(
                {
                    vol.Required(CONF_OBJECT_NAME): selector.TextSelector(
                        selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
                    ),
                    vol.Optional(CONF_OBJECT_AREA): selector.AreaSelector(),
                    vol.Optional(CONF_OBJECT_MANUFACTURER): selector.TextSelector(
                        selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
                    ),
                    vol.Optional(CONF_OBJECT_MODEL): selector.TextSelector(
                        selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
                    ),
                    vol.Optional(CONF_OBJECT_SERIAL_NUMBER): selector.TextSelector(
                        selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)
                    ),
                    vol.Optional(CONF_OBJECT_INSTALLATION_DATE): selector.DateSelector(),
                    vol.Optional(CONF_OBJECT_WARRANTY_EXPIRY): selector.DateSelector(),
                    # v1.4.0 (#43): place under serial_number per the request
                    vol.Optional(CONF_OBJECT_DOCUMENTATION_URL): selector.TextSelector(
                        selector.TextSelectorConfig(type=selector.TextSelectorType.URL)
                    ),
                    # v1.4.10 (#46): free-form notes (multiline)
                    vol.Optional(CONF_OBJECT_NOTES): selector.TextSelector(
                        selector.TextSelectorConfig(
                            type=selector.TextSelectorType.TEXT,
                            multiline=True,
                        )
                    ),
                    vol.Optional("go_back", default=False): selector.BooleanSelector(),
                }
            ),
            errors=errors,
        )

    async def async_step_task_menu(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Show menu to add tasks or finish."""
        return self.async_show_menu(
            step_id="task_menu",
            menu_options=["add_task", "finish"],
            description_placeholders={
                "object_name": self._object_data.get(CONF_OBJECT_NAME, ""),
                "task_count": str(len(self._tasks)),
            },
        )

    async def async_step_add_task(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Add a maintenance task."""
        return await self._schedule_add_task(
            user_input,
            step_id="add_task",
            on_go_back=self.async_step_task_menu,
            time_based_step=self.async_step_time_based,
            calendar_step=self.async_step_calendar,
            sensor_step=self.async_step_sensor_select,
            one_time_step=self.async_step_one_time,
            manual_step=self.async_step_manual,
            seed_id=True,
            description_placeholders={
                "object_name": self._object_data.get(CONF_OBJECT_NAME, ""),
            },
        )

    async def async_step_time_based(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure time-based schedule."""
        return await self._schedule_time_based(
            user_input,
            step_id="time_based",
            on_go_back=self.async_step_add_task,
            on_complete=self._save_task_and_return,
        )

    async def async_step_calendar(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure a calendar recurrence kind (weekdays / nth_weekday /
        day_of_month) during initial setup."""
        return await self._schedule_calendar(
            user_input,
            step_id="calendar",
            on_go_back=self.async_step_add_task,
            on_complete=self._save_task_and_return,
        )

    async def async_step_one_time(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure a one-time (non-recurring) task."""
        return await self._schedule_one_time(
            user_input,
            step_id="one_time",
            on_go_back=self.async_step_add_task,
            on_complete=self._save_task_and_return,
        )

    # --- Sensor trigger steps (thin wrappers delegating to TriggerConfigMixin) ---

    async def async_step_sensor_select(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Select sensor entity for trigger."""
        self._on_cancel = lambda: self.async_step_add_task()
        return await self._trigger_sensor_select(
            user_input,
            step_id="sensor_select",
            next_step=self.async_step_sensor_attribute,
        )

    async def async_step_sensor_attribute(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Select attribute to monitor."""
        self._on_cancel = lambda: self.async_step_sensor_select()
        return await self._trigger_sensor_attribute(
            user_input,
            step_id="sensor_attribute",
            next_step=self.async_step_trigger_type,
            error_step_id="sensor_select",
        )

    async def async_step_trigger_type(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Select trigger type."""
        self._on_cancel = lambda: self.async_step_sensor_attribute()
        return await self._trigger_type_select(
            user_input,
            step_id="trigger_type",
            threshold_step=self.async_step_trigger_threshold,
            counter_step=self.async_step_trigger_counter,
            state_change_step=self.async_step_trigger_state_change,
            runtime_step=self.async_step_trigger_runtime,
            compound_step=self.async_step_compound_logic,
        )

    async def async_step_trigger_threshold(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure threshold trigger."""
        self._on_cancel = lambda: self.async_step_trigger_type()
        return await self._trigger_threshold_config(
            user_input,
            step_id="trigger_threshold",
            on_complete=self._save_task_and_return,
        )

    async def async_step_trigger_counter(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure counter trigger."""
        self._on_cancel = lambda: self.async_step_trigger_type()
        return await self._trigger_counter_config(
            user_input,
            step_id="trigger_counter",
            on_complete=self._save_task_and_return,
        )

    async def async_step_trigger_state_change(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure state change trigger."""
        self._on_cancel = lambda: self.async_step_trigger_type()
        return await self._trigger_state_change_config(
            user_input,
            step_id="trigger_state_change",
            on_complete=self._save_task_and_return,
        )

    async def async_step_trigger_runtime(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure runtime trigger."""
        self._on_cancel = lambda: self.async_step_trigger_type()
        return await self._trigger_runtime_config(
            user_input,
            step_id="trigger_runtime",
            on_complete=self._save_task_and_return,
        )

    # --- Compound Trigger Steps ---

    async def async_step_compound_logic(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Select compound trigger logic."""
        self._on_cancel = lambda: self.async_step_trigger_type()
        return await self._trigger_compound_logic(
            user_input,
            step_id="compound_logic",
            next_step=self.async_step_compound_condition_entity,
        )

    async def async_step_compound_condition_entity(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Select entity for compound condition."""
        if getattr(self, "_compound_conditions", []):
            self._on_cancel = lambda: self.async_step_compound_review()
        else:
            self._on_cancel = lambda: self.async_step_compound_logic()
        return await self._trigger_compound_condition_entity(
            user_input,
            step_id="compound_condition_entity",
            next_step=self.async_step_compound_condition_type,
        )

    async def async_step_compound_condition_type(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Select trigger type for compound condition."""
        self._on_cancel = lambda: self.async_step_compound_condition_entity()
        return await self._trigger_compound_condition_type(
            user_input,
            step_id="compound_condition_type",
            threshold_step=self.async_step_compound_condition_threshold,
            counter_step=self.async_step_compound_condition_counter,
            state_change_step=self.async_step_compound_condition_state_change,
            runtime_step=self.async_step_compound_condition_runtime,
        )

    async def async_step_compound_condition_threshold(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure threshold for compound condition."""
        self._on_cancel = lambda: self.async_step_compound_condition_type()
        return await self._trigger_compound_condition_config(
            user_input,
            "threshold",
            step_id="compound_condition_threshold",
            on_complete=self.async_step_compound_review,
        )

    async def async_step_compound_condition_counter(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure counter for compound condition."""
        self._on_cancel = lambda: self.async_step_compound_condition_type()
        return await self._trigger_compound_condition_config(
            user_input,
            "counter",
            step_id="compound_condition_counter",
            on_complete=self.async_step_compound_review,
        )

    async def async_step_compound_condition_state_change(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure state_change for compound condition."""
        self._on_cancel = lambda: self.async_step_compound_condition_type()
        return await self._trigger_compound_condition_config(
            user_input,
            "state_change",
            step_id="compound_condition_state_change",
            on_complete=self.async_step_compound_review,
        )

    async def async_step_compound_condition_runtime(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure runtime for compound condition."""
        self._on_cancel = lambda: self.async_step_compound_condition_type()
        return await self._trigger_compound_condition_config(
            user_input,
            "runtime",
            step_id="compound_condition_runtime",
            on_complete=self.async_step_compound_review,
        )

    async def async_step_compound_review(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Review compound trigger conditions."""
        self._on_cancel = lambda: self.async_step_compound_logic()
        return await self._trigger_compound_review(
            user_input,
            step_id="compound_review",
            add_condition_step=self.async_step_compound_condition_entity,
            on_complete=self._save_task_and_return,
        )

    # --- Manual & Finish ---

    async def async_step_manual(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Configure manual schedule."""
        return await self._schedule_manual(
            user_input,
            step_id="manual",
            on_go_back=self.async_step_add_task,
            on_complete=self._save_task_and_return,
        )

    async def async_step_finish(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
        """Finish the object setup and create the config entry."""
        if not self._tasks:
            # No tasks defined: go back to task menu with error
            return self.async_show_menu(
                step_id="task_menu",
                menu_options=["add_task", "finish"],
                description_placeholders={
                    "object_name": self._object_data.get(CONF_OBJECT_NAME, ""),
                    "task_count": "0",
                },
            )

        object_name = self._object_data.get(CONF_OBJECT_NAME, "Unknown")
        object_slug = slugify_object_name(object_name)

        await self.async_set_unique_id(f"maintenance_supporter_{object_slug}")
        self._abort_if_unique_id_configured()

        # Add task_ids to object
        self._object_data["task_ids"] = list(self._tasks.keys())

        return self.async_create_entry(
            title=object_name,
            data={
                CONF_OBJECT: self._object_data,
                # Store recurrence in the canonical nested `schedule` shape.
                CONF_TASKS: {tid: normalize_task_storage(td) for tid, td in self._tasks.items()},
            },
        )

    def _save_task_and_return(self) -> ConfigFlowResult:
        """Save the current task and return to task menu."""
        from .config_flow_schedule import build_new_task_record

        task_id = self._current_task.get("id", uuid4().hex)
        task_data = build_new_task_record(
            self._current_task,
            task_id=task_id,
            object_id=self._object_data.get("id", ""),
            hass=self.hass,
            # No entry (and thus no Store) exists yet during setup — history and
            # a backdated last_performed must ride entry.data.
            seed_history=True,
            include_last_performed=True,
        )
        self._tasks[task_id] = task_data
        self._current_task = {}

        _LOGGER.debug("Task saved: %s (total: %d)", task_data["name"], len(self._tasks))

        # Return to task menu using show_menu (not await)
        return self.async_show_menu(
            step_id="task_menu",
            menu_options=["add_task", "finish"],
            description_placeholders={
                "object_name": self._object_data.get(CONF_OBJECT_NAME, ""),
                "task_count": str(len(self._tasks)),
            },
        )

    @staticmethod
    @callback
    def async_get_options_flow(
        config_entry: ConfigEntry,
    ) -> OptionsFlow:
        """Get the options flow for this handler."""
        from .config_flow_options_global import GlobalOptionsFlow
        from .config_flow_options_task import MaintenanceOptionsFlow

        if config_entry.unique_id == GLOBAL_UNIQUE_ID:
            return GlobalOptionsFlow()
        return MaintenanceOptionsFlow()
