"""tmux operations for Legionnaire using libtmux."""

from pathlib import Path

import libtmux
from libtmux.exc import LibTmuxException

# Color constants
PARENT_RED = "colour196"

CHILD_PALETTE = [
    "colour27",
    "colour33",
    "colour39",
    "colour45",
    "colour51",
    "colour50",
    "colour49",
    "colour48",
    "colour47",
    "colour46",
    "colour82",
    "colour118",
]

# Session management marker
LEGION_MANAGED_OPTION = "@legion_managed"
LEGION_WSTYLE_OPTION = "@legion_wstyle"
LEGION_PREV_FORMAT = "@legion_prev_window_status_format"
LEGION_PREV_CURRENT_FORMAT = "@legion_prev_window_status_current_format"


class TmuxError(Exception):
    """Raised when a tmux operation fails."""

    pass


class SessionNotManagedError(TmuxError):
    """Raised when trying to use an unmanaged session."""

    pass


def get_server() -> libtmux.Server:
    """Get the tmux server instance.

    Returns:
        The libtmux Server object.
    """
    return libtmux.Server()


def session_exists(session_name: str) -> bool:
    """Check if a tmux session exists.

    Args:
        session_name: The name of the session to check.

    Returns:
        True if the session exists, False otherwise.
    """
    server = get_server()
    return server.has_session(session_name)


def is_session_managed(session: libtmux.Session) -> bool:
    """Check if a session is managed by Legionnaire.

    Args:
        session: The session to check.

    Returns:
        True if the session has @legion_managed=1, False otherwise.
    """
    try:
        value = session.show_option(LEGION_MANAGED_OPTION)
        # libtmux returns int for numeric values
        return value == 1 or value == "1"
    except LibTmuxException:
        return False


def ensure_managed_session(parent_id: str, parent_cwd: Path) -> libtmux.Session:
    """Ensure a managed tmux session exists for a parent.

    Creates the session if it doesn't exist. Fails if the session exists
    but is not managed by Legionnaire.

    Args:
        parent_id: The parent ID (used as session name).
        parent_cwd: The working directory for the session.

    Returns:
        The libtmux Session object.

    Raises:
        SessionNotManagedError: If the session exists but is not managed.
    """
    server = get_server()

    if server.has_session(parent_id):
        session = server.sessions.get(session_name=parent_id)
        if not is_session_managed(session):
            raise SessionNotManagedError(
                f"Session '{parent_id}' exists but is not managed by Legionnaire. "
                f"Either rename the existing session or use a different parent ID."
            )
        return session

    # Create new session
    session = server.new_session(
        session_name=parent_id,
        start_directory=str(parent_cwd),
        attach=False,
    )

    # Mark as managed and configure
    session.set_option(LEGION_MANAGED_OPTION, "1")
    session.set_option("allow-rename", "off")
    session.set_option("automatic-rename", "off")

    # Save previous window status formats before modifying
    try:
        prev_format = session.show_option("window-status-format")
        session.set_option(LEGION_PREV_FORMAT, prev_format)
    except LibTmuxException:
        pass

    try:
        prev_current = session.show_option("window-status-current-format")
        session.set_option(LEGION_PREV_CURRENT_FORMAT, prev_current)
    except LibTmuxException:
        pass

    # Set custom window status formats that use @legion_wstyle
    session.set_option("window-status-format", "#{" + LEGION_WSTYLE_OPTION + "}#W#[default]")
    session.set_option(
        "window-status-current-format", "#[bold]#{" + LEGION_WSTYLE_OPTION + "}#W#[default]"
    )

    return session


def ensure_parent_window(session: libtmux.Session, parent_cwd: Path) -> libtmux.Window:
    """Ensure the parent window exists at index 0.

    Args:
        session: The tmux session.
        parent_cwd: The working directory for the parent window.

    Returns:
        The parent window.
    """
    # Check if window 0 exists and is named "parent"
    windows = session.windows
    if windows:
        first_window = windows[0]
        if first_window.name != "parent":
            first_window.rename_window("parent")
        # Set the color style
        first_window.set_option(LEGION_WSTYLE_OPTION, f"#[fg={PARENT_RED}]")
        return first_window

    # Create parent window (shouldn't normally happen since session creation creates one)
    window = session.new_window(
        window_name="parent",
        start_directory=str(parent_cwd),
        attach=False,
    )
    window.set_option(LEGION_WSTYLE_OPTION, f"#[fg={PARENT_RED}]")
    return window


def ensure_child_window(
    session: libtmux.Session,
    child_id: str,
    child_cwd: Path,
) -> libtmux.Window:
    """Ensure a child window exists in the session.

    Args:
        session: The tmux session.
        child_id: The child ID (used as window name).
        child_cwd: The working directory for the child window.

    Returns:
        The child window.
    """
    # Check if window already exists
    for window in session.windows:
        if window.name == child_id:
            return window

    # Create new window
    window = session.new_window(
        window_name=child_id,
        start_directory=str(child_cwd),
        attach=False,
    )
    return window


def list_child_windows(session: libtmux.Session) -> set[str]:
    """List all child window names in a session.

    Args:
        session: The tmux session.

    Returns:
        A set of window names, excluding 'parent'.
    """
    return {w.name for w in session.windows if w.name != "parent"}


def kill_window(session: libtmux.Session, window_name: str) -> bool:
    """Kill a window by name.

    Args:
        session: The tmux session.
        window_name: The name of the window to kill.

    Returns:
        True if the window was killed, False if it didn't exist.
    """
    for window in session.windows:
        if window.name == window_name:
            window.kill()
            return True
    return False


def kill_session(session: libtmux.Session, restore_formats: bool = True) -> None:
    """Kill a managed session.

    Args:
        session: The session to kill.
        restore_formats: If True, restore original window status formats first.
    """
    # Note: format restoration is a best-effort since we're killing the session anyway
    session.kill()


def _get_window_by_name(session: libtmux.Session, name: str) -> libtmux.Window | None:
    """Find a window by name, refreshing from server."""
    session.refresh()
    for w in session.windows:
        if w.name == name:
            return w
    return None


def reorder_windows(session: libtmux.Session, sorted_child_ids: list[str]) -> None:
    """Reorder child windows to match sorted order.

    Ensures parent is at index 0, and children are at indices 1..N
    in the order specified by sorted_child_ids.

    Args:
        session: The tmux session.
        sorted_child_ids: Child IDs in desired order.
    """
    server = session.server

    # Get current windows and find a temporary index range
    session.refresh()
    windows = session.windows
    if not windows:
        return

    existing_indices = {int(w.index) for w in windows}
    temp_base = max(existing_indices) + 100

    # First pass: move all windows to temporary indices to avoid conflicts
    window_names = [w.name for w in windows]
    temp_idx = temp_base
    sess_name = session.name
    for name in window_names:
        window = _get_window_by_name(session, name)
        if window:
            src = f"{sess_name}:{window.index}"
            dst = f"{sess_name}:{temp_idx}"
            server.cmd("move-window", "-s", src, "-t", dst)
        temp_idx += 1

    # Second pass: move to final positions
    # Parent goes to index 0
    parent_window = _get_window_by_name(session, "parent")
    if parent_window:
        src = f"{sess_name}:{parent_window.index}"
        server.cmd("move-window", "-s", src, "-t", f"{sess_name}:0")

    # Children go to indices 1..N in sorted order
    idx = 1
    for child_id in sorted_child_ids:
        child_window = _get_window_by_name(session, child_id)
        if child_window:
            src = f"{sess_name}:{child_window.index}"
            dst = f"{sess_name}:{idx}"
            server.cmd("move-window", "-s", src, "-t", dst)
            idx += 1


def apply_colors(session: libtmux.Session, sorted_child_ids: list[str]) -> None:
    """Apply deterministic colors to all windows.

    Parent gets PARENT_RED, children get colors from CHILD_PALETTE
    based on their position in sorted_child_ids.

    Args:
        session: The tmux session.
        sorted_child_ids: Child IDs in sorted order.
    """
    for window in session.windows:
        if window.name == "parent":
            window.set_option(LEGION_WSTYLE_OPTION, f"#[fg={PARENT_RED}]")
        elif window.name in sorted_child_ids:
            idx = sorted_child_ids.index(window.name)
            color = CHILD_PALETTE[idx % len(CHILD_PALETTE)]
            window.set_option(LEGION_WSTYLE_OPTION, f"#[fg={color}]")


def send_keys_to_window(window: libtmux.Window, keys: str) -> None:
    """Send keys to the first pane of a window.

    Args:
        window: The tmux window.
        keys: The keys/command to send.
    """
    pane = window.panes[0]
    pane.send_keys(keys)


def get_session(parent_id: str) -> libtmux.Session | None:
    """Get a session by name if it exists.

    Args:
        parent_id: The parent ID (session name).

    Returns:
        The session, or None if it doesn't exist.
    """
    server = get_server()
    if not server.has_session(parent_id):
        return None
    return server.sessions.get(session_name=parent_id)
