"""Init command for creating new scry-run projects."""

import json
from pathlib import Path

import click
from rich.console import Console
from rich.panel import Panel
from rich.prompt import Prompt, Confirm

from scry_run.home import ensure_home_exists, get_app_dir
from scry_run.packages import ensure_scry_run_installed

console = Console()


EXPAND_PROMPT = '''You are helping design a CLI application. The user provided a brief description, and you need to expand it into a clear, detailed description that will guide code generation.

App name: {name}
User's description: {description}

Expand this into a clear app description that includes:

1. **Core Purpose** (1-2 sentences) - What the app does at its heart

2. **Key Features** (2-4 bullet points) - Features that directly support the core purpose:
   - Focus on what the user actually asked for
   - Only add features that are ESSENTIAL to the core purpose
   - Do NOT invent extra features the user didn't mention
   - Keep it minimal and focused

3. **Usage Examples** (2-3 examples) - Show concrete CLI invocations like:
   ```
   {name} <command> [args]
   ```
   Keep examples simple and directly relevant to the core purpose.

IMPORTANT GUIDELINES:
- STICK TO WHAT THE USER ASKED FOR - don't add unrelated features
- Less is more - a focused app is better than a bloated one
- If the user said "todo list", make a todo list - not a project management suite
- Total description length: 100-200 words (keep it concise!)

**OPEN WORLD PRINCIPLE**: This app uses scry-run for dynamic code generation. Methods are generated on-demand at runtime, meaning the set of commands is effectively unlimited - but inputs must still be STRUCTURED like a normal CLI:
- Use standard CLI patterns: commands, subcommands, flags, and positional arguments
- The vocabulary of commands/arguments is open-ended, but the SYNTAX is structured
- NOT a natural language interface - avoid examples like "how do I..." or question-form inputs
- Example for "hello" app:
  - GOOD: `hello korean`, `hello --lang=french --formal`, `hello random`
  - BAD: `hello how do you say goodbye in korean?` (natural language query)
- Example for "todo" app:
  - GOOD: `todo add "buy milk"`, `todo list --due=today`, `todo done 3`
  - BAD: `todo what's due today?` (question form)

**APP FLAGS** - Recommend runtime flags for the app:
- "quiet": true - For TUI/interactive apps (curses, textual, rich.live, pygame, etc.)
  This shows scry-run status in terminal title bar instead of stderr, preventing display disruption.
  Only set to true if the app uses a TUI or graphical library.

Return a JSON object with this structure:
{{
  "description": "The expanded description text here...",
  "flags": {{
    "quiet": false
  }}
}}

Return ONLY the JSON object, no markdown code fences or explanation.'''


def expand_description(name: str, description: str) -> dict | None:
    """Use LLM to expand a brief description into a detailed one.

    Args:
        name: App name
        description: User's brief description

    Returns:
        Dict with 'description' and 'flags' keys, or None if expansion fails
    """
    try:
        from scry_run.generator import CodeGenerator, ScryRunError

        generator = CodeGenerator()
        prompt = EXPAND_PROMPT.format(name=name, description=description)

        # Use generate_freeform for text generation
        response = generator.generate_freeform(prompt)
        response = response.strip()

        # Parse JSON response
        # Handle potential markdown code fences
        if response.startswith("```"):
            lines = response.split("\n")
            # Remove first and last lines (code fences)
            response = "\n".join(lines[1:-1])

        result = json.loads(response)

        # Validate structure
        if "description" not in result:
            console.print("[yellow]Warning:[/yellow] LLM response missing 'description' field")
            return None

        # Ensure flags exists with defaults
        if "flags" not in result:
            result["flags"] = {}

        return result

    except json.JSONDecodeError as e:
        console.print(f"[yellow]Warning:[/yellow] Could not parse LLM response as JSON: {e}")
        return None
    except ScryRunError as e:
        console.print(f"[yellow]Warning:[/yellow] Could not expand description: {e.message}")
        return None
    except Exception as e:
        console.print(f"[yellow]Warning:[/yellow] Could not expand description: {e}")
        return None


MAIN_TEMPLATE = '''#!/usr/bin/env python3
"""
{description}

Generated by scry-run. Run with: scry-run run {name}
"""

import sys

# =============================================================================
# scry-run: LLM-POWERED DYNAMIC CODE GENERATION
# =============================================================================
#
# This module uses automatic code generation. ANY METHOD CALLED WILL BE
# AUTO-GENERATED if it doesn't exist. Use this power!
#
# =============================================================================
# WHAT GETS AUTO-GENERATED
# =============================================================================
#
# 1. CONSTRUCTOR (__init__):
#    - If you create an object with arguments, __init__ gets generated!
#    - Example: app = MyClass("config.json", debug=True)
#    - The LLM sees the args and generates appropriate initialization
#
# 2. INSTANCE METHODS (self.xxx):
#    - Any method you call on an instance gets generated
#    - Including _private_helpers - they work too!
#
# 3. CLASS METHODS (MyClass.xxx):
#    - Class-level method calls also trigger generation
#
# =============================================================================
# CORE PRINCIPLES - READ CAREFULLY!
# =============================================================================
#
# 1. KEEP FUNCTIONS SHORT (5-15 lines max!)
#    - Each function should do ONE thing
#    - Long functions are WRONG - break them up
#    - Call helper methods freely - they'll be generated!
#
# 2. USE DYNAMIC DISPATCH instead of conditionals/maps:
#    BAD:  if lang == "french": return "Bonjour"
#          elif lang == "spanish": return "Hola"
#    GOOD: return self._greet_in_{{lang}}()  # Dynamic method call!
#
#    BAD:  handlers = {{"add": self.add, "remove": self.remove}}
#    GOOD: return getattr(self, f"_handle_{{command}}")(*args)
#
# 3. DELEGATE EVERYTHING:
#    - Don't implement logic inline - call a method for it
#    - self._parse_args(args) instead of inline parsing
#    - self._format_output(result) instead of inline formatting
#    - self._validate_input(x) instead of inline checks
#
# 4. USE METAPROGRAMMING PATTERNS:
#    - getattr(self, f"_do_{{action}}")() for dynamic dispatch
#    - self._handle_{{category}}_{{item}}() for 2-level dispatch
#    - Call methods that don't exist yet - they WILL be generated!
#
# 5. PREFER RECURSION & COMPOSITION:
#    - Process items one at a time via self._process_single(item)
#    - Compose results: self._combine(self._step1(), self._step2())
#    - Chain transformations: self._step3(self._step2(self._step1(x)))
#
# 6. NEVER HARDCODE LISTS/MAPS:
#    BAD:  LANGUAGES = ["english", "french", "spanish"]
#    GOOD: Call self._get_supported_languages() - generated dynamically!
#
#    BAD:  COMMANDS = {{"help": ..., "run": ...}}
#    GOOD: Check hasattr(self, f"_cmd_{{name}}") or just call it!
#
# 7. ERROR HANDLING VIA METHODS:
#    - self._handle_error(e) instead of inline handling
#    - self._fallback_for_{{operation}}() for graceful degradation
#
# 8. HANDLING CLI ARGUMENTS & FLEXIBILITY:
#    - DESIGN FOR UNKNOWN FLAGS: The user might add new flags!
#    - Use argparse.ArgumentParser().parse_known_args()
#    - Return tuple (known_args, unknown_args) to preserve unknown flags
#    - FLEXIBLE SCOPE: If a flag/command is outside the description scope,
#      or involves "unseen-as-of-yet" flags, HANDLE IT FLEXIBLY.
#    - INFER INTENT: Use the flag name to guess the desired feature.
#
# =============================================================================
# EXAMPLE PATTERNS
# =============================================================================
#
# Creating MULTIPLE CLASSES dynamically:
#   from scry_run import llm_create
#   TodoItem = llm_create("TodoItem", "A single todo with title and done status")
#   Database = llm_create("Database", "SQLite database wrapper")
#   
#   item = TodoItem("Buy milk")
#   item.mark_done()  # auto-generated!
#
# Constructor with args (auto-generated __init__):
#   app = TodoList("my_tasks.db")  # __init__ generated with db_path param!
#
# Dynamic language handler:
#   def greet(self, lang): return getattr(self, f"_greet_{{lang}}")()
#
# Command dispatcher:
#   def run(self, cmd, *args): return getattr(self, f"_cmd_{{cmd}}")(*args)
#
# Recursive processing:
#   def process(self, items):
#       if not items: return []
#       return [self._process_one(items[0])] + self.process(items[1:])
#
# Chained transformation:
#   def transform(self, x):
#       return self._finalize(self._enhance(self._prepare(x)))
#
# =============================================================================
from scry_run import ScryClass, scry_create


class {class_name}(ScryClass):
    """{description}
    
    Methods are auto-generated. Keep them SHORT and MODULAR!
    Use dynamic dispatch: getattr(self, f"_handle_{{x}}")()
    
    To create additional classes, use llm_create:
        OtherClass = llm_create("OtherClass", "Description here")
    """
    pass


if __name__ == "__main__":
    app = {class_name}()
    result = app.main(sys.argv[1:])
    if result is not None:
        print(result)
'''


def to_class_name(name: str) -> str:
    """Convert a project name to a valid Python class name."""
    # Remove special characters and convert to PascalCase
    parts = name.replace("-", "_").replace(" ", "_").split("_")
    return "".join(part.capitalize() for part in parts if part)


@click.command()
@click.option(
    "--name", "-n",
    help="App name (used for the directory)",
)
@click.option(
    "--description", "-d",
    help="App description",
)
@click.option(
    "--auto-expand/--no-auto-expand",
    default=True,
    help="Automatically expand description with features and examples (default: enabled)",
)
def init(
    name: str | None,
    description: str | None,
    auto_expand: bool,
) -> None:
    """Initialize a new scry-run app.

    Creates an app in ~/.scry-run/apps/<name>/ with:
    - app.py: The main executable
    - cache.json: Empty cache file
    - logs/: Directory for log files

    By default, your brief description is expanded using AI to include
    features and usage examples. Use --no-auto-expand to disable.

    Examples:

        # Interactive mode (recommended)
        scry-run init

        # Non-interactive with auto-expand
        scry-run init --name=todoist --description='todo list app'

        # Skip auto-expand
        scry-run init --name=myapp --description='my app' --no-auto-expand
    """
    # Interactive mode if options not provided
    if not name:
        console.print("[bold blue]scry-run App Setup[/bold blue]\n")
        name = Prompt.ask("App name", default="myapp")

    if not description:
        description = Prompt.ask(
            "App description (brief is fine - will be expanded)",
            default=f"A {name} application"
        )

    # Auto-expand description using LLM
    # Track recommended flags from LLM
    recommended_flags: dict = {}

    if auto_expand:
        console.print()
        console.print("[dim]Expanding description with AI...[/dim]")
        result = expand_description(name, description)

        if result:
            expanded_desc = result["description"]
            recommended_flags = result.get("flags", {})

            # Show the expanded description
            console.print()
            panel = Panel(
                expanded_desc,
                title="[bold]Expanded Description[/bold]",
                border_style="blue",
            )
            console.print(panel)

            # Show recommended flags if any are non-default
            if recommended_flags.get("quiet"):
                console.print("[dim]Recommended: quiet mode (TUI app)[/dim]")

            console.print()

            # Let user confirm, edit, or reject
            console.print("[dim](y)es, (n)o, or (e)dit in $EDITOR[/dim]")
            choice = Prompt.ask(
                "Use this description?",
                choices=["y", "n", "e"],
                default="y",
            )
            if choice == "y":
                description = expanded_desc
            elif choice == "e":
                # Open in editor
                edited = click.edit(expanded_desc)
                if edited:
                    description = edited.strip()
                    console.print("[dim]Using edited description.[/dim]")
                else:
                    console.print("[dim]Editor returned empty, using original.[/dim]")
            else:
                console.print("[dim]Using original description.[/dim]")
                recommended_flags = {}  # Reset flags if user rejected expansion
        else:
            console.print("[dim]Using original description.[/dim]")

    # Validate name
    if not name.replace("_", "").replace("-", "").isalnum():
        console.print("[red]Error:[/red] App name must be alphanumeric (with - or _)")
        raise SystemExit(1)

    # Ensure home directory exists
    ensure_home_exists()

    # Get app directory
    app_dir = get_app_dir(name)

    # Check for existing app
    if app_dir.exists():
        if not Confirm.ask(f"[yellow]App '{name}' already exists[/yellow]. Overwrite?"):
            console.print("[yellow]Aborted.[/yellow]")
            raise SystemExit(0)

    # Create app directory
    app_dir.mkdir(parents=True, exist_ok=True)

    # Create virtual environment and install scry-run
    ensure_scry_run_installed(app_dir)

    class_name = to_class_name(name)

    # Create app.py
    app_file = app_dir / "app.py"
    app_content = MAIN_TEMPLATE.format(
        name=name,
        description=description,
        class_name=class_name,
    )
    app_file.write_text(app_content)
    app_file.chmod(app_file.stat().st_mode | 0o111)  # Make executable

    # Create empty cache.json
    cache_file = app_dir / "cache.json"
    cache_file.write_text(json.dumps({}))

    # Create settings.json with default flags from LLM recommendation
    settings = {
        "default_flags": {
            "quiet": recommended_flags.get("quiet", False),
        }
    }
    settings_file = app_dir / "settings.json"
    settings_file.write_text(json.dumps(settings, indent=2))

    # Create logs directory
    logs_dir = app_dir / "logs"
    logs_dir.mkdir(exist_ok=True)

    console.print()
    console.print("[bold green]App created successfully![/bold green]")
    console.print()
    console.print(f"  [dim]Location:[/dim]  {app_dir}")
    console.print(f"  [dim]Main file:[/dim] {app_file}")
    console.print(f"  [dim]Settings:[/dim]  {settings_file}")
    console.print()
    if recommended_flags.get("quiet"):
        console.print("[dim]Quiet mode enabled (TUI app) - status shown in title bar.[/dim]")
        console.print()
    console.print("[bold]Useful commands:[/bold]")
    console.print(f"  [cyan]scry-run run {name}[/cyan]         Run your app")
    console.print(f"  [cyan]scry-run info {name}[/cyan]        View app details and cache stats")
    console.print(f"  [cyan]scry-run cache list {name}[/cyan]  List generated methods")
    console.print()
    console.print("[dim]Methods are generated on first use using Claude CLI.[/dim]")
