"""CLI-specific helpers: JSON formatter, Rich rendering, and CLI workflows."""

from __future__ import annotations

import asyncio
import json
from collections.abc import Generator
from pathlib import Path
from typing import TYPE_CHECKING

from rich.console import Console, RenderableType
from rich.table import Table

from lilbee.app.ingest import copy_files
from lilbee.app.status import StatusResult
from lilbee.cli import theme
from lilbee.core.config import cfg

if TYPE_CHECKING:
    from lilbee.cli.sync import SyncStatus


def json_output(data: dict) -> None:
    """Print a JSON object to stdout."""
    print(json.dumps(data))


def render_status_result(status: StatusResult) -> Generator[RenderableType, None, None]:
    """Yield Rich renderables for a :class:`StatusResult`."""
    yield f"[{theme.LABEL}]Documents:[/{theme.LABEL}]  {status.config.documents_dir}"
    yield f"[{theme.LABEL}]Database:[/{theme.LABEL}]   {status.config.data_dir}"
    yield f"[{theme.LABEL}]Chat model:[/{theme.LABEL}] {status.config.chat_model}"
    yield f"[{theme.LABEL}]Embeddings:[/{theme.LABEL}] {status.config.embedding_model}"
    vision = status.config.vision_model or "(disabled)"
    reranker = status.config.reranker_model or "(disabled)"
    yield f"[{theme.LABEL}]Vision:[/{theme.LABEL}]     {vision}"
    yield f"[{theme.LABEL}]Reranker:[/{theme.LABEL}]   {reranker}"
    if status.config.enable_ocr is not None:
        ocr_label = "enabled" if status.config.enable_ocr else "disabled"
        yield f"[{theme.LABEL}]Vision OCR:[/{theme.LABEL}] {ocr_label}"
    yield ""

    if not status.sources:
        yield (
            "No documents indexed. Drop files into the documents directory and run 'lilbee sync'."
        )
        return

    table = Table(title="Indexed Documents")
    table.add_column("File", style=theme.ACCENT)
    table.add_column("Hash", style=theme.MUTED, max_width=12)
    table.add_column("Chunks", justify="right")
    table.add_column("Ingested", style=theme.MUTED)
    for s in status.sources:
        table.add_row(s.filename, s.file_hash, str(s.chunk_count), s.ingested_at)
    yield table
    b = theme.LABEL
    yield f"\n[{b}]{len(status.sources)}[/{b}] documents, [{b}]{status.total_chunks}[/{b}] chunks"


def render_status(con: Console) -> None:
    """Print status info (documents, paths, chunk counts)."""
    from lilbee.app.status import gather_status

    for renderable in render_status_result(gather_status()):
        con.print(renderable)


def copy_paths(paths: list[Path], con: Console, *, force: bool = False) -> list[str]:
    """Copy *paths* into the documents directory. Returns list of copied names."""
    result = copy_files(paths, force=force)
    for name in result.skipped:
        con.print(
            f"[{theme.WARNING}]Warning:[/{theme.WARNING}] {name} already exists in knowledge base "
            f"(use --force to overwrite)"
        )
    return result.copied


def add_paths(
    paths: list[Path],
    con: Console,
    *,
    force: bool = False,
    background: bool = False,
    chat_mode: bool = False,
    sync_status: SyncStatus | None = None,
) -> None:
    """Copy *paths* into the knowledge base and sync (human output).
    When *background* is True (chat ``/add``), sync runs in a background thread
    and this function returns immediately after copying files.
    """
    from lilbee.data.ingest import sync

    copied = copy_paths(paths, con, force=force)
    if chat_mode:
        print(f"Copied {len(copied)} path(s) to {cfg.documents_dir}")
    else:
        con.print(
            f"[{theme.MUTED}]Copied {len(copied)} path(s) to {cfg.documents_dir}[/{theme.MUTED}]"
        )

    if background:
        from lilbee.cli.sync import run_sync_background

        run_sync_background(con, chat_mode=chat_mode, sync_status=sync_status)
        return

    result = asyncio.run(sync())
    con.print(result)


def sync_result_to_json(result: object) -> dict:
    """Convert a SyncResult to the JSON output envelope."""
    from lilbee.data.ingest import SyncResult

    if not isinstance(result, SyncResult):
        raise TypeError(f"Expected SyncResult, got {type(result).__name__}")
    return {"command": "sync", **result.model_dump()}


def auto_sync(con: Console, *, background: bool = False) -> None:
    """Run document sync before queries.
    When *background* is True, sync runs in a background thread and this
    function returns immediately (for chat/REPL).  When False (default),
    sync blocks until complete (for ``lilbee ask``).
    """
    if background:
        from lilbee.cli.sync import run_sync_background

        run_sync_background(con)
        return

    from lilbee.data.ingest import sync

    try:
        result = asyncio.run(sync())
    except RuntimeError as exc:
        con.print(f"[{theme.ERROR}]Error:[/{theme.ERROR}] {exc}")
        raise SystemExit(1) from None
    total = (
        len(result.added)
        + len(result.updated)
        + len(result.removed)
        + len(result.failed)
        + len(result.skipped)
    )
    if total:
        con.print(
            f"[{theme.MUTED}]Synced: {len(result.added)} added, "
            f"{len(result.updated)} updated, "
            f"{len(result.removed)} removed, "
            f"{len(result.skipped)} skipped, "
            f"{len(result.failed)} failed[/{theme.MUTED}]"
        )
