"""
tmux session enumeration and snapshot helpers for the tmux-web muxplex.

In-memory cache:
    _session_list  — most-recently-enumerated list of session names.
    _snapshots     — most-recently-captured pane text, keyed by session name.
    _session_activity — most-recently-captured session activity timestamps.

Public API:
    get_session_list()                              → list[str]
    get_snapshots()                                 → dict[str, str]
    get_session_activity()                          → dict[str, float]
    update_session_cache(names, snapshots, activity) → None
    run_tmux(*args)                                 → str   (raises RuntimeError on nonzero exit)
    enumerate_sessions()                            → tuple[list[str], dict[str, float]]
    capture_pane(name, lines)                       → str
    snapshot_all(names)                             → dict[str, str]
"""

import asyncio

# ---------------------------------------------------------------------------
# In-memory cache
# ---------------------------------------------------------------------------

_session_list: list[str] = []
_snapshots: dict[str, str] = {}
_session_activity: dict[str, float] = {}


def get_session_list() -> list[str]:
    """Return a copy of the cached session name list."""
    return list(_session_list)


def get_snapshots() -> dict[str, str]:
    """Return a copy of the cached pane-snapshot dict."""
    return dict(_snapshots)


def get_session_activity() -> dict[str, float]:
    """Return a copy of the cached session activity timestamp dict."""
    return dict(_session_activity)


def update_session_cache(
    names: list[str],
    snapshots: dict[str, str],
    activity: dict[str, float] | None = None,
) -> None:
    """Replace the in-memory caches with fresh data.

    Sets _session_list to *names* and _snapshots to the provided *snapshots* dict.
    Callers must pass the return value of snapshot_all() as *snapshots*.
    Optionally accepts *activity* — a mapping of session name to Unix timestamp
    of last activity (from ``#{session_activity}``).
    """
    global _session_list, _snapshots, _session_activity
    _session_list = list(names)
    _snapshots = snapshots
    if activity is not None:
        _session_activity = activity


# ---------------------------------------------------------------------------
# Subprocess helpers
# ---------------------------------------------------------------------------


async def run_tmux(*args: str) -> str:
    """Run `tmux <args>` in a subprocess and return stdout as a string.

    Raises:
        RuntimeError: If the process exits with a nonzero return code.
                      The error message contains the decoded stderr output.
    """
    proc = await asyncio.create_subprocess_exec(
        "tmux",
        *args,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    stdout_bytes, stderr_bytes = await proc.communicate()
    if proc.returncode != 0:
        raise RuntimeError(stderr_bytes.decode("utf-8", errors="replace"))
    return stdout_bytes.decode("utf-8", errors="replace")


# ---------------------------------------------------------------------------
# Session enumeration
# ---------------------------------------------------------------------------


async def enumerate_sessions() -> tuple[list[str], dict[str, float]]:
    """Return the list of currently running tmux session names and their activity timestamps.

    Calls ``tmux list-sessions -F '#{session_name}\\t#{session_activity}'``,
    splits on newlines, and parses each line into a name and a Unix timestamp.

    Returns ``([], {})`` if tmux is not running (RuntimeError from run_tmux).
    """
    try:
        output = await run_tmux(
            "list-sessions", "-F", "#{session_name}\t#{session_activity}"
        )
    except (RuntimeError, FileNotFoundError):
        return [], {}

    names: list[str] = []
    activity: dict[str, float] = {}
    for line in output.splitlines():
        line = line.strip()
        if not line:
            continue
        parts = line.split("\t", 1)
        name = parts[0].strip()
        if not name:
            continue
        names.append(name)
        if len(parts) > 1:
            try:
                activity[name] = float(parts[1].strip())
            except (ValueError, TypeError):
                pass
    return names, activity


# ---------------------------------------------------------------------------
# Pane capture
# ---------------------------------------------------------------------------


async def capture_pane(session_name: str, lines: int = 30) -> str:
    """Capture the last *lines* lines of output from *session_name*.

    Returns the captured text, or '' on any error.
    """
    try:
        return await run_tmux(
            "capture-pane",
            "-e",  # preserve ANSI escape sequences for color rendering
            "-p",
            "-t",
            session_name,
            "-S",
            f"-{lines}",
        )
    except RuntimeError:
        return ""


async def snapshot_all(names: list[str]) -> dict[str, str]:
    """Capture all sessions concurrently and return a name→text mapping.

    Uses asyncio.gather with return_exceptions=True so that individual
    failures do not abort the whole batch.  Failed sessions map to ''.

    Note: this function does not mutate module state — it does not update the module cache.
    Callers are responsible for passing the result to update_session_cache.
    """
    if not names:
        return {}
    results = await asyncio.gather(
        *[capture_pane(name) for name in names],
        return_exceptions=True,
    )
    snapshots: dict[str, str] = {}
    for name, result in zip(names, results):
        if isinstance(result, BaseException):
            snapshots[name] = ""
        else:
            snapshots[name] = result
    return snapshots
