#
#   Imandra Inc.
#
#   speclogician/presentation/renderers/smart_find.py
#

from __future__ import annotations

from rich import box
from rich.console import Group
from rich.panel import Panel
from rich.rule import Rule
from rich.table import Table, Column
from rich.text import Text

from speclogician.presentation.ctx import RenderCtx
from speclogician.presentation.models.smart_find import SmartFindPM


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

def _snip(s: str | None, n: int = 140) -> str:
    x = (s or "").strip()
    if not x:
        return ""
    x = x.replace("\n", " ")
    return (x[: n - 1] + "…") if len(x) > n else x


def _validity_text(iml_valid: str) -> Text:
    v = (iml_valid or "").lower()
    if v == "valid":
        return Text("✓ valid", style="bold green")
    if v == "invalid":
        return Text("✗ invalid", style="bold red")
    return Text("? unknown", style="bold yellow")


def _counts_table(pm: SmartFindPM, *, ctx: RenderCtx) -> Table:
    b = box.MINIMAL if ctx.compact else box.SIMPLE
    t = Table(
        show_header=True,
        header_style="bold",
        box=b,
        show_edge=False,
        pad_edge=False,
        expand=False,  # keep count column close
    )
    t.add_column("Type", style="dim", no_wrap=True)
    t.add_column("Count", justify="right", no_wrap=True)

    c = pm.counts
    t.add_row("Test traces", str(c.num_test_traces))
    t.add_row("Log traces", str(c.num_log_traces))
    t.add_row("Doc refs", str(c.num_doc_refs))
    t.add_row("Src code refs", str(c.num_src_code_refs))
    return t


def _want_art_id(ctx: RenderCtx) -> bool:
    return bool(getattr(ctx, "show_art_ids", False) or getattr(ctx, "show_ids", False))


def _rule(title: str, n: int) -> Rule:
    return Rule(f"[bold]{title}[/bold] ({n})", style="dim")


# -----------------------------------------------------------------------------
# per-kind tables
# -----------------------------------------------------------------------------

def _test_traces_table(pm: SmartFindPM, *, ctx: RenderCtx) -> Table:
    b = box.MINIMAL if ctx.compact else box.SIMPLE
    show_art_id = _want_art_id(ctx)

    cols: list[Column] = []
    if show_art_id:
        cols.append(Column("art_id", style="dim", no_wrap=True, width=12))
    cols += [
        Column("name", no_wrap=False, width=34, overflow="ellipsis"),
        Column("iml", no_wrap=True, width=9),
        Column("time", style="dim", no_wrap=True, width=20),
        Column("snippet", no_wrap=False, width=68, overflow="ellipsis"),
    ]

    t = Table(
        *cols,
        show_header=True,
        header_style="bold",
        box=b,
        show_edge=False,
        pad_edge=False,
        expand=False,
    )

    for tt in pm.items.test_traces:
        core = tt.core
        label = tt.name or tt.filepath or "-"
        snippet = (
            _snip(tt.contents, 180)
            or _snip(core.given, 180)
            or _snip(core.when, 180)
            or _snip(core.then, 180)
            or "—"
        )

        row = []
        if show_art_id:
            row.append(Text(core.art_id or "-", style="dim"))
        row += [
            Text(label, style="bold"),
            _validity_text(core.iml_validity.iml_valid),
            Text(core.time or "—", style="dim"),
            Text(snippet, style="dim" if snippet == "—" else ""),
        ]
        t.add_row(*row)

    if not pm.items.test_traces:
        if show_art_id:
            t.add_row(Text("-", style="dim"), Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("", style="dim"))
        else:
            t.add_row(Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("", style="dim"), Text("", style="dim"))

    return t


def _log_traces_table(pm: SmartFindPM, *, ctx: RenderCtx) -> Table:
    b = box.MINIMAL if ctx.compact else box.SIMPLE
    show_art_id = _want_art_id(ctx)

    cols: list[Column] = []
    if show_art_id:
        cols.append(Column("art_id", style="dim", no_wrap=True, width=12))
    cols += [
        Column("filename", no_wrap=False, width=34, overflow="ellipsis"),
        Column("iml", no_wrap=True, width=9),
        Column("time", style="dim", no_wrap=True, width=20),
        Column("snippet", no_wrap=False, width=68, overflow="ellipsis"),
    ]

    t = Table(
        *cols,
        show_header=True,
        header_style="bold",
        box=b,
        show_edge=False,
        pad_edge=False,
        expand=False,
    )

    for lt in pm.items.log_traces:
        core = lt.core
        label = lt.filename or "-"
        snippet = (
            _snip(lt.contents, 180)
            or _snip(core.given, 180)
            or _snip(core.when, 180)
            or _snip(core.then, 180)
            or "—"
        )

        row = []
        if show_art_id:
            row.append(Text(core.art_id or "-", style="dim"))
        row += [
            Text(label, style="bold"),
            _validity_text(core.iml_validity.iml_valid),
            Text(core.time or "—", style="dim"),
            Text(snippet, style="dim" if snippet == "—" else ""),
        ]
        t.add_row(*row)

    if not pm.items.log_traces:
        if show_art_id:
            t.add_row(Text("-", style="dim"), Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("", style="dim"))
        else:
            t.add_row(Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("", style="dim"), Text("", style="dim"))

    return t


def _doc_refs_table(pm: SmartFindPM, *, ctx: RenderCtx) -> Table:
    b = box.MINIMAL if ctx.compact else box.SIMPLE
    show_art_id = _want_art_id(ctx)

    cols: list[Column] = []
    if show_art_id:
        cols.append(Column("art_id", style="dim", no_wrap=True, width=12))
    cols += [
        Column("meta", no_wrap=False, width=46, overflow="ellipsis"),
        Column("snippet", no_wrap=False, width=92, overflow="ellipsis"),
    ]

    t = Table(
        *cols,
        show_header=True,
        header_style="bold",
        box=b,
        show_edge=False,
        pad_edge=False,
        expand=False,
    )

    for dr in pm.items.doc_refs:
        label = dr.meta or "DocRef"
        snippet = _snip(dr.text, 220) or "—"
        row = []
        if show_art_id:
            row.append(Text(dr.core.art_id or "-", style="dim"))
        row += [
            Text(label, style="bold"),
            Text(snippet, style="dim" if snippet == "—" else ""),
        ]
        t.add_row(*row)

    if not pm.items.doc_refs:
        if show_art_id:
            t.add_row(Text("-", style="dim"), Text("(none)", style="dim"), Text("—", style="dim"))
        else:
            t.add_row(Text("(none)", style="dim"), Text("—", style="dim"))

    return t


def _src_code_refs_table(pm: SmartFindPM, *, ctx: RenderCtx) -> Table:
    b = box.MINIMAL if ctx.compact else box.SIMPLE
    show_art_id = _want_art_id(ctx)

    cols: list[Column] = []
    if show_art_id:
        cols.append(Column("art_id", style="dim", no_wrap=True, width=12))
    cols += [
        Column("file", no_wrap=False, width=44, overflow="ellipsis"),
        Column("lang", no_wrap=True, width=10),
        Column("iml", no_wrap=True, width=7),
        Column("snippet", no_wrap=False, width=77, overflow="ellipsis"),
    ]

    t = Table(
        *cols,
        show_header=True,
        header_style="bold",
        box=b,
        show_edge=False,
        pad_edge=False,
        expand=False,
    )

    for sr in pm.items.src_code_refs:
        file_label = sr.file_path or "SrcCodeRef"
        lang = sr.language or "—"
        has_iml = "✓" if (sr.iml_code or "").strip() else "—"
        snippet = _snip(sr.src_code, 220) or _snip(sr.iml_code, 220) or "—"

        row = []
        if show_art_id:
            row.append(Text(sr.core.art_id or "-", style="dim"))
        row += [
            Text(file_label, style="bold"),
            Text(lang, style="cyan" if lang != "—" else "dim"),
            Text(has_iml, style="green" if has_iml == "✓" else "dim"),
            Text(snippet, style="dim" if snippet == "—" else ""),
        ]
        t.add_row(*row)

    if not pm.items.src_code_refs:
        if show_art_id:
            t.add_row(Text("-", style="dim"), Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("—", style="dim"))
        else:
            t.add_row(Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("—", style="dim"))

    return t

def _maybe_section(title: str, n: int, body: object, *, ctx: RenderCtx) -> list[object]:
    # Default: do not render empty sections
    show_empty = bool(getattr(ctx, "show_empty_sections", False))
    if n == 0 and not show_empty:
        return []
    return [Text(""), _rule(title, n), body]

# -----------------------------------------------------------------------------
# renderer
# -----------------------------------------------------------------------------

def render_smart_find(pm: SmartFindPM, *, ctx: RenderCtx):
    needle = (pm.counts.needle or "").strip().replace("\n", " ")
    header = Text.assemble(
        ("Smart find", "bold"),
        ("  ", ""),
        ("needle: ", "dim"),
        (repr(needle) if needle else "(empty)", "cyan" if needle else "dim"),
    )

    blocks: list[object] = [
        header,
        Text(""),
        _counts_table(pm, ctx=ctx),
    ]

    blocks += _maybe_section("Test traces", len(pm.items.test_traces), _test_traces_table(pm, ctx=ctx), ctx=ctx)
    blocks += _maybe_section("Log traces", len(pm.items.log_traces), _log_traces_table(pm, ctx=ctx), ctx=ctx)
    blocks += _maybe_section("Doc refs", len(pm.items.doc_refs), _doc_refs_table(pm, ctx=ctx), ctx=ctx)
    blocks += _maybe_section("Src code refs", len(pm.items.src_code_refs), _src_code_refs_table(pm, ctx=ctx), ctx=ctx)

    return Panel(Group(*blocks), title="[bold]Smart find[/bold]", border_style="magenta")