"""
rich-click is a minimal Python module to combine the efforts of the excellent packages 'rich' and 'click'.

The intention is to provide attractive help output from click, formatted with rich, with minimal
customisation required.
"""

from typing import Any, Callable, cast, Dict, Optional, overload, Type, TYPE_CHECKING, TypeVar, Union

from click import Command
from click import command as click_command
from click import Group
from click import group as click_group
from click import pass_context as click_pass_context
from rich.console import Console
from typing_extensions import Concatenate, ParamSpec

from . import rich_click  # noqa: F401

from rich_click._compat_click import CLICK_IS_BEFORE_VERSION_8X
from rich_click.rich_command import RichCommand, RichGroup, RichMultiCommand  # noqa: F401
from rich_click.rich_context import RichContext
from rich_click.rich_help_configuration import RichHelpConfiguration

# MyPy does not like star imports. Therefore when we are type checking, we import each individual module
# from click here. This way MyPy will recognize the import and not throw any errors. Furthermore, because of
# the TYPE_CHECKING check, it does not influence the start routine at all.

_AnyCallable = Callable[..., Any]
F = TypeVar("F", bound=Callable[..., Any])
FC = TypeVar("FC", bound=Union[Command, _AnyCallable])


GrpType = TypeVar("GrpType", bound=Group)


# variant: no call, directly as decorator for a function.
@overload
def group(name: _AnyCallable) -> RichGroup:
    ...


# variant: with positional name and with positional or keyword cls argument:
# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...)
@overload
def group(
    name: Optional[str],
    cls: Type[GrpType],
    **attrs: Any,
) -> Callable[[_AnyCallable], GrpType]:
    ...


# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...)
@overload
def group(
    name: None = None,
    *,
    cls: Type[GrpType],
    **attrs: Any,
) -> Callable[[_AnyCallable], GrpType]:
    ...


# variant: with optional string name, no cls argument provided.
@overload
def group(name: Optional[str] = ..., cls: None = None, **attrs: Any) -> Callable[[_AnyCallable], RichGroup]:
    ...


def group(
    name: Union[str, _AnyCallable, None] = None,
    cls: Optional[Type[GrpType]] = None,
    **attrs: Any,
) -> Union[Group, Callable[[_AnyCallable], Union[RichGroup, GrpType]]]:
    """
    Group decorator function.

    Defines the group() function so that it uses the RichGroup class by default.
    """
    if cls is None:
        cls = cast(Type[GrpType], RichGroup)

    if callable(name):
        return command(cls=cls, **attrs)(name)

    return command(name, cls, **attrs)


CmdType = TypeVar("CmdType", bound=Command)


# variant: no call, directly as decorator for a function.
@overload
def command(name: _AnyCallable) -> RichCommand:
    ...


# variant: with positional name and with positional or keyword cls argument:
# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...)
@overload
def command(
    name: Optional[str],
    cls: Type[CmdType],
    **attrs: Any,
) -> Callable[[_AnyCallable], CmdType]:
    ...


# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...)
@overload
def command(
    name: None = None,
    *,
    cls: Type[CmdType],
    **attrs: Any,
) -> Callable[[_AnyCallable], CmdType]:
    ...


# variant: with optional string name, no cls argument provided.
@overload
def command(name: Optional[str] = ..., cls: None = None, **attrs: Any) -> Callable[[_AnyCallable], RichCommand]:
    ...


def command(
    name: Union[Optional[str], _AnyCallable] = None,
    cls: Optional[Type[CmdType]] = None,
    **attrs: Any,
) -> Union[Command, Callable[[_AnyCallable], Union[RichCommand, CmdType]]]:
    """
    Command decorator function.

    Defines the command() function so that it uses the RichCommand class by default.
    """
    if cls is None:
        cls = cast(Type[CmdType], RichCommand)

    if callable(name):
        return click_command(cls=cls, **attrs)(name)

    return click_command(name, cls=cls, **attrs)


class NotSupportedError(Exception):
    """Not Supported Error."""

    pass


def rich_config(
    console: Optional[Console] = None, help_config: Optional[RichHelpConfiguration] = None
) -> Callable[[FC], FC]:
    """Use decorator to configure Rich Click settings.

    Args:
        console: A Rich Console that will be accessible from the `RichContext`, `RichCommand`, and `RichGroup` instances
            Defaults to None.
        help_config: Rich help configuration that is used internally to format help messages and exceptions
            Defaults to None.
    """
    if CLICK_IS_BEFORE_VERSION_8X:

        def decorator_with_warning(obj: FC) -> FC:
            import warnings

            warnings.warn(
                "`rich_config()` does not work with versions of click prior to version 8.0.0."
                " Please update to a newer version of click to use this functionality.",
                RuntimeWarning,
            )
            return obj

        return decorator_with_warning

    def decorator(obj: FC) -> FC:
        extra: Dict[str, Any] = {}
        if console is not None:
            extra["rich_console"] = console
        if help_config is not None:
            extra["rich_help_config"] = help_config

        if isinstance(obj, (RichCommand, RichGroup)):
            obj.context_settings.update(extra)
        elif callable(obj) and not isinstance(obj, (Command, Group)):
            if hasattr(obj, "__rich_context_settings__"):
                obj.__rich_context_settings__.update(extra)
            else:
                setattr(obj, "__rich_context_settings__", extra)
        else:
            raise NotSupportedError("`rich_config` requires a `RichCommand` or `RichGroup`. Try using the cls keyword")
        return obj

    return decorator


# Users of rich_click would face issues using mypy with this code,
# if not for wrapping `pass_context` with a new function signature:
#
# @click.command()
# @click.pass_context
# def cli(ctx: click.RichContext) -> None:
#    ...


P = ParamSpec("P")
R = TypeVar("R")


def pass_context(f: Callable[Concatenate[RichContext, P], R]) -> Callable[P, R]:
    # flake8: noqa: D400,D401
    """Marks a callback as wanting to receive the current context
    object as first argument.
    """
    return click_pass_context(f)  # type: ignore[arg-type]
