import inspect
from typing import Any

import click

from sayer.core.commands.base import BaseSayerCommand
from sayer.core.console.sayer import render_help_for_command
from sayer.state import State


class SayerCommand(BaseSayerCommand):
    """
    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, we 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 ""

    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        """
        Programmatic invocation that reuses Sayer's wrapper:
          - activates a Click context
          - lets @click.pass_context inject ctx
          - maps positional args to parameter names
          - preserves Sayer features (state, hooks, type molding, async handling)
        """
        # Establish an active Click context so pass_context can inject it.
        with click.Context(self, info_name=self.name):
            # If positional args were provided, map them to parameter names
            if args:
                original = getattr(self.callback, "_original_func", self.callback)
                if original is None:
                    raise TypeError("Positional arguments are not supported for this command.")

                signature = inspect.signature(original)
                # Build the ordered list of user-facing parameters (exclude injected ones)
                ordered_params = []
                for p in signature.parameters.values():
                    # skip injected click.Context
                    if p.annotation is click.Context:
                        continue
                    # skip injected State subclasses
                    ann = p.annotation
                    if isinstance(ann, type):
                        try:
                            if issubclass(ann, State):
                                continue
                        except Exception:
                            pass
                    ordered_params.append(p)

                if len(args) > len(ordered_params):
                    raise TypeError(
                        f"{original.__name__}() takes {len(ordered_params)} positional "
                        f"argument(s) but {len(args)} were given"
                    )

                # Assign positionals into kwargs by parameter order
                for value, p in zip(args, ordered_params, strict=False):
                    if p.name in kwargs:
                        raise TypeError(f"Multiple values for argument '{p.name}'")
                    kwargs[p.name] = value

            kwargs["_sayer_natural_call"] = True

            # Filter out silent params
            param_map = {p.name: p for p in self.params}
            filtered_kwargs = {
                k: v for k, v in kwargs.items() if k not in param_map or getattr(param_map[k], "expose_value", True)
            }

            # If we're inside an asyncio event loop and the underlying function is async,
            # Activate a transient context so pass_context can inject it
            with click.Context(self, info_name=self.name):
                return self.callback(**filtered_kwargs)
