"""Example plugin that uses the new `ResultTable` API.

This module demonstrates a minimal plugin adapter that yields `ResultModel`
instances, a set of `ColumnSpec` definitions, and a tiny CLI-friendly renderer
(`render_table`) for demonstration.

Run this to see sample output:
    python -m plugins.example_plugin

Example usage (piped selector):
    plugin-table -plugin example -sample | select -select 1 | add-file -instance default
"""
from __future__ import annotations

from pathlib import Path
from typing import Any, Dict, Iterable, List

from SYS.result_table_api import ColumnSpec, ResultModel, title_column, ext_column


SAMPLE_ITEMS = [
    {
        "name": "Book of Awe.pdf",
        "path": "sample/Book of Awe.pdf",
        "ext": "pdf",
        "size": 1024000,
        "source": "example",
    },
    {
        "name": "Song of Joy.mp3",
        "path": "sample/Song of Joy.mp3",
        "ext": "mp3",
        "size": 5120000,
        "source": "example",
    },
    {
        "name": "Cover Image.jpg",
        "path": "sample/Cover Image.jpg",
        "ext": "jpg",
        "size": 20480,
        "source": "example",
    },
]


def adapter(items: Iterable[Dict[str, Any]]) -> Iterable[ResultModel]:
    """Convert plugin-specific items into `ResultModel` instances.

    This adapter enforces the strict API requirement: it yields only
    `ResultModel` instances (no legacy dict objects).
    """
    for it in items:
        title = it.get("name") or it.get("title") or (Path(str(it.get("path"))).stem if it.get("path") else "")
        yield ResultModel(
            title=str(title),
            path=str(it.get("path")) if it.get("path") else None,
            ext=str(it.get("ext")) if it.get("ext") else None,
            size_bytes=int(it.get("size")) if it.get("size") is not None else None,
            metadata=dict(it),
            source=str(it.get("source")) if it.get("source") else "example",
        )


# Columns are intentionally *not* mandated. Create a factory that inspects
# sample rows and builds only columns that make sense for the plugin data.
from SYS.result_table_api import metadata_column


def columns_factory(rows: List[ResultModel]) -> List[ColumnSpec]:
    cols: List[ColumnSpec] = [title_column()]

    # If any row has an extension, include Ext column
    if any(getattr(r, "ext", None) for r in rows):
        cols.append(ext_column())

    # If any row has size, include Size column
    if any(getattr(r, "size_bytes", None) for r in rows):
        cols.append(ColumnSpec("size", "Size", lambda rr: rr.size_bytes or "", lambda v: _format_size(v)))

    # Add any top-level metadata keys discovered (up to 3) as optional columns
    seen_keys = []
    for r in rows:
        for k in (r.metadata or {}).keys():
            if k in ("name", "title", "path"):
                continue
            if k not in seen_keys:
                seen_keys.append(k)
            if len(seen_keys) >= 3:
                break
        if len(seen_keys) >= 3:
            break

    for k in seen_keys:
        cols.append(metadata_column(k))

    return cols


# Selection function: cmdlets rely on this to build selector args when the user
# selects a row (e.g., '@3' -> run next-cmd with the returned args). Prefer
# -path if available, otherwise fall back to -title.
def selection_fn(row: ResultModel) -> List[str]:
    if row.path:
        return ["-path", row.path]
    return ["-title", row.title]


# Register the plugin with the registry so callers can discover it by name
from SYS.result_table_adapters import register_plugin
register_plugin(
    "example",
    adapter,
    columns=columns_factory,
    selection_fn=selection_fn,
    metadata={"description": "Example plugin demonstrating dynamic columns and selectors"},
)


def _format_size(size: Any) -> str:
    try:
        s = int(size)
    except Exception:
        return ""
    if s >= 1024 ** 3:
        return f"{s / (1024 ** 3):.2f} GB"
    if s >= 1024 ** 2:
        return f"{s / (1024 ** 2):.2f} MB"
    if s >= 1024:
        return f"{s / 1024:.2f} KB"
    return f"{s} B"


def render_table(rows: Iterable[ResultModel], columns: List[ColumnSpec]) -> str:
    """Render a simple ASCII table of `rows` using `columns`.

    This is intentionally very small and dependency-free for demonstration.
    Renderers in the project should implement the `Renderer` protocol.
    """
    rows = list(rows)

    # Build cell matrix (strings)
    matrix: List[List[str]] = []
    for r in rows:
        cells: List[str] = []
        for col in columns:
            raw = col.extractor(r)
            if col.format_fn:
                try:
                    cell = col.format_fn(raw)
                except Exception:
                    cell = str(raw or "")
            else:
                cell = str(raw or "")
            cells.append(cell)
        matrix.append(cells)

    # Compute column widths as max(header, content)
    headers = [c.header for c in columns]
    widths = [len(h) for h in headers]
    for row_cells in matrix:
        for i, cell in enumerate(row_cells):
            widths[i] = max(widths[i], len(cell))

    # Helper to format a row
    def fmt_row(cells: List[str]) -> str:
        return " | ".join(cell.ljust(widths[i]) for i, cell in enumerate(cells))

    lines: List[str] = []
    lines.append(fmt_row(headers))
    lines.append("-+-".join("-" * w for w in widths))
    for row_cells in matrix:
        lines.append(fmt_row(row_cells))

    return "\n".join(lines)


# Rich-based renderer (returns a Rich Table renderable)
def render_table_rich(rows: Iterable[ResultModel], columns: List[ColumnSpec]):
    """Render rows as a `rich.table.Table` for terminal output.

    Returns the Table object; callers may `Console.print(table)` to render.
    """
    try:
        from rich.table import Table as RichTable
    except Exception as exc:  # pragma: no cover - rare if rich missing
        raise RuntimeError("rich is required for rich renderer") from exc

    table = RichTable(show_header=True, header_style="bold")
    for col in columns:
        table.add_column(col.header)

    for r in rows:
        cells: List[str] = []
        for col in columns:
            raw = col.extractor(r)
            if col.format_fn:
                try:
                    cell = col.format_fn(raw)
                except Exception:
                    cell = str(raw or "")
            else:
                cell = str(raw or "")
            cells.append(cell)
        table.add_row(*cells)

    return table


def demo() -> None:
    rows = list(adapter(SAMPLE_ITEMS))
    table = render_table_rich(rows, columns_factory(rows))
    try:
        from rich.console import Console
    except Exception:
        # Fall back to plain printing if rich is not available
        print("Example provider output:")
        print(render_table(rows, columns_factory(rows)))
        return

    console = Console()
    console.print("Example provider output:")
    console.print(table)


def demo_with_selection(idx: int = 0) -> None:
    """Demonstrate how a cmdlet would use plugin registration and selection args.

    - Fetch the registered plugin by name
    - Build rows via adapter
    - Render the table
    - Show the selection args for the chosen row; these are the args a cmdlet
      would append when the user picks that row.
    """
    from SYS.result_table_adapters import get_plugin

    provider = get_plugin("example")
    rows = list(provider.adapter(SAMPLE_ITEMS))
    cols = provider.get_columns(rows)

    # Render
    try:
        from rich.console import Console
    except Exception:
        print(render_table(rows, cols))
        sel_args = provider.selection_args(rows[idx])
        print("Selection args for row", idx, "->", sel_args)
        return

    console = Console()
    console.print("Example provider output:")
    console.print(render_table_rich(rows, cols))

    # Selection args example
    sel = provider.selection_args(rows[idx])
    console.print("Selection args for row", idx, "->", sel)


if __name__ == "__main__":
    demo()
