"""CLI entry point for scry-run."""

import sys
import click
from rich.console import Console
from typing import Optional

from scry_run.cli.init import init

console = Console()
from scry_run.cli.cache import cache
from scry_run.cli.run import run
from scry_run.cli.env import env
from scry_run.cli.apps import list_apps, which_app, rm_app, reset_app, info_app
from scry_run.cli.config_cmd import config_cmd
from scry_run.generator import CodeGenerator, ScryRunError


class DefaultGroup(click.Group):
    """Click Group that invokes a default command for unknown commands."""
    
    def __init__(self, *args, **kwargs):
        # To support default command, we need to allow resolving unknown commands
        # to the default handler
        self.default_cmd_name = kwargs.pop("default_if_missing", None)
        super().__init__(*args, **kwargs)

    def resolve_command(self, ctx, args):
        try:
            # Try to resolve the command normally
            return super().resolve_command(ctx, args)
        except click.UsageError as e:
            # If command not found and we have a default...
            if self.default_cmd_name:
                # check if the command actually exists in our list (to be safe)
                cmd = self.get_command(ctx, self.default_cmd_name)
                if cmd:
                    # If we are falling back to default, we treat ALL args
                    # as arguments to the default command.
                    # This is tricky because Click removed the command name from args
                    # if it thought it was a command but failed.
                    # But here, 'args' passed to resolve_command are the remaining args.
                    
                    # If the first arg was what Click thought was a command name,
                    # we need to put it back into the args list for the default command
                    # if we want the default command to see it.
                    # actually resolve_command returns (cmd_name, cmd, args)
                    # The 'args' passed IN are the ones to be processed.
                    
                    # We just return the default command and the full args.
                    return self.default_cmd_name, cmd, args
            
            # If no default or other error, raise original
            raise


@click.group(cls=DefaultGroup, default_if_missing="ask", context_settings={"ignore_unknown_options": True})
@click.version_option()
def main() -> None:
    """scry-run: LLM-powered dynamic code generation.
    
    Use a metaclass to automatically generate missing code via LLMs.
    """
    pass


@main.command(hidden=True, context_settings={"ignore_unknown_options": True})
@click.argument("prompt_parts", nargs=-1)
def ask(prompt_parts: tuple[str, ...]) -> None:
    """Default command: Ask the coding assistant."""
    # Collect all parts including unknown options causing confusion
    ctx = click.get_current_context()
    
    # If ignore_unknown_options=True, unparsed options are in ctx.args (as a list)
    # prompt_parts contains the positional arguments
    
    # We need to reconstruct the order if possible, or just append.
    # Typically ctx.args contains options that were skipped.
    # But since we want "everything as string", order matters slightly but appending is likely safe enough for natural language.
    # However, for perfect reconstruction, we might look at sys.argv keywords? NO.
    
    # Let's combine them. prompt_parts are the recognized parts (non-options).
    # ctx.args are the ignored options.
    # Wait, Click splits them.
    # If user types: scry-run --flag word
    # --flag goes to ctx.args (if ignored), word goes to prompt_parts.
    
    parent_args = ctx.parent.args if ctx.parent else []
    current_args = ctx.args
    
    full_parts = list(parent_args) + list(prompt_parts) + list(current_args)
    
    if not full_parts:
        # If no arguments, show help
        # Note: ctx.parent.get_help() returns the help string for the group (main)
        click.echo(ctx.parent.get_help())
        ctx.exit(0)
    
    # Simple join might be messy if flags were interspersed. 
    # But "write a poem -f json" -> "write a poem" + "-f" + "json"?
    # Click might parse "-f" as option, "json" as argument.
    # prompt_parts=["write", "a", "poem", "json"], ctx.args=["-f"]
    # So "write a poem json -f". Acceptable for LLM prompt usually.
    
    prompt = " ".join(full_parts)
    
    try:
        generator = CodeGenerator()
        response = generator.generate_freeform(prompt)
        console.print(response)

    except ScryRunError as e:
        console.print(f"[red]Error:[/red] {e.message}", highlight=False)
        if e.hint:
            console.print(f"[dim]Hint:[/dim] {e.hint}", highlight=False)
        sys.exit(1)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}", highlight=False)
        sys.exit(1)


# Register commands
main.add_command(init)
main.add_command(init, name="new")  # Alias
main.add_command(cache)
main.add_command(run)
main.add_command(env)
main.add_command(list_apps, name="list")
main.add_command(which_app, name="which")
main.add_command(rm_app, name="rm")
main.add_command(reset_app, name="reset")
main.add_command(info_app, name="info")
main.add_command(config_cmd, name="config")


if __name__ == "__main__":
    main()
