#
#   Imandra Inc.
#
#   speclogician/commands/find_cmd.py
#

from __future__ import annotations

from typing import Any

import typer
from rich.table import Table

from .utils import get_state
from ..utils import console

app = typer.Typer(help="Find various components of the state")


# -----------------------------------------------------------------------------
# helpers
# -----------------------------------------------------------------------------

def _matches_component(q: str, *, name: str, src_code: str) -> bool:
    qq = (q or "").lower()
    return (qq in (name or "").lower()) or (qq in (src_code or "").lower())


def _emit_json(payload: dict[str, Any]) -> None:
    # Keep stdout clean for machine mode (matches your convention elsewhere)
    import json as _json
    typer.echo(_json.dumps(payload, indent=2, sort_keys=True))


def _preview(src: str, n: int = 160) -> str:
    return (src or "")[:n].replace("\n", " ")


# -----------------------------------------------------------------------------
# predicates
# -----------------------------------------------------------------------------

@app.command(name="predicates", help="Search state/action predicates by name or source text")
def search_predicates(
    ctx: typer.Context,
    q: str = typer.Argument(..., help="Search string (name or source snippet)"),
    kind: str = typer.Option(
        "all",
        "--kind",
        help="Search scope: all|state|action",
    ),
    json_only: bool = typer.Option(
        False,
        "--json",
        help="Emit JSON only (no rich output)",
    ),
) -> None:
    state = get_state(ctx=ctx)
    dm = state.curr_state().spec.domain_model

    k = (kind or "all").lower().strip()
    if k not in ("all", "state", "action"):
        raise typer.BadParameter("kind must be one of: all|state|action")

    matches: list[dict[str, Any]] = []

    if k in ("all", "state"):
        for p in getattr(dm, "state_preds", []):
            name = getattr(p, "name", "")
            src_code = getattr(p, "src_code", "")
            if _matches_component(q, name=name, src_code=src_code):
                matches.append(
                    {
                        "type": "StatePredicate",
                        "kind": getattr(p, "kind", "state_predicate"),
                        "name": name,
                        "src_preview": _preview(src_code),
                    }
                )

    if k in ("all", "action"):
        for p in getattr(dm, "action_preds", []):
            name = getattr(p, "name", "")
            src_code = getattr(p, "src_code", "")
            if _matches_component(q, name=name, src_code=src_code):
                matches.append(
                    {
                        "type": "ActionPredicate",
                        "kind": getattr(p, "kind", "action_predicate"),
                        "name": name,
                        "src_preview": _preview(src_code),
                    }
                )

    # Deterministic output (nice for tests)
    matches.sort(key=lambda m: (m.get("type", ""), m.get("name", "")))

    if json_only:
        _emit_json(
            {
                "query": q,
                "kind": k,
                "count": len(matches),
                "results": matches,
            }
        )
        return

    t = Table(title=f"Predicate search for {q!r}", show_header=True, header_style="bold")
    t.add_column("Type", no_wrap=True)
    t.add_column("Name", no_wrap=True)
    t.add_column("Preview")
    for m in matches:
        prev = m["src_preview"]
        t.add_row(m["type"], m["name"], prev + ("…" if len(prev) == 160 else ""))
    console.print(t)


# -----------------------------------------------------------------------------
# transitions
# -----------------------------------------------------------------------------

@app.command(name="transitions", help="Search transition functions by name or source text")
def search_transitions(
    ctx: typer.Context,
    q: str = typer.Argument(..., help="Search string (name or source snippet)"),
    json_only: bool = typer.Option(
        False,
        "--json",
        help="Emit JSON only (no rich output)",
    ),
) -> None:
    state = get_state(ctx=ctx)
    dm = state.curr_state().spec.domain_model

    matches: list[dict[str, Any]] = []

    for tr in getattr(dm, "transitions", []):
        name = getattr(tr, "name", "")
        src_code = getattr(tr, "src_code", "")
        if _matches_component(q, name=name, src_code=src_code):
            matches.append(
                {
                    "type": "Transition",
                    "kind": getattr(tr, "kind", "transition"),
                    "name": name,
                    "src_preview": _preview(src_code),
                }
            )

    matches.sort(key=lambda m: m.get("name", ""))

    if json_only:
        _emit_json(
            {
                "query": q,
                "count": len(matches),
                "results": matches,
            }
        )
        return

    t = Table(title=f"Transition search for {q!r}", show_header=True, header_style="bold")
    t.add_column("Name", no_wrap=True)
    t.add_column("Preview")
    for m in matches:
        prev = m["src_preview"]
        t.add_row(m["name"], prev + ("…" if len(prev) == 160 else ""))
    console.print(t)


# -----------------------------------------------------------------------------
# artifacts (smart_find + PM renderer)
# -----------------------------------------------------------------------------

@app.command("artifacts")
def search_artifacts(
    ctx: typer.Context,
    q: str = typer.Argument(..., help="Search string"),
    kind: str = typer.Option(
        "all",
        "--kind",
        help="Search scope: all|tests|logs|docs|src",
    ),
    json_only: bool = typer.Option(
        False,
        "--json",
        help="Emit JSON only (no rich output)",
    ),
    ids: bool = typer.Option(
        False,
        "--ids",
        help="Show artifact IDs in rich output",
    ),
    compact: bool = typer.Option(
        False,
        "--compact",
        help="Use compact tables/panels in rich output",
    ),
    show_empty: bool = typer.Option(
        False,
        "--show-empty",
        help="Render empty sections in rich output",
    ),
    max_per_type: int = typer.Option(
        50,
        "--max-per-type",
        min=1,
        help="Maximum results to show per artifact type",
    ),
) -> None:
    """Smart text search across artifacts (tests/logs/docs/src)."""
    state = get_state(ctx=ctx)
    ac = state.curr_state().art_container

    k = (kind or "all").lower().strip()
    include: set[str] | None
    if k in ("all", "*"):
        include = None
    elif k in ("tests", "test"):
        include = {"test"}
    elif k in ("logs", "log"):
        include = {"log"}
    elif k in ("docs", "doc"):
        include = {"doc"}
    elif k in ("src", "source"):
        include = {"src"}
    else:
        raise typer.BadParameter("kind must be one of: all|tests|logs|docs|src")

    # --- search ---
    res = ac.smart_find(q, include=include, max_per_type=max_per_type)

    # --- build PM + render ---
    from speclogician.presentation.ctx import RenderCtx
    from speclogician.presentation.builders.smart_find import build_smart_find_pm
    from speclogician.presentation.renderers.smart_find import render_smart_find

    rctx = RenderCtx(
        show_art_ids=ids,
        show_ids=ids,
        compact=compact,
        show_empty_sections=show_empty,  # now a real field on RenderCtx
    )

    pm = build_smart_find_pm(res, rctx)

    if json_only:
        payload = pm.model_dump()
        # test-friendly extra metadata
        payload["query"] = q
        payload["kind"] = k
        _emit_json(payload)
        return

    console.print(render_smart_find(pm, ctx=rctx))