"""Reusable Rich Prompt subclasses and prompt helper functions for terminal input."""

from __future__ import annotations

from datetime import date

from rich.console import Console
from rich.prompt import FloatPrompt, InvalidResponse, Prompt


class SliderPrompt(FloatPrompt):
    """FloatPrompt that enforces min/max range. Raise InvalidResponse if out of range."""

    def __init__(
        self,
        prompt: str,
        *,
        min_value: float,
        max_value: float,
        console: Console | None = None,
    ) -> None:
        """Initialise with prompt text and min/max range."""
        super().__init__(prompt, console=console)
        self.min_value = min_value
        self.max_value = max_value

    def process_response(self, value: str) -> float:
        """Parse float and validate it is within min/max range."""
        result = super().process_response(value)
        if not (self.min_value <= result <= self.max_value):
            raise InvalidResponse(
                f"[prompt.invalid]Please enter a value between {self.min_value} and {self.max_value}"
            )
        return result

    @classmethod
    def ask_range(
        cls,
        label: str,
        *,
        min_value: float,
        max_value: float,
        default: float,
        console: Console | None = None,
    ) -> float:
        """Ask for a float within the given range. Builds prompt from label and range."""
        prompt = f"[green]{label}[/green] [cyan]({min_value} - {max_value})[/cyan]"
        _prompt = cls(
            prompt,
            min_value=min_value,
            max_value=max_value,
            console=console,
        )
        return _prompt(default=default, stream=None)


class ButtonsPrompt(Prompt):
    """Prompt that shows a numbered list and returns the selected option string."""

    def __init__(
        self,
        options: list[str],
        *,
        label: str | None = None,
        console: Console | None = None,
    ) -> None:
        """Initialise with options and optional label."""
        if not options:
            raise ValueError("options must be non-empty")
        self._options = options
        self._label = label
        n = len(options)
        prompt_text = f"[green]Enter choice[/green] [cyan](1-{n})[/cyan]"
        super().__init__(prompt_text, console=console, show_default=False)

    def pre_prompt(self) -> None:
        """Display label and numbered list before the prompt."""
        if self._label is not None:
            self.console.print(f"[green]{self._label}[/green]")
        for i, opt in enumerate(self._options, 1):
            self.console.print(f"  {i}. [cyan]{opt}[/cyan]")

    def process_response(self, value: str) -> str:
        """Parse choice number and return the selected option string."""
        try:
            choice = int(value)
        except ValueError:
            raise InvalidResponse(
                f"[prompt.invalid]Please enter a number between 1 and {len(self._options)}"
            ) from None
        if not (1 <= choice <= len(self._options)):
            raise InvalidResponse(
                f"[prompt.invalid]Please enter a number between 1 and {len(self._options)}"
            )
        return self._options[choice - 1]


class DatePrompt(Prompt):
    """Prompt that validates YYYY-MM-DD and returns the date string."""

    def __init__(
        self,
        label: str,
        *,
        default_date: date,
        console: Console | None = None,
    ) -> None:
        """Initialise with label and default date."""
        self._default_date = default_date
        prompt_text = f"[green]{label}[/green]"
        super().__init__(prompt_text, console=console)

    def process_response(self, value: str) -> str:
        """Validate ISO date and return the string."""
        try:
            date.fromisoformat(value)
        except ValueError:
            raise InvalidResponse(
                "[prompt.invalid]Please enter a valid date (YYYY-MM-DD)"
            ) from None
        return value


def prompt_text(console: Console, message: str) -> str:
    """Prompt for a single line of text. Returns the string entered."""
    return Prompt.ask(f"[green]{message}[/green]", console=console)


def prompt_date(console: Console, label: str, default_date: date) -> str:
    """Prompt for a date (YYYY-MM-DD), re-prompt until valid. Returns ISO date string."""
    prompt = DatePrompt(label, default_date=default_date, console=console)
    return prompt(default=default_date.isoformat(), stream=None)


def prompt_buttons(
    console: Console,
    options: list[str],
    label: str | None = None,
) -> str:
    """Show numbered options and prompt for a choice (1-n). Returns the selected option string."""
    prompt = ButtonsPrompt(options, label=label, console=console)
    return prompt(stream=None)


def prompt_slider(
    console: Console,
    label: str,
    min_value: float,
    max_value: float,
    default: float,
) -> float:
    """Show slider prompt (label, range, default) and return validated float in range."""
    return SliderPrompt.ask_range(
        label,
        min_value=min_value,
        max_value=max_value,
        default=default,
        console=console,
    )
