"""Main CLI application for arxivy."""

from __future__ import annotations

import sys
from collections.abc import Callable
from typing import Annotated, Any, Optional

import typer
from rich.console import Console

from arxivy import __version__
from arxivy.api.errors import ArxivError
from arxivy.api.types import SortBy, SortOrder
from arxivy.commands import run_bibtex, run_new, run_paper, run_search
from arxivy.output import OutputMode, render_to_console, render_to_string, resolve_output_mode

app = typer.Typer(
    name="arxivy",
    help="""CLI for the arXiv API, for humans and agents alike.

Search papers, fetch metadata, and export BibTeX from arXiv.

Typical workflow:
  1. Search: arxivy search "attention" --json
  2. Get details: arxivy paper 1706.03762
  3. Export BibTeX: arxivy bibtex 1706.03762

Output formats:
  Default (TTY): human-readable Rich table
  Piped / --json: structured JSON
  --bibtex / -b: BibTeX entries

JSON output structure:
  Search/new: {"results": [...], "meta": {"total_results": N, ...}}
  Single paper: {"result": {...}}
  Errors: {"error": {"code": "...", "message": "..."}}
""",
    no_args_is_help=True,
)

console = Console()


def _version_callback(value: bool) -> None:
    if value:
        typer.echo(f"arxivy {__version__}")
        raise typer.Exit()


def is_interactive() -> bool:
    """Check if stdout is connected to a terminal."""
    return sys.stdout.isatty()


def output_results(
    data,
    *,
    use_json: bool = False,
    use_bibtex: bool = False,
    show_abstract: bool = False,
) -> None:
    is_tty = is_interactive()
    mode = resolve_output_mode(use_json=use_json, use_bibtex=use_bibtex, is_tty=is_tty)
    if mode in (OutputMode.JSON, OutputMode.BIBTEX):
        print(render_to_string(data, mode=mode, pretty=is_tty))
        return

    render_to_console(data, console=console, show_abstract=show_abstract)


def output_error(e: ArxivError, *, use_json: bool = False, use_bibtex: bool = False) -> None:
    """Output an error in the appropriate format.

    Errors should follow the same output-mode rules as successful responses.
    """
    is_tty = is_interactive()
    mode = resolve_output_mode(use_json=use_json, use_bibtex=use_bibtex, is_tty=is_tty)

    # For errors, BibTeX isn't meaningful. Fall back to JSON when BibTeX is requested.
    if mode is OutputMode.BIBTEX:
        mode = OutputMode.JSON

    if mode is OutputMode.JSON:
        print(render_to_string(e.to_dict(), mode=mode, pretty=is_tty))
    else:
        console.print(f"[red]Error:[/red] {e.message}")
        if e.suggestion:
            console.print(f"[dim]{e.suggestion}[/dim]")
    raise typer.Exit(1)


def run_and_output(
    func: Callable[..., Any],
    *,
    func_kwargs: dict[str, Any],
    use_json: bool,
    use_bibtex: bool,
    show_abstract: bool,
) -> None:
    try:
        data = func(**func_kwargs)
        output_results(data, use_json=use_json, use_bibtex=use_bibtex, show_abstract=show_abstract)
    except ArxivError as e:
        output_error(e, use_json=use_json, use_bibtex=use_bibtex)


@app.callback()
def main(
    version: Annotated[
        bool,
        typer.Option("--version", "-v", callback=_version_callback, is_eager=True, help="Show version and exit."),
    ] = False,
) -> None:
    """CLI for the arXiv API, for humans and agents alike."""


@app.command()
def search(
    query: Annotated[str, typer.Argument(help="Search query")],
    limit: Annotated[int, typer.Option("-n", "--limit", help="Maximum number of results")] = 10,
    offset: Annotated[int, typer.Option("--offset", help="Pagination offset")] = 0,
    category: Annotated[Optional[str], typer.Option("-c", "--category", help="Filter by category (e.g. cs.AI)")] = None,
    sort: Annotated[
        Optional[SortBy],
        typer.Option(
            "--sort",
            help="Sort by.",
            show_choices=True,
        ),
    ] = None,
    order: Annotated[
        Optional[SortOrder],
        typer.Option(
            "--order",
            help="Sort order.",
            show_choices=True,
        ),
    ] = None,
    json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
    bibtex_output: Annotated[bool, typer.Option("--bibtex", "-b", help="Output as BibTeX")] = False,
    abstract: Annotated[bool, typer.Option("--abstract", "-a", help="Show abstracts in table output")] = False,
) -> None:
    """Search papers on arXiv.

    Examples:
        arxivy search "attention is all you need"
        arxivy search "transformers" --category cs.CL --limit 20
        arxivy search "diffusion models" --sort submittedDate --json
        arxivy search "reinforcement learning" --abstract
    """
    run_and_output(
        run_search,
        func_kwargs={
            "query": query,
            "limit": limit,
            "offset": offset,
            "category": category,
            "sort_by": sort,
            "sort_order": order,
        },
        use_json=json_output,
        use_bibtex=bibtex_output,
        show_abstract=abstract,
    )


@app.command()
def paper(
    ids: Annotated[list[str], typer.Argument(help="One or more arXiv IDs (e.g. 1706.03762)")],
    json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
    bibtex_output: Annotated[bool, typer.Option("--bibtex", "-b", help="Output as BibTeX")] = False,
    abstract: Annotated[bool, typer.Option("--abstract", "-a", help="Show abstracts in table output")] = False,
) -> None:
    """Fetch paper(s) by arXiv ID.

    Single ID shows a detailed view (always includes abstract).
    Multiple IDs show a table; use --abstract to include abstracts.
    Supports full URLs (https://arxiv.org/abs/...) and old-style IDs.

    Examples:
        arxivy paper 1706.03762
        arxivy paper 1706.03762 2010.11929 --json
        arxivy paper https://arxiv.org/abs/1706.03762
    """
    run_and_output(
        run_paper,
        func_kwargs={"ids": ids},
        use_json=json_output,
        use_bibtex=bibtex_output,
        show_abstract=abstract,
    )


@app.command()
def bibtex(
    ids: Annotated[list[str], typer.Argument(help="One or more arXiv IDs")],
) -> None:
    """Export BibTeX for arXiv papers.

    Shortcut for: arxivy paper <ids> --bibtex

    Examples:
        arxivy bibtex 1706.03762
        arxivy bibtex 1706.03762 2010.11929
    """
    run_and_output(
        run_bibtex,
        func_kwargs={"ids": ids},
        use_json=False,
        use_bibtex=True,
        show_abstract=False,
    )


@app.command()
def new(
    category: Annotated[str, typer.Argument(help="arXiv category (e.g. cs.AI, math.CO, hep-ph)")],
    limit: Annotated[int, typer.Option("-n", "--limit", help="Maximum number of results")] = 10,
    json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
    bibtex_output: Annotated[bool, typer.Option("--bibtex", "-b", help="Output as BibTeX")] = False,
    abstract: Annotated[bool, typer.Option("--abstract", "-a", help="Show abstracts in table output")] = False,
) -> None:
    """Show recent papers in an arXiv category.

    Papers are sorted by submission date (newest first).

    Examples:
        arxivy new cs.AI
        arxivy new cs.CL --limit 20 --json
        arxivy new math.CO -n 5 --bibtex
        arxivy new cs.LG -n 3 --abstract
    """
    run_and_output(
        run_new,
        func_kwargs={"category": category, "limit": limit},
        use_json=json_output,
        use_bibtex=bibtex_output,
        show_abstract=abstract,
    )


if __name__ == "__main__":
    app()
