from __future__ import annotations

import sys
from typing import Any, Dict, List, Sequence
from . import _shared as sh
from SYS.logger import log, debug
from SYS import pipeline as ctx

from SYS.result_table_api import ResultModel
from SYS.result_table_adapters import get_provider
from SYS.result_table_renderers import RichRenderer

Cmdlet = sh.Cmdlet
CmdletArg = sh.CmdletArg
parse_cmdlet_args = sh.parse_cmdlet_args
normalize_result_input = sh.normalize_result_input


CMDLET = Cmdlet(
    name="select",
    summary="Select items from a piped result set (interactive or via -select) and emit the selected item(s).",
    usage="select -select <n|1-3|1,3> [-multi] [-run-cmd <name>]",
    arg=[
        CmdletArg("select", type="string", description="Selection string (e.g., 1, 2-4)", alias="s"),
        CmdletArg("multi", type="flag", description="Allow multiple selections."),
        CmdletArg("interactive", type="flag", description="Prompt interactively for selection."),
        CmdletArg("run-cmd", type="string", description="Cmdlet to invoke with selected items (each)"),
    ],
    detail=[
        "Accepts piped input from provider-table or other sources and emits the selected item(s) as dicts.",
        "If -run-cmd is provided, invokes the named cmdlet for each selected item with selector args and the item as piped input.",
    ],
)


def _parse_selection(selection: str, max_len: int) -> List[int]:
    """Parse a selection string like '1', '1-3', '1,3,5-7' into 0-based indices."""
    if not selection:
        return []
    parts = [p.strip() for p in str(selection).split(",") if p.strip()]
    indices = set()
    for part in parts:
        if "-" in part:
            try:
                a, b = part.split("-", 1)
                start = int(a.strip())
                end = int(b.strip())
                if start > end:
                    start, end = end, start
                for i in range(start, end + 1):
                    if 1 <= i <= max_len:
                        indices.add(i - 1)
            except Exception:
                raise ValueError(f"Invalid range: {part}")
        else:
            try:
                v = int(part)
                if 1 <= v <= max_len:
                    indices.add(v - 1)
            except Exception:
                raise ValueError(f"Invalid selection: {part}")
    return sorted(indices)


def _dict_to_result_model(d: Dict[str, Any]) -> ResultModel:
    if isinstance(d, ResultModel):
        return d
    # Allow dicts or objects with attributes
    title = d.get("title") or d.get("name") or (d.get("path") and str(d.get("path")).split("/")[-1])
    return ResultModel(
        title=str(title) if title is not None else "",
        path=d.get("path") if d.get("path") is not None else None,
        ext=d.get("ext") if d.get("ext") is not None else None,
        size_bytes=d.get("size_bytes") if d.get("size_bytes") is not None else None,
        metadata=d.get("metadata") or {},
        source=d.get("source") or None,
    )


def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
    parsed = parse_cmdlet_args(args, CMDLET)

    select_raw = parsed.get("select")
    allow_multi = bool(parsed.get("multi", False))
    interactive = bool(parsed.get("interactive", False))
    run_cmd = parsed.get("run-cmd")

    inputs = normalize_result_input(result)
    if not inputs:
        log("No input provided to select; pipe provider-table output or use a cmdlet that emits items.", file=sys.stderr)
        return 1

    first_src = inputs[0].get("source") if isinstance(inputs[0], dict) else None
    if not first_src:
        log("Input items must include 'source' to resolve provider for selection.", file=sys.stderr)
        return 1

    try:
        provider = get_provider(first_src)
    except Exception:
        log(f"Unknown provider: {first_src}", file=sys.stderr)
        return 1

    # Model-ize items
    rows = [_dict_to_result_model(item if isinstance(item, dict) else item) for item in inputs]

    # Columns: provider must supply them (no legacy defaults)
    cols = provider.get_columns(rows)

    # Render table to console
    try:
        table = RichRenderer().render(rows, cols, None)
        try:
            from rich.console import Console

            Console().print(table)
        except Exception:
            for r in rows:
                print(" ".join(str((c.extractor(r) or "")) for c in cols))
    except Exception as exc:
        log(f"Rendering failed: {exc}", file=sys.stderr)
        return 1

    # Determine selection indices
    indices: List[int] = []
    if select_raw:
        try:
            indices = _parse_selection(str(select_raw), len(rows))
        except ValueError as exc:
            log(str(exc), file=sys.stderr)
            return 1
    elif interactive:
        # Prompt user (single index only unless multi)
        try:
            from rich.prompt import Prompt

            prompt_text = "Select item(s) (e.g., 1 or 1,3-5)"
            if not allow_multi:
                prompt_text += " (single)"
            choice = Prompt.ask(prompt_text).strip()
            indices = _parse_selection(choice, len(rows))
        except Exception as exc:
            log(f"Interactive selection failed: {exc}", file=sys.stderr)
            return 1
    else:
        log("No selection requested. Use -select or -interactive.", file=sys.stderr)
        return 1

    if not indices:
        log("No valid selection indices provided", file=sys.stderr)
        return 1

    # Build selected items and emit
    selected_items: List[Dict[str, Any]] = []
    for idx in indices:
        try:
            raw = inputs[idx] if idx < len(inputs) else None
            if isinstance(raw, dict):
                selected = dict(raw)
            elif isinstance(raw, ResultModel):
                selected = {
                    "title": raw.title,
                    "path": raw.path,
                    "ext": raw.ext,
                    "size_bytes": raw.size_bytes,
                    "metadata": raw.metadata or {},
                    "source": raw.source,
                }
            else:
                try:
                    selected = raw.to_dict()
                except Exception:
                    selected = {"title": getattr(raw, "title", str(raw))}

            # Ensure selection args exist using provider's selector only
            if not selected.get("_selection_args"):
                try:
                    sel_args = provider.selection_args(rows[idx])
                    selected["_selection_args"] = sel_args
                except Exception:
                    log("Selection args missing and provider selector failed.", file=sys.stderr)
                    return 1

            selected_items.append(selected)
        except Exception:
            continue

    # Emit selected items so downstream cmdlets can consume them
    try:
        for itm in selected_items:
            ctx.emit(itm)
    except Exception:
        pass

    # Optionally run follow-up cmdlet for each selected item
    if run_cmd:
        try:
            from cmdlet import ensure_cmdlet_modules_loaded, get as get_cmdlet

            ensure_cmdlet_modules_loaded()
            cmd_fn = get_cmdlet(run_cmd)
            if not cmd_fn:
                log(f"Follow-up cmdlet not found: {run_cmd}", file=sys.stderr)
                return 1

            exit_code = 0
            for itm in selected_items:
                sel_args = itm.get("_selection_args") or []
                # Invoke follow-up cmdlet with the selected item as piped input
                try:
                    ret = cmd_fn(itm, sel_args, config or {})
                except Exception as exc:
                    log(f"Follow-up cmdlet raised: {exc}", file=sys.stderr)
                    ret = 1
                if ret != 0:
                    exit_code = ret
            return exit_code
        except Exception as exc:
            log(f"Failed to invoke follow-up cmdlet: {exc}", file=sys.stderr)
            return 1

    return 0


CMDLET.exec = _run
CMDLET.register()
