"""Alive CLI — the self-evolving AI agent."""

import asyncio
import logging
from typing import Optional

import typer
from rich.console import Console
from rich.panel import Panel
from rich.table import Table

from alive import __version__
from alive.config import ensure_dirs

app = typer.Typer(
    name="alive",
    help="A self-evolving AI agent that builds its own skills.",
    no_args_is_help=True,
    rich_markup_mode="rich",
)
console = Console()
_log = logging.getLogger("alive")


def _setup_logging(verbose: bool) -> None:
    """Configure logging — DEBUG if verbose, WARNING otherwise."""
    level = logging.DEBUG if verbose else logging.WARNING
    logging.basicConfig(level=level, format="%(name)s %(message)s")


def version_callback(value: bool) -> None:
    if value:
        console.print(f"[bold green]alive[/] v{__version__}")
        raise typer.Exit()


@app.callback()
def main(
    version: Optional[bool] = typer.Option(
        None, "--version", "-v", help="Show version and exit.", callback=version_callback, is_eager=True
    ),
) -> None:
    """[bold green]alive[/] — A self-evolving AI agent that builds its own skills."""
    ensure_dirs()


# ── Shared helpers ──────────────────────────────────────────────────────────


def _get_provider(name: str | None = None):
    """Resolve an LLM provider, showing a helpful error on failure."""
    from alive.llm import get_provider

    try:
        return get_provider(name)
    except ValueError as e:
        console.print(Panel(str(e), title="[bold red]Provider Error[/]", border_style="red"))
        raise typer.Exit(1)
    except ImportError as e:
        pkg = str(e).split("'")[-2] if "'" in str(e) else "the required package"
        console.print(Panel(
            f"Missing dependency: {e}\n\n"
            f"Fix: pip install {pkg}\n"
            f"Or switch to a local model: alive config --provider ollama",
            title="[bold red]Missing Package[/]",
            border_style="red",
        ))
        raise typer.Exit(1)


def _verbose_error(e: Exception, verbose: bool) -> str:
    """Format an error — full traceback if verbose, str otherwise."""
    if verbose:
        import traceback
        return traceback.format_exc()
    return str(e)


# ── alive run ────────────────────────────────────────────────────────────────


@app.command()
def run(
    request: str = typer.Argument(..., help="Natural language request for Alive to process."),
    provider: Optional[str] = typer.Option(None, "--provider", "-p", help="LLM provider override."),
    simple: bool = typer.Option(False, "--simple", "-s", help="Direct LLM call, skip planning."),
    dry_run: bool = typer.Option(False, "--dry-run", help="Show execution plan without running it."),
    verbose: bool = typer.Option(False, "--verbose", "-V", help="Show LLM prompts, skill code, and full errors."),
) -> None:
    """Process a natural language request."""
    _setup_logging(verbose)
    llm = _get_provider(provider)

    if simple:
        if dry_run:
            console.print(Panel(
                f"[bold]Request:[/] {request}\n[bold]Mode:[/] simple (direct LLM call)",
                title="[bold cyan]Dry Run[/]",
                border_style="cyan",
            ))
            return

        with console.status("[bold cyan]  Thinking...", spinner="dots"):
            try:
                result = asyncio.run(llm.complete(
                    "You are Alive, a helpful AI assistant. "
                    "Respond concisely and helpfully to the user's request.",
                    request,
                ))
            except Exception as e:
                console.print(Panel(
                    _verbose_error(e, verbose),
                    title="[bold red]LLM Error[/]",
                    border_style="red",
                ))
                raise typer.Exit(1)
        console.print()
        console.print(Panel(result, title="[bold green]alive[/]", border_style="green", padding=(1, 2)))
        _log_request(request, "success", [], [])
        return

    # Full pipeline: plan → compose → result
    from alive.core.composer import CompositionError, compose
    from alive.core.planner import display_plan, plan_request

    try:
        plan = asyncio.run(plan_request(request, provider=llm, console=console))
    except Exception as e:
        console.print(Panel(
            _verbose_error(e, verbose),
            title="[bold red]Planning Error[/]",
            border_style="red",
        ))
        _log_request(request, "failed", [], [])
        raise typer.Exit(1)

    if dry_run:
        display_plan(plan, console=console)
        console.print(f"\n[dim]Run without --dry-run to execute this plan.[/]")
        return

    try:
        result = asyncio.run(compose(plan, provider=llm, console=console))
    except CompositionError as e:
        console.print(Panel(str(e), title="[bold red]Aborted[/]", border_style="red"))
        _log_request(request, "failed", [], [])
        raise typer.Exit(1)
    except Exception as e:
        console.print(Panel(
            _verbose_error(e, verbose),
            title="[bold red]Execution Error[/]",
            border_style="red",
        ))
        _log_request(request, "failed", [], [])
        raise typer.Exit(1)

    # Log the request with skill info
    skills_used = []
    skills_built = []
    for sr in result.step_results:
        if sr.step.type == "EXISTING_SKILL" and sr.step.skill:
            skills_used.append(sr.step.skill)
        elif sr.step.type == "NEW_SKILL" and sr.success and sr.step.capability:
            skills_built.append(sr.step.capability)
    status_str = "success" if result.success else "partial"
    _log_request(request, status_str, skills_used, skills_built)

    console.print()
    console.print(Panel(result.final_output, title="[bold green]alive[/]", border_style="green", padding=(1, 2)))


def _log_request(
    request: str, status: str, skills_used: list[str], skills_built: list[str]
) -> None:
    """Best-effort request logging for dream log data."""
    try:
        from alive.core.self_model import record_request

        record_request(request, status, skills_used, skills_built)
    except Exception as e:
        _log.debug("Failed to log request: %s", e)


# ── alive build ──────────────────────────────────────────────────────────────


@app.command()
def build(
    description: str = typer.Argument(..., help="Describe the skill to build."),
    verbose: bool = typer.Option(False, "--verbose", "-V", help="Show generated code and LLM prompts."),
    provider: Optional[str] = typer.Option(None, "--provider", "-p", help="LLM provider override."),
) -> None:
    """Build a new skill from a natural language description."""
    _setup_logging(verbose)
    from alive.core.skill_builder import build_skill

    llm = _get_provider(provider) if provider else None

    try:
        asyncio.run(build_skill(description, console=console, verbose=verbose, provider=llm))
    except RuntimeError as e:
        console.print(Panel(
            f"{e}\n\n[dim]Try rephrasing or simplifying the description.[/]",
            title="[bold red]Build Failed[/]",
            border_style="red",
        ))
        raise typer.Exit(1)
    except ValueError as e:
        console.print(Panel(str(e), title="[bold red]Invalid Input[/]", border_style="red"))
        raise typer.Exit(1)


# ── alive plan ───────────────────────────────────────────────────────────────


@app.command()
def plan(
    request: str = typer.Argument(..., help="Natural language request to plan."),
    provider: Optional[str] = typer.Option(None, "--provider", "-p", help="LLM provider override."),
    verbose: bool = typer.Option(False, "--verbose", "-V", help="Show full LLM responses."),
) -> None:
    """Show the execution plan for a request without running it."""
    _setup_logging(verbose)
    from alive.core.planner import display_plan, plan_request

    llm = _get_provider(provider)

    try:
        result = asyncio.run(plan_request(request, provider=llm, console=console))
    except Exception as e:
        console.print(Panel(
            _verbose_error(e, verbose),
            title="[bold red]Planning Error[/]",
            border_style="red",
        ))
        raise typer.Exit(1)

    display_plan(result, console=console)


# ── alive skills ─────────────────────────────────────────────────────────────


@app.command()
def skills(
    search: Optional[str] = typer.Option(None, "--search", "-s", help="Search skills by keyword."),
) -> None:
    """List all installed skills."""
    from alive.core.skill_registry import get_registry

    registry = get_registry()
    skill_list = registry.search(search) if search else registry.list_all()
    if not skill_list:
        msg = f"No skills matching '{search}'." if search else "No skills installed yet."
        console.print(Panel(
            f"[dim]{msg}[/]\n\n"
            "[dim]Build one with:[/] alive build \"description of what it should do\"",
            title="[bold]Skills[/]",
            border_style="yellow",
        ))
        return

    table = Table(title="Installed Skills", border_style="cyan", show_lines=False)
    table.add_column("Name", style="bold green", no_wrap=True)
    table.add_column("Confidence", justify="right", style="cyan")
    table.add_column("Used", justify="right")
    table.add_column("Last Used", justify="right", style="dim")
    table.add_column("Description", max_width=50)

    for s in skill_list:
        conf = s.confidence_score
        conf_style = "green" if conf >= 0.8 else "yellow" if conf >= 0.5 else "red"
        table.add_row(
            s.name,
            f"[{conf_style}]{conf:.0%}[/{conf_style}]",
            str(s.times_used),
            s.last_used.split("T")[0] if s.last_used else "never",
            s.description[:50],
        )
    console.print(table)


# ── alive status ─────────────────────────────────────────────────────────────


@app.command()
def status(
    classify: bool = typer.Option(False, "--classify", help="Use LLM to classify skill domains."),
    provider: Optional[str] = typer.Option(None, "--provider", "-p", help="LLM provider for classification."),
    verbose: bool = typer.Option(False, "--verbose", "-V", help="Show classification details."),
) -> None:
    """Show the agent's self-model — capabilities, gaps, and stats."""
    _setup_logging(verbose)
    from alive.core.self_model import build_self_model, display_self_model

    llm = None
    if classify:
        llm = _get_provider(provider)

    try:
        model = asyncio.run(build_self_model(provider=llm))
    except Exception as e:
        console.print(Panel(
            _verbose_error(e, verbose),
            title="[bold red]Error[/]",
            border_style="red",
        ))
        raise typer.Exit(1)

    display_self_model(model, console=console)


# ── alive dream ──────────────────────────────────────────────────────────────


@app.command()
def dream(
    provider: Optional[str] = typer.Option(None, "--provider", "-p", help="LLM provider override."),
    verbose: bool = typer.Option(False, "--verbose", "-V", help="Show gathered data and LLM prompt."),
) -> None:
    """Generate a dream log — reflections on what Alive has learned."""
    _setup_logging(verbose)
    from alive.dream.dream_log import generate_dream

    llm = _get_provider(provider)

    try:
        asyncio.run(generate_dream(provider=llm, console=console))
    except Exception as e:
        console.print(Panel(
            _verbose_error(e, verbose),
            title="[bold red]Dream Error[/]",
            border_style="red",
        ))
        raise typer.Exit(1)


# ── alive share ──────────────────────────────────────────────────────────────


@app.command()
def share(
    skill_name: str = typer.Argument(..., help="Name of the skill to export."),
    gist: bool = typer.Option(False, "--gist", help="Also publish as a GitHub Gist (requires GITHUB_TOKEN)."),
) -> None:
    """Export a skill as a shareable archive."""
    from alive.sharing.export import export_skill

    try:
        result = export_skill(skill_name, create_gist=gist)
    except FileNotFoundError as e:
        console.print(Panel(
            f"{e}\n\n[dim]See available skills with:[/] alive skills",
            title="[bold red]Not Found[/]",
            border_style="red",
        ))
        raise typer.Exit(1)
    except ValueError as e:
        console.print(Panel(str(e), title="[bold red]Invalid Skill[/]", border_style="red"))
        raise typer.Exit(1)

    lines = f"[bold]{result.archive_path}[/]\n\nInstall command:\n  [cyan]{result.install_command}[/]"
    if result.gist_url:
        lines += f"\n\nGist: [link={result.gist_url}]{result.gist_url}[/link]"
    console.print(Panel(lines, title="[bold green]Skill Exported[/]", border_style="green"))


# ── alive install ────────────────────────────────────────────────────────────


@app.command()
def install(
    source: str = typer.Argument(..., help="Path, URL, or gist URL of a .alive-skill.tar.gz."),
    yes: bool = typer.Option(False, "--yes", "-y", help="Skip approval prompt."),
    no_test: bool = typer.Option(False, "--no-test", help="Skip running tests after install."),
) -> None:
    """Import a skill from a local archive, URL, or GitHub gist."""
    from rich.syntax import Syntax

    from alive.sharing.import_skill import import_skill, preview_skill

    # Step 1: Preview
    try:
        with console.status("[bold cyan]  Fetching skill...", spinner="dots"):
            preview = preview_skill(source)
    except FileNotFoundError as e:
        console.print(Panel(
            f"{e}\n\n[dim]Check the path or URL and try again.[/]",
            title="[bold red]Not Found[/]",
            border_style="red",
        ))
        raise typer.Exit(1)
    except ValueError as e:
        console.print(Panel(str(e), title="[bold red]Invalid Archive[/]", border_style="red"))
        raise typer.Exit(1)

    # Step 2: Show preview
    dep_text = ", ".join(preview.dependencies) if preview.dependencies else "[dim]none[/]"
    console.print(Panel(
        f"[bold]{preview.name}[/] — {preview.description}\n"
        f"[bold]Dependencies:[/] {dep_text}",
        title="[bold cyan]Skill Preview[/]",
        border_style="cyan",
    ))

    if "skill.py" in preview.files:
        console.print()
        console.print(Panel(
            Syntax(preview.files["skill.py"], "python", theme="monokai", line_numbers=True),
            title=f"[bold]{preview.name}/skill.py[/]",
            border_style="dim",
        ))

    # Step 3: Ask for approval
    if not yes:
        console.print()
        approve = typer.confirm("Install this skill?", default=True)
        if not approve:
            console.print("[yellow]Installation cancelled.[/]")
            raise typer.Exit(0)

    # Step 4: Install
    try:
        with console.status("[bold cyan]  Installing...", spinner="dots"):
            result = import_skill(source, run_tests=not no_test)
    except ValueError as e:
        console.print(Panel(str(e), title="[bold red]Install Error[/]", border_style="red"))
        raise typer.Exit(1)

    # Step 5: Show results
    if result.tests_passed is not None:
        if result.tests_passed:
            console.print(f"\n  [green]Tests passed[/]")
        else:
            console.print(f"\n  [red]Tests failed[/]")
            if result.test_output:
                console.print(Panel(
                    result.test_output[:1000],
                    title="[bold red]Test Output[/]",
                    border_style="red",
                ))

    console.print(Panel(
        f"Installed [bold green]{result.skill_name}[/]\n\n"
        f"[dim]Run it with:[/] alive run \"use {result.skill_name} to ...\"",
        title="[bold green]Skill Installed[/]",
        border_style="green",
    ))


# ── alive browse ─────────────────────────────────────────────────────────────


@app.command()
def browse(
    search: Optional[str] = typer.Option(None, "--search", "-s", help="Filter skills by keyword."),
) -> None:
    """Browse community skills available for installation."""
    from alive.sharing.browse import browse_skills

    try:
        with console.status("[bold cyan]  Fetching community skills...", spinner="dots"):
            skill_list = browse_skills(search=search)
    except ConnectionError as e:
        console.print(Panel(
            f"{e}\n\n[dim]Check your internet connection and try again.[/]",
            title="[bold red]Connection Error[/]",
            border_style="red",
        ))
        raise typer.Exit(1)

    if not skill_list:
        msg = f"No skills matching '{search}'." if search else "No community skills available yet."
        console.print(Panel(f"[dim]{msg}[/]", title="[bold]Community Skills[/]", border_style="yellow"))
        return

    table = Table(title="Community Skills", border_style="cyan", show_lines=False)
    table.add_column("Name", style="bold green", no_wrap=True)
    table.add_column("Description", max_width=40)
    table.add_column("Author", style="dim")
    table.add_column("Tags", style="cyan")
    table.add_column("Install", style="dim")

    for s in skill_list:
        table.add_row(
            s.name,
            s.description[:40],
            s.author,
            ", ".join(s.tags[:3]) if s.tags else "",
            f"alive install {s.install_url}" if s.install_url else "",
        )
    console.print(table)


# ── alive config ─────────────────────────────────────────────────────────────


@app.command()
def config(
    provider: Optional[str] = typer.Option(None, "--provider", "-p", help="Set the LLM provider."),
    claude_key: Optional[str] = typer.Option(None, "--claude-key", help="Set Claude API key."),
    openai_key: Optional[str] = typer.Option(None, "--openai-key", help="Set OpenAI API key."),
    deepseek_key: Optional[str] = typer.Option(None, "--deepseek-key", help="Set DeepSeek API key."),
    show: bool = typer.Option(False, "--show", help="Show current configuration."),
) -> None:
    """Configure Alive — set provider, API keys, and preferences."""
    import json

    from alive.config import CONFIG_FILE

    # Load existing config
    if CONFIG_FILE.exists():
        try:
            cfg = json.loads(CONFIG_FILE.read_text())
        except json.JSONDecodeError:
            console.print("[yellow]Config file was corrupted, starting fresh.[/]")
            cfg = {}
    else:
        cfg = {}

    if show:
        if not cfg:
            console.print(Panel(
                "[dim]No configuration set yet.[/]\n\n"
                "Get started:\n"
                "  alive config --provider claude --claude-key sk-ant-...\n"
                "  alive config --provider ollama  [dim]# no API key needed[/]",
                title="[bold]Config[/]",
                border_style="yellow",
            ))
        else:
            # Mask API keys
            display = {}
            for k, v in cfg.items():
                if "key" in k.lower() and isinstance(v, str) and len(v) > 8:
                    display[k] = v[:4] + "..." + v[-4:]
                else:
                    display[k] = v
            from rich.syntax import Syntax

            console.print(Panel(
                Syntax(json.dumps(display, indent=2), "json", theme="monokai"),
                title="[bold cyan]Current Config[/]",
                border_style="cyan",
            ))
        return

    if not any([provider, claude_key, openai_key, deepseek_key]):
        console.print(
            "[yellow]Usage:[/] alive config --show | --provider <name> | --claude-key <key>\n\n"
            "Available providers: claude, openai, ollama, deepseek"
        )
        return

    if provider:
        from alive.llm.registry import PROVIDERS

        if provider not in PROVIDERS:
            console.print(
                f"[red]Unknown provider '{provider}'.[/]\n\n"
                f"Choose from: {', '.join(PROVIDERS)}"
            )
            raise typer.Exit(1)
        cfg["provider"] = provider

    if claude_key:
        cfg["claude_key"] = claude_key
    if openai_key:
        cfg["openai_key"] = openai_key
    if deepseek_key:
        cfg["deepseek_key"] = deepseek_key

    CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
    CONFIG_FILE.write_text(json.dumps(cfg, indent=2))
    console.print(Panel(
        "[green]Configuration saved.[/]",
        title="[bold green]Config Updated[/]",
        border_style="green",
    ))


# ── alive init ───────────────────────────────────────────────────────────────


@app.command(name="init")
def init_cmd(
    provider: Optional[str] = typer.Option(None, "--provider", "-p", help="Set initial LLM provider."),
) -> None:
    """Initialize Alive — create directories, venv, and install seed skills."""
    import json

    from alive.config import ALIVE_HOME, CONFIG_FILE, DREAMS_DIR, SKILLS_DIR, VENV_DIR

    console.print(Panel(
        f"Setting up Alive at [bold]{ALIVE_HOME}[/]",
        title="[bold cyan]Initializing Alive[/]",
        border_style="cyan",
    ))

    # Step 1: Create directories
    for d in [ALIVE_HOME, SKILLS_DIR, DREAMS_DIR]:
        d.mkdir(parents=True, exist_ok=True)
    console.print("  [green]\u2713[/green] Created directories")

    # Step 2: Create venv
    with console.status("[bold cyan]  Creating Python venv...", spinner="dots"):
        from alive.sandbox.dependency import get_dependency_manager

        dm = get_dependency_manager()
        dm.ensure_venv()
    console.print(f"  [green]\u2713[/green] Created venv at [dim]{VENV_DIR}[/]")

    # Step 3: Install seed skills
    from alive.seeds import ensure_seeds
    from alive.seeds._installer import _SEED_NAMES

    ensure_seeds(SKILLS_DIR)
    console.print(f"  [green]\u2713[/green] Installed seed skills: {', '.join(_SEED_NAMES)}")

    # Step 4: Set provider if specified
    if provider:
        from alive.llm.registry import PROVIDERS

        if provider not in PROVIDERS:
            console.print(f"\n[yellow]Unknown provider '{provider}'. Skipping.[/]")
        else:
            cfg: dict = {}
            if CONFIG_FILE.exists():
                try:
                    cfg = json.loads(CONFIG_FILE.read_text())
                except json.JSONDecodeError:
                    pass
            cfg["provider"] = provider
            CONFIG_FILE.write_text(json.dumps(cfg, indent=2))
            console.print(f"  [green]\u2713[/green] Set provider to [bold]{provider}[/]")

    # Step 5: Detect available provider
    from alive.llm.registry import _auto_detect_provider, _inject_config_keys, _load_config

    cfg_data = _load_config()
    _inject_config_keys(cfg_data)
    detected = cfg_data.get("provider") or _auto_detect_provider()

    console.print(Panel(
        f"[bold green]Alive is ready![/]\n\n"
        f"[bold]Provider:[/] {detected}\n"
        f"[bold]Skills:[/] {len(_SEED_NAMES)} seed skills installed\n"
        f"[bold]Home:[/] {ALIVE_HOME}\n\n"
        f"[dim]Get started:[/]\n"
        f"  alive run \"what is 2 + 2\"\n"
        f"  alive build \"a skill that generates random passwords\"\n"
        f"  alive skills",
        title="[bold green]Setup Complete[/]",
        border_style="green",
    ))


# ── alive replay ────────────────────────────────────────────────────────────


@app.command()
def replay(
    skill_name: str = typer.Argument(..., help="Name of the skill to replay the build story for."),
) -> None:
    """Replay a skill's birth story — the full build journey from request to working code."""
    from alive.core.replay import display_birth_story, load_birth_story

    try:
        story = load_birth_story(skill_name)
    except FileNotFoundError as e:
        console.print(Panel(
            f"{e}\n\n[dim]See available skills with:[/] alive skills",
            title="[bold red]Not Found[/]",
            border_style="red",
        ))
        raise typer.Exit(1)

    display_birth_story(story, console=console)


# ── alive evolve ────────────────────────────────────────────────────────────


@app.command()
def evolve(
    skill_name: str = typer.Argument(..., help="Name of the skill to evolve."),
    provider: Optional[str] = typer.Option(None, "--provider", "-p", help="LLM provider override."),
    yes: bool = typer.Option(False, "--yes", "-y", help="Apply without confirmation."),
    rollback: bool = typer.Option(False, "--rollback", help="Rollback to previous version."),
    verbose: bool = typer.Option(False, "--verbose", "-V", help="Show full analysis."),
) -> None:
    """Evolve a skill — analyze and improve it with LLM suggestions."""
    _setup_logging(verbose)

    if rollback:
        from alive.core.evolve import rollback_skill

        if rollback_skill(skill_name):
            console.print(Panel(
                f"[bold green]{skill_name}[/] rolled back to previous version.",
                title="[bold green]Rollback Complete[/]",
                border_style="green",
            ))
        else:
            console.print(Panel(
                f"No previous version found for '{skill_name}'.\n\n"
                "[dim]Skills can only be rolled back after an evolution.[/]",
                title="[bold yellow]Nothing to Rollback[/]",
                border_style="yellow",
            ))
        return

    llm = _get_provider(provider)

    from alive.core.evolve import (
        analyze_skill,
        apply_evolution,
        display_evolution_result,
        display_suggestion,
    )

    # Analyze
    with console.status("[bold cyan]  Analyzing skill for improvements...", spinner="dots"):
        try:
            suggestion = asyncio.run(analyze_skill(skill_name, provider=llm))
        except FileNotFoundError as e:
            console.print(Panel(
                f"{e}",
                title="[bold red]Not Found[/]",
                border_style="red",
            ))
            raise typer.Exit(1)
        except Exception as e:
            console.print(Panel(
                _verbose_error(e, verbose),
                title="[bold red]Analysis Error[/]",
                border_style="red",
            ))
            raise typer.Exit(1)

    if suggestion is None:
        console.print(Panel(
            f"[bold]{skill_name}[/] looks good — no improvements suggested.\n\n"
            "[dim]The skill is well-written and doesn't need changes right now.[/]",
            title="[bold green]No Changes Needed[/]",
            border_style="green",
        ))
        return

    # Show suggestion
    display_suggestion(suggestion, console)

    # Confirm
    if not yes:
        console.print()
        approve = typer.confirm("Apply this evolution?", default=True)
        if not approve:
            console.print("[yellow]Evolution cancelled.[/]")
            return

    # Apply
    with console.status("[bold cyan]  Testing and applying evolution...", spinner="dots"):
        result = apply_evolution(suggestion)

    display_evolution_result(result, console)


# ── alive watch ─────────────────────────────────────────────────────────────


@app.command()
def watch(
    lines: int = typer.Option(200, "--lines", "-n", help="Number of history lines to analyze."),
    provider: Optional[str] = typer.Option(None, "--provider", "-p", help="LLM provider override."),
    verbose: bool = typer.Option(False, "--verbose", "-V", help="Show raw history and LLM prompt."),
) -> None:
    """Watch your shell history and suggest skills to automate repeated patterns."""
    _setup_logging(verbose)
    llm = _get_provider(provider)

    from alive.core.watch import analyze_history, display_suggestions

    with console.status("[bold cyan]  Analyzing your shell history...", spinner="dots"):
        try:
            suggestions = asyncio.run(analyze_history(provider=llm, n_lines=lines))
        except FileNotFoundError as e:
            console.print(Panel(
                f"{e}",
                title="[bold red]No History Found[/]",
                border_style="red",
            ))
            raise typer.Exit(1)
        except ValueError as e:
            console.print(Panel(
                f"{e}",
                title="[bold yellow]Not Enough Data[/]",
                border_style="yellow",
            ))
            raise typer.Exit(1)
        except Exception as e:
            console.print(Panel(
                _verbose_error(e, verbose),
                title="[bold red]Analysis Error[/]",
                border_style="red",
            ))
            raise typer.Exit(1)

    display_suggestions(suggestions, console)
