#
#   Imandra Inc.
#
#   codelogician/commands/cmd_doc.py
#

import json
import shutil
from pathlib import Path
from typing import Annotated

import typer

from codelogician.doc import DOC_COMMAND_HELP_AGENT, DOC_COMMAND_HELP_HUMAN
from codelogician.doc.search import (
    SEARCH_HELP,
    SEARCH_SCHEMA,
    handle_json_search,
    search_error_corpus,
    search_md,
    search_prelude,
)

__all__ = ('app', 'DOC_COMMAND_HELP_HUMAN')

app = typer.Typer(help=DOC_COMMAND_HELP_HUMAN, rich_markup_mode='markdown')


@app.callback(invoke_without_command=True)
def _main(
    ctx: typer.Context,
):
    if ctx.invoked_subcommand is None:
        typer.echo(ctx.get_help())


@app.command(
    name='help',
    help='Show help message for subcommands. Use `doc help --agent` for agent-specific documentation',
)
def help(
    ctx: typer.Context,
    agent: Annotated[
        bool, typer.Option('--agent', help='Show agent-specific documentation')
    ] = False,
):
    if agent:
        typer.echo(DOC_COMMAND_HELP_AGENT)
    else:
        typer.echo(ctx.get_help())


@app.command(
    name='dump', help='Save the skill/ documentation directory to a specified path'
)
def dump(
    path: Annotated[
        Path, typer.Argument(help='Directory path to copy skill/ docs into')
    ],
):
    from codelogician.doc.search import SKILL_DIR

    src = SKILL_DIR
    dest = path.resolve()
    if dest.exists():
        typer.secho(
            f'Destination already exists: {dest}', fg=typer.colors.RED, err=True
        )
        raise typer.Exit(1)
    shutil.copytree(src, dest)
    typer.secho(f'Copied skill/ to {dest}', fg=typer.colors.GREEN)


# search subcommand group
# ====================


search_app = typer.Typer(help=SEARCH_HELP)
app.add_typer(search_app, name='search')


def _render_md_results(results: list[dict]) -> None:
    for r in results:
        if r['score'] == 0.0:
            typer.echo(f'--- {r["path"]} ---')
            typer.echo(r['content'])
            typer.echo()
        else:
            typer.echo(r['path'])
            if r.get('name'):
                typer.echo(f'  name: {r["name"]}')
            if r.get('description'):
                typer.echo(f'  desc: {r["description"]}')
            typer.echo(
                f'  (fuzzy match, use `doc search md "{r.get("name", r["path"])}"` for full content)'
            )
            typer.echo()


@search_app.callback(invoke_without_command=True)
def _search_main(
    ctx: typer.Context,
):
    if ctx.invoked_subcommand is None:
        typer.echo(ctx.get_help())


@search_app.command(name='md', help='Fuzzy search markdown file names and descriptions')
def search_md_cmd(
    query: Annotated[str, typer.Argument(help='Search query')],
    as_json: Annotated[
        bool, typer.Option('--json', help='Pass a JSON search request')
    ] = False,
    max_results: Annotated[
        int, typer.Option('--max-results', '-n', help='Maximum number of results')
    ] = 2,
):
    if as_json:
        results = handle_json_search(query)
        typer.echo(json.dumps(results, indent=2))
        return

    results = search_md(query, max_results=max_results)
    if not results:
        typer.secho('No matching documents found.', fg=typer.colors.YELLOW, err=True)
        raise typer.Exit(1)

    _render_md_results(results)


@search_app.command(name='error', help='Search the error corpus (error_corpus.json)')
def search_error_cmd(
    query: Annotated[str, typer.Argument(help='Search query')],
    max_results: Annotated[
        int, typer.Option('--max-results', '-n', help='Maximum number of results')
    ] = 10,
):
    results = search_error_corpus(query, max_results=max_results)
    if not results:
        typer.secho('No matching errors found.', fg=typer.colors.YELLOW, err=True)
        raise typer.Exit(1)

    for item in results:
        typer.echo(f'{item.get("kind", "?")}: {item.get("name", "(unnamed)")}')
        typer.echo(f'  msg: {item.get("msg_str", "")[:120]}')
        if item.get('explanation'):
            typer.echo(f'  explanation: {item["explanation"][:120]}')
        if item.get('solution_description'):
            typer.echo(f'  fix: {item["solution_description"][:120]}')
        typer.echo()


@search_app.command(name='prelude', help='Search the prelude reference (prelude.json)')
def search_prelude_cmd(
    query: Annotated[str, typer.Argument(help='Search query')],
    max_results: Annotated[
        int, typer.Option('--max-results', '-n', help='Maximum number of results')
    ] = 10,
):
    results = search_prelude(query, max_results=max_results)
    if not results:
        typer.secho(
            'No matching prelude entries found.', fg=typer.colors.YELLOW, err=True
        )
        raise typer.Exit(1)

    for entry in results:
        module = entry.get('module', '') or '(global)'
        typer.echo(f'{module}.{entry.get("name", "?")} [{entry.get("type", "?")}]')
        typer.echo(f'  sig: {entry.get("signature", "")}')
        if entry.get('doc'):
            typer.echo(f'  doc: {entry["doc"][:120]}')
        typer.echo()


@search_app.command(name='schema', help='Print the JSON schema for search requests')
def search_schema_cmd():
    typer.echo(json.dumps(SEARCH_SCHEMA, indent=2))
