"""
Command-line interface for SpecSoloist.

Usage:
    sp list                     List all specs
    sp create <name> <desc>     Create a new spec
    sp validate <name>          Validate a spec
    sp compile <name>           Compile a spec to code
    sp test <name>              Run tests for a spec
    sp fix <name>               Auto-fix failing tests
    sp build                    Compile all specs
"""

import argparse
import sys
import os

from .core import SpecSoloistCore
from .resolver import CircularDependencyError, MissingDependencyError
from spechestra.composer import SpecComposer, Architecture
from spechestra.conductor import SpecConductor
from .respec import Respecer
from . import ui


def main():
    parser = argparse.ArgumentParser(
        prog="sp",
        description="Spec-as-Source AI coding framework"
    )
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # list
    subparsers.add_parser("list", help="List all specification files")

    # create
    create_parser = subparsers.add_parser("create", help="Create a new spec from template")
    create_parser.add_argument("name", help="Spec name (e.g., 'auth' creates auth.spec.md)")
    create_parser.add_argument("description", help="Brief description of the component")
    create_parser.add_argument("--type", default="function", help="Type: function, class, module, typedef")

    # validate
    validate_parser = subparsers.add_parser("validate", help="Validate a spec's structure")
    validate_parser.add_argument("name", help="Spec name to validate")

    # verify
    subparsers.add_parser("verify", help="Verify all specs for orchestration readiness")

    # graph
    subparsers.add_parser("graph", help="Export dependency graph as Mermaid")

    # compile
    compile_parser = subparsers.add_parser("compile", help="Compile a spec to code")
    compile_parser.add_argument("name", help="Spec name to compile")
    compile_parser.add_argument("--model", help="Override LLM model")
    compile_parser.add_argument("--no-tests", action="store_true", help="Skip test generation")
    compile_parser.add_argument("--arrangement", metavar="FILE", help="Path to arrangement YAML file")

    # test
    test_parser = subparsers.add_parser("test", help="Run tests for a spec")
    test_parser.add_argument("name", help="Spec name to test")

    # fix
    fix_parser = subparsers.add_parser("fix", help="Auto-fix failing tests")
    fix_parser.add_argument("name", help="Spec name to fix")
    fix_parser.add_argument("--no-agent", action="store_true",
                                help="Use direct LLM API instead of agent CLI")
    fix_parser.add_argument("--auto-accept", action="store_true", help="Skip interactive review")
    fix_parser.add_argument("--model", help="Override LLM model")

    # build
    build_parser = subparsers.add_parser("build", help="Compile all specs in dependency order")
    build_parser.add_argument("--incremental", action="store_true", help="Only recompile changed specs")
    build_parser.add_argument("--parallel", action="store_true", help="Compile independent specs concurrently")
    build_parser.add_argument("--workers", type=int, default=4, help="Max parallel workers (default: 4)")
    build_parser.add_argument("--model", help="Override LLM model")
    build_parser.add_argument("--no-tests", action="store_true", help="Skip test generation")
    build_parser.add_argument("--arrangement", metavar="FILE", help="Path to arrangement YAML file")

    # compose
    compose_parser = subparsers.add_parser("compose", help="Draft architecture and specs from natural language")
    compose_parser.add_argument("request", help="Description of the system you want to build")
    compose_parser.add_argument("--no-agent", action="store_true",
                                help="Use direct LLM API instead of agent CLI")
    compose_parser.add_argument("--auto-accept", action="store_true", help="Skip interactive review (with --no-agent)")
    compose_parser.add_argument("--model", help="Override LLM model")

    # conduct
    conduct_parser = subparsers.add_parser("conduct", help="Orchestrate project build")
    conduct_parser.add_argument("src_dir", nargs="?", default=None, help="Spec directory (default: src/)")
    conduct_parser.add_argument("--no-agent", action="store_true",
                                help="Use direct LLM API instead of agent CLI")
    conduct_parser.add_argument("--auto-accept", action="store_true", help="Skip interactive review")
    conduct_parser.add_argument("--incremental", action="store_true", help="Only recompile changed specs")
    conduct_parser.add_argument("--parallel", action="store_true", help="Compile independent specs concurrently")
    conduct_parser.add_argument("--workers", type=int, default=4, help="Max parallel workers (default: 4)")
    conduct_parser.add_argument("--model", help="Override LLM model")
    conduct_parser.add_argument("--arrangement", metavar="FILE", help="Path to arrangement YAML file")

    # perform
    perform_parser = subparsers.add_parser("perform", help="Execute an orchestration workflow")
    perform_parser.add_argument("workflow", help="Workflow spec name")
    perform_parser.add_argument("inputs", help="JSON inputs for the workflow")

    # respec
    respec_parser = subparsers.add_parser("respec", help="Reverse engineer code to spec")
    respec_parser.add_argument("file", help="Path to source file")
    respec_parser.add_argument("--test", help="Path to test file (optional)")
    respec_parser.add_argument("--out", help="Output path (optional)")
    respec_parser.add_argument("--no-agent", action="store_true",
                               help="Use direct LLM API instead of agent CLI")
    respec_parser.add_argument("--model", help="LLM model override (with --no-agent)")
    respec_parser.add_argument("--auto-accept", action="store_true", help="Skip interactive review")

    args = parser.parse_args()

    if args.command is None:
        parser.print_help()
        sys.exit(0)

    # Initialize core
    try:
        core = SpecSoloistCore(os.getcwd())
    except Exception as e:
        ui.print_error(f"Failed to initialize SpecSoloist: {e}")
        sys.exit(1)

    try:
        if args.command == "list":
            cmd_list(core)
        elif args.command == "create":
            cmd_create(core, args.name, args.description, args.type)
        elif args.command == "validate":
            cmd_validate(core, args.name)
        elif args.command == "verify":
            cmd_verify(core)
        elif args.command == "graph":
            cmd_graph(core)
        elif args.command == "compile":
            cmd_compile(core, args.name, args.model, not args.no_tests, args.arrangement)
        elif args.command == "test":
            cmd_test(core, args.name)
        elif args.command == "fix":
            cmd_fix(core, args.name, args.no_agent, args.auto_accept, args.model)
        elif args.command == "build":
            cmd_build(core, args.incremental, args.parallel, args.workers, args.model, not args.no_tests, args.arrangement)
        elif args.command == "compose":
            cmd_compose(core, args.request, args.no_agent, args.auto_accept, args.model)
        elif args.command == "conduct":
            cmd_conduct(core, args.src_dir, args.no_agent, args.auto_accept,
                        args.incremental, args.parallel, args.workers, args.model, args.arrangement)
        elif args.command == "perform":
            cmd_perform(core, args.workflow, args.inputs)
        elif args.command == "respec":
            cmd_respec(core, args.file, args.test, args.out, args.no_agent, args.model, args.auto_accept)
    except KeyboardInterrupt:
        ui.print_warning("\nOperation cancelled by user.")
        sys.exit(130)
    except CircularDependencyError as e:
        ui.print_error(str(e))
        sys.exit(1)
    except MissingDependencyError as e:
        ui.print_error(str(e))
        sys.exit(1)
    except Exception as e:
        ui.print_error(f"Unexpected error: {e}")
        # In verbose mode, we might want to print the stack trace
        sys.exit(1)


def cmd_list(core: SpecSoloistCore):
    specs = core.list_specs()
    if not specs:
        ui.print_warning("No specs found in src/")
        ui.print_info("Create one with: sp create <name> '<description>'")
        return

    table = ui.create_table(["Name", "Type", "Status", "Description"], title="Project Specifications")

    for spec_file in specs:
        try:
            name = spec_file.replace(".spec.md", "")
            # Parse to get metadata
            parsed = core.parser.parse_spec(name)
            meta = parsed.metadata
            
            # Color code status
            status_style = "green" if meta.status == "stable" else "yellow"
            
            table.add_row(
                f"[bold]{name}[/]", 
                meta.type,
                f"[{status_style}]{meta.status}[/]",
                meta.description or ""
            )
        except Exception as e:
            # Fallback for unparseable specs
            table.add_row(spec_file, "???", "error", f"Error: {str(e)}")

    ui.console.print(table)


def cmd_create(core: SpecSoloistCore, name: str, description: str, spec_type: str):
    ui.print_header("Creating Spec", name)
    try:
        path = core.create_spec(name, description, type=spec_type)
        ui.print_success(f"Created spec at: [bold]{path}[/]")
    except FileExistsError:
        ui.print_error(f"Spec already exists: {name}.spec.md")
        sys.exit(1)


def cmd_validate(core: SpecSoloistCore, name: str):
    ui.print_header("Validating Spec", name)
    result = core.validate_spec(name)
    
    if result["valid"]:
        ui.print_success(f"{name} is VALID")
    else:
        ui.print_error(f"{name} is INVALID")
        for error in result["errors"]:
            ui.print_step(f"[red]{error}[/]")
        sys.exit(1)


def cmd_verify(core: SpecSoloistCore):
    ui.print_header("Verifying Project", "Checking schemas")
    
    with ui.spinner("Verifying all specs..."):
        result = core.verify_project()
        
    table = ui.create_table(["Spec", "Status", "Schema", "Details"], title="Verification Results")
    
    for name, data in result["results"].items():
        status = data["status"]
        status_color = "green" if status == "valid" else "yellow" if status == "warning" else "red"
        
        schema_status = "[green]Yes[/]" if data.get("schema_defined") else "[dim]No[/]"
        details = data.get("message") or (", ".join(data.get("errors", [])) if "errors" in data else "")
        
        table.add_row(
            name,
            f"[{status_color}]{status.upper()}[/]",
            schema_status,
            details
        )
        
    ui.console.print(table)
    
    if result["success"]:
        ui.print_success("Project verification complete.")
    else:
        ui.print_warning("Some specs have issues or missing schemas.")
        # We don't exit(1) here because warnings shouldn't break the build pipeline necessarily,
        # unless strict mode is enabled (future feature).


def cmd_graph(core: SpecSoloistCore):
    ui.print_header("Dependency Graph", "Mermaid format")
    
    graph = core.get_dependency_graph()
    
    lines = ["graph TD"]
    for spec in graph.specs:
        deps = graph.get_dependencies(spec)
        if not deps:
            lines.append(f"    {spec}")
        for dep in deps:
            lines.append(f"    {spec} --> {dep}")
            
    mermaid = "\n".join(lines)
    ui.console.print(ui.Panel(mermaid, title="Mermaid.js Output"))
    ui.print_info("Paste this into https://mermaid.live to visualize.")


def cmd_compile(core: SpecSoloistCore, name: str, model: str, generate_tests: bool,
                arrangement_arg: str | None = None):
    _check_api_key()

    ui.print_header("Compiling Spec", name)

    arrangement = _resolve_arrangement(core, arrangement_arg)
    _apply_arrangement(core, arrangement)

    # Validate first
    validation = core.validate_spec(name)
    if not validation["valid"]:
        ui.print_error("Invalid spec")
        for error in validation["errors"]:
            ui.print_step(f"[red]{error}[/]")
        sys.exit(1)

    try:
        # Compile code
        with ui.spinner(f"Compiling [bold]{name}[/] implementation..."):
            result = core.compile_spec(name, model=model, arrangement=arrangement)
        ui.print_success(result)

        # Generate tests
        if generate_tests:
            spec = core.parser.parse_spec(name)
            if spec.metadata.type != "typedef":
                with ui.spinner(f"Generating tests for [bold]{name}[/]..."):
                    result = core.compile_tests(name, model=model, arrangement=arrangement)
                ui.print_success(result)
            else:
                ui.print_info("Skipping tests for typedef spec")

    except Exception as e:
        ui.print_error(str(e))
        sys.exit(1)


def cmd_test(core: SpecSoloistCore, name: str):
    ui.print_header("Running Tests", name)

    arrangement = _resolve_arrangement(core, None)
    _apply_arrangement(core, arrangement)

    with ui.spinner(f"Running tests for [bold]{name}[/]..."):
        result = core.run_tests(name)
    
    if result["success"]:
        ui.print_success("Tests PASSED")
        # Only show output if verbose? For now, nice to see summary
        # ui.console.print(Panel(result["output"], title="Output", border_style="dim"))
    else:
        ui.print_error("Tests FAILED")
        ui.console.print(ui.Panel(result["output"], title="Failure Output", border_style="red"))
        sys.exit(1)


def cmd_fix(core: SpecSoloistCore, name: str, no_agent: bool, auto_accept: bool, model: str | None = None):
    """Auto-fix failing tests."""
    _check_api_key()

    ui.print_header("Auto-Fixing Spec", name)

    if no_agent:
        _fix_with_llm(core, name, model)
    else:
        _fix_with_agent(name, auto_accept, model=model)


def _fix_with_agent(name: str, auto_accept: bool, model: str | None = None):
    """Use an AI agent CLI for multi-step self-healing."""
    agent = _detect_agent_cli()
    if not agent:
        ui.print_error("No agent CLI found (claude or gemini). Install one or use --no-agent.")
        sys.exit(1)

    ui.print_info(f"Using {agent} agent with native subagent...")
    if model:
        ui.print_info(f"Model: {model}")

    # Build natural language prompt
    prompt = f"fix: Resolve failing tests for {name}"

    try:
        _run_agent_oneshot(agent, prompt, auto_accept, model=model)
    except Exception as e:
        ui.print_error(f"Agent error: {e}")
        sys.exit(1)


def _fix_with_llm(core: SpecSoloistCore, name: str, model: str | None = None):
    """Direct LLM self-healing (single-shot, no agent iteration)."""
    with ui.spinner(f"Analyzing [bold]{name}[/] failures and generating fix..."):
        try:
            result = core.attempt_fix(name, model=model)
            ui.print_success(result)
        except Exception as e:
            ui.print_error(f"Fix failed: {e}")
            sys.exit(1)


def cmd_build(core: SpecSoloistCore, incremental: bool, parallel: bool, workers: int, model: str,
              generate_tests: bool, arrangement_arg: str | None = None):
    _check_api_key()

    specs = core.list_specs()
    if not specs:
        ui.print_warning("No specs found in src/")
        sys.exit(1)

    mode_parts = []
    if incremental:
        mode_parts.append("incremental")
    if parallel:
        mode_parts.append(f"parallel-{workers}")
    mode_str = f"[{', '.join(mode_parts)}]" if mode_parts else "[full]"

    ui.print_header("Building Project", f"{len(specs)} specs {mode_str}")

    arrangement = _resolve_arrangement(core, arrangement_arg)
    _apply_arrangement(core, arrangement)

    with ui.spinner("Compiling project..."):
        result = core.compile_project(
            model=model,
            generate_tests=generate_tests,
            incremental=incremental,
            parallel=parallel,
            max_workers=workers,
            arrangement=arrangement,
        )

    # Summary Table
    table = ui.create_table(["Result", "Spec", "Details"], title="Build Summary")
    
    for spec in result.specs_compiled:
        table.add_row("[green]Compiled[/]", spec, "Success")
    
    for spec in result.specs_skipped:
        table.add_row("[dim]Skipped[/]", spec, "Unchanged")
        
    for spec in result.specs_failed:
        error = result.errors.get(spec, "Unknown error")
        # Truncate long errors
        if len(error) > 50:
            error = error[:47] + "..."
        table.add_row("[red]Failed[/]", spec, error)

    ui.console.print(table)

    if result.success:
        ui.print_success("Build complete.")
    else:
        ui.print_error("Build failed.")
        sys.exit(1)


def cmd_compose(core: SpecSoloistCore, request: str, no_agent: bool, auto_accept: bool,
                model: str | None = None):
    """Draft architecture and specs from natural language."""

    ui.print_header("Composing System", request[:50] + "..." if len(request) > 50 else request)

    if no_agent:
        _compose_with_llm(core, request, auto_accept)
    else:
        _compose_with_agent(request, auto_accept, model=model)


def _compose_with_agent(request: str, auto_accept: bool, model: str | None = None):
    """Use an AI agent CLI for multi-step composition."""
    agent = _detect_agent_cli()
    if not agent:
        ui.print_error("No agent CLI found (claude or gemini). Install one or use --no-agent.")
        sys.exit(1)

    ui.print_info(f"Using {agent} agent with native subagent...")
    if model:
        ui.print_info(f"Model: {model}")

    # Simple natural language prompt - the native subagent handles the rest
    prompt = f"compose: {request}"

    try:
        _run_agent_oneshot(agent, prompt, auto_accept, model=model)
    except Exception as e:
        ui.print_error(f"Agent error: {e}")
        sys.exit(1)


def _compose_with_llm(core: SpecSoloistCore, request: str, auto_accept: bool):
    """Direct LLM composition (single-shot, no agent iteration)."""
    _check_api_key()

    ui.print_info("Using direct LLM call (no agent)...")

    # Initialize Composer
    composer = SpecComposer(core.project_dir)

    with ui.spinner("Drafting architecture..."):
        architecture = composer.draft_architecture(request)

    # Interactive loop
    while True:
        # Display Architecture
        ui.print_header("Architecture Draft", architecture.description)

        arch_table = ui.create_table(["Component", "Type", "Dependencies", "Description"])
        for comp in architecture.components:
            deps = ", ".join(comp.dependencies) if comp.dependencies else "-"
            arch_table.add_row(
                f"[bold]{comp.name}[/]",
                comp.type,
                deps,
                comp.description
            )
        ui.console.print(arch_table)
        ui.console.print()

        if auto_accept:
            break

        from rich.prompt import Prompt, Confirm
        choice = Prompt.ask(
            "[bold]Action[/]",
            choices=["proceed", "edit", "cancel"],
            default="proceed"
        )

        if choice == "cancel":
            ui.print_warning("Composition cancelled.")
            return

        if choice == "edit":
            # Edit in external editor
            import tempfile
            import subprocess
            import shlex

            with tempfile.NamedTemporaryFile(suffix=".yaml", mode='w', delete=False) as tf:
                tf.write(architecture.to_yaml())
                tf_path = tf.name

            editor = os.environ.get("EDITOR", "vim")

            try:
                subprocess.call(shlex.split(editor) + [tf_path])

                # Read back
                with open(tf_path, 'r') as f:
                    new_yaml = f.read()

                # Parse
                try:
                    architecture = Architecture.from_yaml(new_yaml)
                    ui.print_success("Architecture updated from editor.")
                except Exception as e:
                    ui.print_error(f"Failed to parse updated architecture: {e}")
                    if not Confirm.ask("Retry editing?", default=True):
                        return
                    continue  # Loop back to display and prompt

            finally:
                if os.path.exists(tf_path):
                    os.remove(tf_path)
            continue  # Show updated architecture

        if choice == "proceed":
            break

    with ui.spinner("Generating specs..."):
        spec_paths = composer.generate_specs(architecture)

    ui.print_success(f"Generated {len(spec_paths)} specs:")
    for path in spec_paths:
        rel_path = os.path.relpath(path, os.getcwd())
        ui.console.print(f"  - [bold]{rel_path}[/]")

    ui.print_info("Review specs, then run 'sp conduct' to build.")


def cmd_conduct(core: SpecSoloistCore, src_dir: str | None, no_agent: bool, auto_accept: bool,
                 incremental: bool, parallel: bool, workers: int, model: str | None = None,
                 arrangement_arg: str | None = None):
    """Orchestrate project build."""

    ui.print_header("Conducting Build", src_dir or "project specs")

    if no_agent:
        arrangement = _resolve_arrangement(core, arrangement_arg)
        _conduct_with_llm(core, incremental, parallel, workers, arrangement=arrangement)
    else:
        _conduct_with_agent(src_dir, auto_accept, model=model)


def _conduct_with_agent(src_dir: str | None, auto_accept: bool, model: str | None = None):
    """Use an AI agent CLI for multi-step orchestrated build."""
    agent = _detect_agent_cli()
    if not agent:
        ui.print_error("No agent CLI found (claude or gemini). Install one or use --no-agent.")
        sys.exit(1)

    ui.print_info(f"Using {agent} agent with native subagent...")
    if model:
        ui.print_info(f"Model: {model}")

    spec_dir = src_dir or "src/"

    # Detect if this is a quine attempt (spec_dir is score/)
    is_quine = "score" in spec_dir

    if is_quine:
        # Create quine output directory
        quine_dir = "build/quine"
        os.makedirs(f"{quine_dir}/src/specsoloist", exist_ok=True)
        os.makedirs(f"{quine_dir}/src/spechestra", exist_ok=True)
        os.makedirs(f"{quine_dir}/tests", exist_ok=True)

        ui.print_info(f"🔄 Quine mode: Will regenerate code to {quine_dir}/")

        prompt = (
            f"conduct: Read all *.spec.md files in {spec_dir}, resolve their dependency order, "
            f"then compile each spec into working code by spawning soloist subagents. "
            f"\n\n**CRITICAL - Output Paths (MUST be followed exactly):**\n"
            f"- Write specsoloist implementations to: {quine_dir}/src/specsoloist/<name>.py\n"
            f"- Write spechestra implementations to: {quine_dir}/src/spechestra/<name>.py\n"
            f"- Write tests to: {quine_dir}/tests/test_<name>.py\n"
            f"- Run tests with: PYTHONPATH={quine_dir}/src uv run python -m pytest <test_path> -v\n\n"
            f"**WARNING**: Do NOT write to src/ or tests/ directly — those contain the original source. "
            f"ALL output MUST go to {quine_dir}/. "
            f"When spawning soloist subagents, include the FULL output paths in each soloist prompt.\n\n"
            f"This is a QUINE VALIDATION - you are intentionally regenerating code to verify specs are complete. "
            f"Do NOT skip compilation because code already exists elsewhere. "
            f"Run the full test suite from {quine_dir}/tests/ when done."
        )
    else:
        prompt = (
            f"conduct: Read all *.spec.md files in {spec_dir}, resolve their dependency order, "
            f"then compile each spec into working code by spawning soloist subagents. "
            f"Write implementations to the appropriate src/ paths and tests to tests/. "
            f"Run the full test suite when done."
        )

    if model:
        prompt += (
            f'\n\n**Model**: When spawning soloist subagents via the Task tool, '
            f'set model: "{model}".'
        )

    try:
        _run_agent_oneshot(agent, prompt, auto_accept, model=model, is_quine=is_quine)
    except Exception as e:
        ui.print_error(f"Agent error: {e}")
        sys.exit(1)


def _conduct_with_llm(core: SpecSoloistCore, incremental: bool, parallel: bool, workers: int,
                      arrangement=None):
    """Direct LLM build (single-shot compilation, no agent iteration)."""
    _check_api_key()

    ui.print_info("Using direct LLM calls (no agent)...")

    conductor = SpecConductor(core.root_dir)

    with ui.spinner("Orchestrating build..."):
        result = conductor.build(
            incremental=incremental,
            parallel=parallel,
            max_workers=workers,
            arrangement=arrangement,
        )

    table = ui.create_table(["Result", "Spec", "Details"], title="Conductor Report")

    for spec in result.specs_compiled:
        table.add_row("[green]Compiled[/]", spec, "Success")

    for spec in result.specs_skipped:
        table.add_row("[dim]Skipped[/]", spec, "Unchanged")

    for spec in result.specs_failed:
        error = result.errors.get(spec, "Unknown error")
        if len(error) > 50:
            error = error[:47] + "..."
        table.add_row("[red]Failed[/]", spec, error)

    ui.console.print(table)

    if result.success:
        ui.print_success("Conductor finished successfully.")
    else:
        ui.print_error("Conductor reported failures.")
        sys.exit(1)


def cmd_perform(core: SpecSoloistCore, workflow: str, inputs_json: str):
    import json
    from rich.prompt import Confirm
    
    ui.print_header("Performing Workflow", workflow)
    
    try:
        inputs = json.loads(inputs_json)
    except json.JSONDecodeError:
        ui.print_error("Invalid JSON inputs")
        sys.exit(1)
        
    conductor = SpecConductor(core.project_dir)
    
    def checkpoint_callback(step_name: str) -> bool:
        return Confirm.ask(f"[bold yellow]Checkpoint:[/] Proceed with step '{step_name}'?")

    try:
        result = conductor.perform(
            workflow, 
            inputs, 
            checkpoint_callback=checkpoint_callback
        )
        
        # Display results
        table = ui.create_table(["Step", "Status", "Duration", "Result"], title="Performance Summary")
        for step in result.steps:
            status = "[green]Success[/]" if step.success else "[red]Failed[/]"
            duration = f"{step.duration:.2f}s"
            
            # Format output briefly
            output_str = str(step.outputs)[:50] + "..." if len(str(step.outputs)) > 50 else str(step.outputs)
            if step.error:
                output_str = f"[red]{step.error}[/]"
                
            table.add_row(step.name, status, duration, output_str)
            
        ui.console.print(table)
        
        if result.success:
            ui.print_success("Performance complete")
            ui.print_info(f"Trace saved to: {result.trace_path}")
            # Show final outputs
            ui.console.print(ui.Panel(str(result.outputs), title="Final Outputs"))
        else:
            ui.print_error("Performance failed")
            sys.exit(1)
            
    except Exception as e:
        ui.print_error(f"Performance error: {e}")
        # Print traceback for debugging
        import traceback
        traceback.print_exc()
        sys.exit(1)


def cmd_respec(core: SpecSoloistCore, file_path: str, test_path: str, out_path: str, no_agent: bool, model: str, auto_accept: bool):
    """Reverse engineer code to spec."""

    ui.print_header("Respec: Code → Spec", file_path)

    if no_agent:
        _respec_with_llm(core, file_path, test_path, out_path, model)
    else:
        _respec_with_agent(file_path, test_path, out_path, auto_accept, model=model)


def _respec_with_agent(file_path: str, test_path: str, out_path: str, auto_accept: bool,
                       model: str | None = None):
    """Use an AI agent CLI for multi-step respec with validation."""
    agent = _detect_agent_cli()
    if not agent:
        ui.print_error("No agent CLI found (claude or gemini). Install one or use --no-agent.")
        sys.exit(1)

    ui.print_info(f"Using {agent} agent with native subagent...")
    if model:
        ui.print_info(f"Model: {model}")

    # Build natural language prompt
    prompt_parts = [f"respec {file_path}"]
    if test_path:
        prompt_parts.append(f"using tests from {test_path}")
    if out_path:
        prompt_parts.append(f"to {out_path}")
    prompt = " ".join(prompt_parts)

    try:
        _run_agent_oneshot(agent, prompt, auto_accept, model=model)
    except Exception as e:
        ui.print_error(f"Agent error: {e}")
        sys.exit(1)


def _respec_with_llm(core: SpecSoloistCore, file_path: str, test_path: str, out_path: str, model: str):
    """Direct LLM respec (single-shot, no validation loop)."""
    _check_api_key()

    ui.print_info("Using direct LLM call (no agent)...")

    respecer = Respecer(core.config)

    with ui.spinner("Analyzing code and generating spec..."):
        try:
            spec_content = respecer.respec(file_path, test_path, model)
        except Exception as e:
            ui.print_error(f"Respec failed: {e}")
            sys.exit(1)

    if out_path:
        with open(out_path, 'w') as f:
            f.write(spec_content)
        ui.print_success(f"Spec saved to: [bold]{out_path}[/]")
        ui.print_info("Run 'sp validate' to check the generated spec.")
    else:
        ui.console.print(ui.Panel(spec_content, title="Generated Spec", border_style="blue"))
        ui.print_info("Use --out <path> to save to file.")


def _detect_agent_cli() -> str | None:
    """Detect which agent CLI is available (claude preferred over gemini)."""
    import shutil
    if shutil.which("claude"):
        return "claude"
    if shutil.which("gemini"):
        return "gemini"
    return None


def _run_agent_oneshot(agent: str, prompt: str, auto_accept: bool, model: str | None = None,
                       is_quine: bool = False):
    """Run an agent CLI in one-shot mode."""
    import subprocess

    if agent == "claude":
        # Claude Code: claude -p "prompt" --verbose for progress visibility
        cmd = ["claude", "-p", prompt, "--verbose"]
        if model:
            cmd.extend(["--model", model])
        if auto_accept and is_quine:
            # bypassPermissions only for quine runs (fully automated, no user present)
            cmd.extend(["--permission-mode", "bypassPermissions"])
        result = subprocess.run(cmd, capture_output=False, text=True)
    elif agent == "gemini":
        # Gemini CLI: gemini -p "prompt"
        cmd = ["gemini", "-p", prompt]
        if model:
            cmd.extend(["--model", model])
        if auto_accept:
            cmd.append("-y")
        result = subprocess.run(cmd, capture_output=False, text=True)
    else:
        raise ValueError(f"Unknown agent: {agent}")

    if result.returncode != 0:
        raise RuntimeError(f"Agent exited with code {result.returncode}")


def _load_arrangement(path: str):
    """Load and parse an Arrangement from a YAML file."""
    try:
        with open(path) as f:
            content = f.read()
        from .parser import SpecParser
        parser = SpecParser(os.getcwd())
        return parser.parse_arrangement(content)
    except FileNotFoundError:
        ui.print_error(f"Arrangement file not found: {path}")
        sys.exit(1)
    except Exception as e:
        ui.print_error(f"Failed to load arrangement: {e}")
        sys.exit(1)


def _discover_arrangement(core):
    """Auto-discover arrangement.yaml in the current working directory."""
    path = os.path.join(os.getcwd(), "arrangement.yaml")
    if os.path.exists(path):
        from .parser import SpecParser
        parser = SpecParser(os.getcwd())
        try:
            with open(path) as f:
                content = f.read()
            return parser.parse_arrangement(content)
        except Exception as e:
            ui.print_warning(f"Could not parse arrangement.yaml: {e}")
            return None
    return None


def _apply_arrangement(core, arrangement):
    """Apply arrangement settings to core (e.g. setup_commands)."""
    if arrangement and arrangement.environment and arrangement.environment.setup_commands:
        core.runner.setup_commands = arrangement.environment.setup_commands


def _resolve_arrangement(core, arrangement_arg):
    """Resolve arrangement: explicit file > auto-discovered > None."""
    if arrangement_arg:
        arr = _load_arrangement(arrangement_arg)
        ui.print_info(f"Using arrangement: [bold]{arrangement_arg}[/]")
        return arr
    arr = _discover_arrangement(core)
    if arr:
        ui.print_info("Using auto-discovered [bold]arrangement.yaml[/]")
    return arr


def _check_api_key():
    """Check that an API key is configured."""
    provider = os.environ.get("SPECSOLOIST_LLM_PROVIDER", "gemini")
    if provider == "gemini" and "GEMINI_API_KEY" not in os.environ:
        ui.print_error("GEMINI_API_KEY not set")
        ui.print_info("Run: export GEMINI_API_KEY='your-key-here'")
        sys.exit(1)
    elif provider == "anthropic" and "ANTHROPIC_API_KEY" not in os.environ:
        ui.print_error("ANTHROPIC_API_KEY not set")
        ui.print_info("Run: export ANTHROPIC_API_KEY='your-key-here'")
        sys.exit(1)


if __name__ == "__main__":
    main()