from typing import Any, Callable, TypeVar, overload

import click

from sayer.core.help import render_help_for_command
from sayer.utils.ui import RichGroup

T = TypeVar("T", bound=Callable[..., Any])


class RichCommand(click.Command):
    """
    A custom click.Command that renders help via Sayer's rich help renderer.
    """

    def get_help(self, ctx: click.Context) -> str:
        render_help_for_command(ctx)
        # click.Command.get_help is expected to return a string, though our
        # render_help_for_command likely prints directly. If render_help_for_command
        # does not return a string, you might need to adjust its signature or
        # how it's called to satisfy the expected return type of get_help.
        # For now, let's assume it handles the output and we return an empty string
        # if no explicit string is generated by render_help_for_command.
        return ""


class Sayer:
    """
    A Sayer application object that wraps a RichGroup and ensures all
    commands use RichCommand for help rendering.
    """

    def __init__(
        self,
        name: str | None = None,
        help: str | None = None,
        epilog: str | None = None,
        context_settings: dict | None = None,
        add_version_option: bool = False,
        version: str | None = None,
        group_class: type[click.Group] = RichGroup,
        command_class: type[click.Command] = RichCommand,
        **group_attrs: Any,
    ) -> None:
        # Prepare group attributes
        attrs: dict = {}
        if help is not None:
            attrs["help"] = help
        if epilog is not None:
            attrs["epilog"] = epilog
        if context_settings:
            attrs["context_settings"] = context_settings
        attrs.update(group_attrs)

        # Instantiate the RichGroup
        group = group_class(name=name, **attrs)
        # Ensure every subcommand uses RichCommand
        group.command_class = command_class

        # Optionally add a --version flag
        if add_version_option:
            if not version:
                raise ValueError("`version` must be provided when `add_version_option=True`.")
            # sayer/app.py:53: error: Unused "type: ignore" comment [unused-ignore]
            # Remove the unused type: ignore comment, as the type checker should
            # be able to infer the type correctly after adding other annotations.
            group = click.version_option(version, "--version", "-V")(group)

        self._group: click.Group = group  # Add type annotation for _group

    @overload
    def command(self, f: T) -> T: ...

    @overload
    def command(self, *args: Any, **kwargs: Any) -> Callable[[T], T]: ...

    def command(self, *args: Any, **kwargs: Any) -> Any:
        """
        A decorator to register a function as a subcommand.
        Uses the underlying RichGroup.command(), so RichCommand is applied.
        """
        return self._group.command(*args, **kwargs)

    def add_app(self, alias: str, app: "Sayer") -> None:
        """
        Nest another Sayer app under this as a sub-group. This is just an alias
        to add_sayer() for clarity.
        """
        self.add_sayer(alias, app)

    def add_sayer(self, alias: str, app: "Sayer") -> None:
        """
        Nest another Sayer app under this as a sub-group.
        """
        self._group.add_command(app._group, name=alias)

    def run(self, args: list[str] | None = None) -> Any:  # click.Group returns Any
        """
        Invoke the CLI application.
        """
        self._group(prog_name=self._group.name, args=args)

    def __call__(self, args: list[str] | None = None) -> Any:
        return self.run(args)

    @property
    def cli(self) -> click.Group:
        """
        Access the underlying RichGroup instance.
        """
        return self._group

    def complete(self, ctx: click.Context, incomplete: str) -> list[str]:
        """
        Get shell completion suggestions.
        """
        return list(self._group.complete(ctx, incomplete))
