from __future__ import annotations

import fcntl
import json
import os
import re
import shutil
import subprocess
import sys
from contextlib import contextmanager
from datetime import datetime, timezone
from typing import Iterator

from dxcli_metadata import current_release_metadata
from dxcli_runtime_config import managed_remote_env

INTERNAL_RUN_COMMAND = "__dx_session_run__"
MANAGED_GLOBAL_ID_ENV = "DXCLI_MANAGED_GLOBAL_ID"
MANAGED_LAUNCH_MODE_ENV = "DXCLI_MANAGED_LAUNCH_MODE"
MANAGEMENT_COMMANDS = {"ls", "list", "lsa", "a", "aa", "s", "sa", "sd", "sda"}
DEFAULT_STATE_DIR = os.path.expanduser(os.getenv("DXCLI_STATE_DIR", "~/.dxcli-portable"))
GLOBAL_ID_START = 1001
GLOBAL_ID_LIMIT = 9999
SESSION_VERSION = 3
TMUX_ENV_PASSTHROUGH_PREFIXES = ("DXCLI_", "CODEX_", "OPENAI_", "ANTHROPIC_")
TMUX_PROXY_ENV_NAMES = (
    "http_proxy",
    "https_proxy",
    "all_proxy",
    "no_proxy",
    "HTTP_PROXY",
    "HTTPS_PROXY",
    "ALL_PROXY",
    "NO_PROXY",
)
MANAGED_CODEX_FEATURES = ("hooks",)
LAUNCH_MODE_CODEX = "codex"
LAUNCH_MODE_SHELL = "shell"
DEFAULT_CODEX_MANAGED_ARGS: list[str] = []
MODEL_SELECTOR_MODELS = {
    "3": "gpt-5.3-codex-spark",
    "4": "gpt-5.4",
    "5": "gpt-5.5",
}
MODEL_SELECTOR_REASONING_EFFORTS = {
    "1": "low",
    "2": "medium",
    "3": "high",
    "4": "xhigh",
}
TMUX_HISTORY_LIMIT = 200000
TMUX_WHEEL_SCROLL_SPEED = 5
TMUX_UTF8_LOCALE = "C.UTF-8"
OUTER_ALT_SCROLL_DISABLE_SEQUENCE = "\033[?1007l"
TMUX_WHEEL_SCROLL_UP_KEYS = ("WheelUpPane",)
TMUX_WHEEL_SCROLL_DOWN_KEYS = ("WheelDownPane",)
TMUX_COPY_MODE_SCROLL_UP_KEYS = (
    "WheelUpPane",
    "Up",
    "S-Up",
    "C-Up",
    "M-Up",
    "PPage",
    "PageUp",
)
TMUX_COPY_MODE_SCROLL_DOWN_KEYS = (
    "WheelDownPane",
    "Down",
    "S-Down",
    "C-Down",
    "M-Down",
    "NPage",
    "PageDown",
)
TMUX_SCROLL_PASSTHROUGH_KEYS = (
    "Up",
    "S-Up",
    "C-Up",
    "M-Up",
    "PPage",
    "PageUp",
    "Down",
    "S-Down",
    "C-Down",
    "M-Down",
    "NPage",
    "PageDown",
)
TMUX_SCROLL_STATUS_KEYS = ("WheelUpStatus", "WheelDownStatus")


def normalize_workdir(path: str | None = None) -> str:
    target = os.getcwd() if path is None else path
    return os.path.realpath(os.path.abspath(target))


def utc_now() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def validate_local_id(value: str) -> str:
    local_id = str(value).strip()
    if not re.fullmatch(r"[1-9]\d*", local_id):
        raise ValueError("本地 ID 必须是正整数。")
    return local_id


def validate_global_id(value: str) -> str:
    global_id = str(value).strip()
    if not re.fullmatch(r"\d{4}", global_id):
        raise ValueError("全局 ID 必须是 4 位数字。")
    return global_id


def record_session_name(record: dict[str, object]) -> str:
    session_name = str(record.get("session_name") or record.get("screen_name") or "").strip()
    if session_name:
        return session_name
    global_id = str(record.get("global_id", "")).strip()
    return f"dxg{global_id}" if global_id else ""


def validate_launch_mode(value: str | None) -> str:
    launch_mode = str(value or LAUNCH_MODE_CODEX).strip().lower()
    if launch_mode not in {LAUNCH_MODE_CODEX, LAUNCH_MODE_SHELL}:
        raise ValueError("启动模式只能是 codex 或 shell。")
    return launch_mode


def _sort_key(record: dict[str, object]) -> tuple[str, int, int]:
    return (
        str(record.get("workdir", "")),
        int(str(record.get("local_id", "0"))),
        int(str(record.get("global_id", "0"))),
    )


class SessionStore:
    def __init__(self, state_dir: str | None = None) -> None:
        self.state_dir = os.path.realpath(os.path.abspath(state_dir or DEFAULT_STATE_DIR))
        self.sessions_path = os.path.join(self.state_dir, "sessions.json")
        self.lock_path = os.path.join(self.state_dir, "sessions.lock")

    def list_sessions(self, workdir: str | None = None) -> list[dict[str, object]]:
        normalized = normalize_workdir(workdir) if workdir is not None else None
        with self._locked_data(write_back=False) as data:
            sessions = list(data.get("sessions", []))
        if normalized is not None:
            sessions = [record for record in sessions if record.get("workdir") == normalized]
        return sorted(sessions, key=_sort_key)

    def get_by_local_id(self, workdir: str, local_id: str) -> dict[str, object] | None:
        normalized_workdir = normalize_workdir(workdir)
        normalized_local_id = validate_local_id(local_id)
        with self._locked_data(write_back=False) as data:
            for record in data.get("sessions", []):
                if record.get("workdir") == normalized_workdir and record.get("local_id") == normalized_local_id:
                    return dict(record)
        return None

    def get_by_global_id(self, global_id: str) -> dict[str, object] | None:
        normalized_global_id = validate_global_id(global_id)
        with self._locked_data(write_back=False) as data:
            for record in data.get("sessions", []):
                if record.get("global_id") == normalized_global_id:
                    return dict(record)
        return None

    def create_session(
        self,
        *,
        workdir: str,
        local_id: str | None,
        global_id: str | None,
        codex_args: list[str] | None = None,
        launch_mode: str = LAUNCH_MODE_CODEX,
    ) -> dict[str, object]:
        normalized_workdir = normalize_workdir(workdir)
        payload_args = [str(arg) for arg in list(codex_args or [])]
        normalized_launch_mode = validate_launch_mode(launch_mode)

        with self._locked_data(write_back=True) as data:
            sessions = data.setdefault("sessions", [])
            existing_globals = {str(record["global_id"]) for record in sessions}
            existing_locals = {
                str(record["local_id"])
                for record in sessions
                if record.get("workdir") == normalized_workdir
            }

            chosen_local_id = (
                self._next_local_id(existing_locals)
                if local_id is None
                else validate_local_id(local_id)
            )
            if chosen_local_id in existing_locals:
                raise ValueError(f"当前目录下的本地 ID {chosen_local_id} 已存在。")

            chosen_global_id = (
                self._next_global_id(data, existing_globals)
                if global_id is None
                else validate_global_id(global_id)
            )
            if chosen_global_id in existing_globals:
                raise ValueError(f"全局 ID {chosen_global_id} 已存在。")

            timestamp = utc_now()
            record = {
                "global_id": chosen_global_id,
                "local_id": chosen_local_id,
                "workdir": normalized_workdir,
                "session_name": f"dxg{chosen_global_id}",
                "created_at": timestamp,
                "last_active_at": timestamp,
                "codex_args": payload_args,
                "launch_mode": normalized_launch_mode,
            }
            sessions.append(record)
            sessions.sort(key=_sort_key)
            return dict(record)

    def touch(self, global_id: str) -> None:
        normalized_global_id = validate_global_id(global_id)
        with self._locked_data(write_back=True) as data:
            for record in data.get("sessions", []):
                if record.get("global_id") == normalized_global_id:
                    record["last_active_at"] = utc_now()
                    return
        raise ValueError(f"找不到全局 ID {normalized_global_id}。")

    def remove(self, global_id: str) -> None:
        normalized_global_id = validate_global_id(global_id)
        with self._locked_data(write_back=True) as data:
            sessions = data.get("sessions", [])
            filtered = [record for record in sessions if record.get("global_id") != normalized_global_id]
            if len(filtered) == len(sessions):
                raise ValueError(f"找不到全局 ID {normalized_global_id}。")
            data["sessions"] = filtered

    def prune_stopped_sessions(self, statuses: dict[str, str] | None = None) -> list[dict[str, object]]:
        active_statuses = query_tmux_statuses() if statuses is None else statuses
        if active_statuses is None:
            return []
        with self._locked_data(write_back=True) as data:
            return self._prune_stopped_sessions_unlocked(data, active_statuses)

    def _next_local_id(self, existing_locals: set[str]) -> str:
        candidate = 1
        while str(candidate) in existing_locals:
            candidate += 1
        return str(candidate)

    def _next_global_id(self, data: dict[str, object], existing_globals: set[str]) -> str:
        for candidate in range(GLOBAL_ID_START, GLOBAL_ID_LIMIT + 1):
            global_id = f"{candidate:04d}"
            if global_id not in existing_globals:
                data["next_global_id"] = candidate + 1
                return global_id
        raise ValueError("没有可用的 4 位全局 ID 了。")

    def _prune_stopped_sessions_unlocked(
        self,
        data: dict[str, object],
        statuses: dict[str, str],
    ) -> list[dict[str, object]]:
        sessions = list(data.get("sessions", []))
        active_session_names = set(statuses)
        kept: list[dict[str, object]] = []
        removed: list[dict[str, object]] = []
        for record in sessions:
            if record_session_name(record) in active_session_names:
                kept.append(record)
            else:
                removed.append(dict(record))
        if removed:
            data["sessions"] = kept
        return removed

    @contextmanager
    def _locked_data(self, *, write_back: bool) -> Iterator[dict[str, object]]:
        os.makedirs(self.state_dir, exist_ok=True)
        with open(self.lock_path, "a+", encoding="utf-8") as lock_file:
            fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
            data = self._read_unlocked()
            try:
                yield data
            finally:
                if write_back:
                    self._write_unlocked(data)
                fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)

    def _normalize_record(self, record: object) -> dict[str, object] | None:
        if not isinstance(record, dict):
            return None
        try:
            global_id = validate_global_id(str(record.get("global_id", "")))
            local_id = validate_local_id(str(record.get("local_id", "")))
        except ValueError:
            return None
        workdir = str(record.get("workdir", "")).strip()
        if not workdir:
            return None
        session_name = record_session_name(record)
        if not session_name:
            return None
        raw_args = record.get("codex_args", [])
        codex_args = [str(arg) for arg in raw_args] if isinstance(raw_args, list) else []
        launch_mode = validate_launch_mode(record.get("launch_mode"))
        created_at = str(record.get("created_at") or "")
        last_active_at = str(record.get("last_active_at") or created_at)
        return {
            "global_id": global_id,
            "local_id": local_id,
            "workdir": normalize_workdir(workdir),
            "session_name": session_name,
            "created_at": created_at,
            "last_active_at": last_active_at,
            "codex_args": codex_args,
            "launch_mode": launch_mode,
        }

    def _read_unlocked(self) -> dict[str, object]:
        if not os.path.exists(self.sessions_path):
            return self._default_data()
        with open(self.sessions_path, "r", encoding="utf-8") as handle:
            try:
                data = json.load(handle)
            except json.JSONDecodeError:
                return self._default_data()
        if not isinstance(data, dict):
            return self._default_data()

        sessions = data.get("sessions")
        if not isinstance(sessions, list):
            normalized_sessions: list[dict[str, object]] = []
        else:
            normalized_sessions = []
            for record in sessions:
                normalized = self._normalize_record(record)
                if normalized is not None:
                    normalized_sessions.append(normalized)

        next_global_id = data.get("next_global_id", GLOBAL_ID_START)
        version = data.get("version", SESSION_VERSION)
        return {
            "version": version,
            "next_global_id": next_global_id,
            "sessions": sorted(normalized_sessions, key=_sort_key),
        }

    def _write_unlocked(self, data: dict[str, object]) -> None:
        payload = dict(data)
        payload["version"] = SESSION_VERSION
        payload.setdefault("next_global_id", GLOBAL_ID_START)
        payload["sessions"] = sorted(payload.get("sessions", []), key=_sort_key)
        temp_path = f"{self.sessions_path}.tmp"
        with open(temp_path, "w", encoding="utf-8") as handle:
            json.dump(payload, handle, indent=2, ensure_ascii=False)
            handle.write("\n")
        os.replace(temp_path, self.sessions_path)

    def _default_data(self) -> dict[str, object]:
        return {
            "version": SESSION_VERSION,
            "next_global_id": GLOBAL_ID_START,
            "sessions": [],
        }


def ensure_tmux_available() -> str:
    tmux_bin = shutil.which("tmux")
    if not tmux_bin:
        raise RuntimeError("找不到 tmux，请先安装 tmux。")
    return tmux_bin


def _truthy_env(name: str, default: bool) -> bool:
    raw = os.getenv(name)
    if raw is None:
        return default
    return raw.strip().lower() not in {"0", "false", "no", "off", ""}


def prepare_managed_codex_args(codex_args: list[str]) -> list[str]:
    prepared = list(codex_args) if codex_args else list(DEFAULT_CODEX_MANAGED_ARGS)
    if _truthy_env("DXCLI_MANAGED_CODEX_HOOKS", True):
        for feature_name in reversed(MANAGED_CODEX_FEATURES):
            prepared = ["--enable", feature_name] + prepared
    if _truthy_env("DXCLI_MANAGED_NO_ALT_SCREEN", False) and "--no-alt-screen" not in prepared:
        prepared = ["--no-alt-screen"] + prepared
    return prepared


def expand_model_selector_args(codex_args: list[str]) -> list[str]:
    if not codex_args:
        return []
    selector = str(codex_args[-1]).strip()
    if not re.fullmatch(r"[345][1-4]?", selector):
        return list(codex_args)
    model = MODEL_SELECTOR_MODELS[selector[0]]
    expanded = list(codex_args[:-1]) + ["-m", model]
    if len(selector) == 2:
        effort = MODEL_SELECTOR_REASONING_EFFORTS[selector[1]]
        expanded += ["-c", f'model_reasoning_effort="{effort}"']
    return expanded


def managed_tmux_mouse_enabled() -> bool:
    return _truthy_env("DXCLI_TMUX_MOUSE", False)


def attach_detach_others_default() -> bool:
    return _truthy_env("DXCLI_ATTACH_DETACH_OTHERS", True)


def managed_tmux_environment(global_id: str, *, launch_mode: str = LAUNCH_MODE_CODEX) -> dict[str, str]:
    env = managed_remote_env()
    for key, value in os.environ.items():
        if key in {MANAGED_GLOBAL_ID_ENV, "DXCLI_INVOKED_NAME"}:
            continue
        if any(key.startswith(prefix) for prefix in TMUX_ENV_PASSTHROUGH_PREFIXES):
            env.setdefault(key, value)
        if key in TMUX_PROXY_ENV_NAMES:
            env.setdefault(key, value)
    env[MANAGED_GLOBAL_ID_ENV] = validate_global_id(global_id)
    env[MANAGED_LAUNCH_MODE_ENV] = validate_launch_mode(launch_mode)
    env.setdefault("LANG", TMUX_UTF8_LOCALE)
    env.setdefault("LC_ALL", TMUX_UTF8_LOCALE)
    env.setdefault("COLORTERM", "truecolor")
    env.setdefault("TERM_PROGRAM", "dxcli")
    return env


def apply_client_terminal_profile() -> None:
    if not sys.stdout.isatty():
        return
    sys.stdout.write(OUTER_ALT_SCROLL_DISABLE_SEQUENCE)
    sys.stdout.flush()


def detect_tmux_default_terminal() -> str:
    infocmp_bin = shutil.which("infocmp")
    if infocmp_bin:
        for candidate in ("tmux-256color", "screen-256color"):
            completed = subprocess.run(
                [infocmp_bin, candidate],
                capture_output=True,
                text=True,
                check=False,
            )
            if completed.returncode == 0:
                return candidate
    return "screen-256color"


def apply_managed_tmux_profile(
    session_name: str,
    *,
    launch_mode: str = LAUNCH_MODE_CODEX,
    tmux_bin: str | None = None,
) -> None:
    active_tmux_bin = tmux_bin or shutil.which("tmux")
    if not active_tmux_bin:
        return

    default_terminal = detect_tmux_default_terminal()
    normalized_launch_mode = validate_launch_mode(launch_mode)
    root_scroll_up_action = [
        "if-shell",
        "-F",
        "#{pane_in_mode}",
        f"send-keys -X -N {TMUX_WHEEL_SCROLL_SPEED} scroll-up",
        f"copy-mode -e \\; send-keys -X -N {TMUX_WHEEL_SCROLL_SPEED} scroll-up",
    ]
    root_scroll_down_action = [
        "if-shell",
        "-F",
        "#{pane_in_mode}",
        f"send-keys -X -N {TMUX_WHEEL_SCROLL_SPEED} scroll-down",
        "",
    ]
    copy_scroll_up_action = [
        "send-keys",
        "-X",
        "-N",
        str(TMUX_WHEEL_SCROLL_SPEED),
        "scroll-up",
    ]
    copy_scroll_down_action = [
        "send-keys",
        "-X",
        "-N",
        str(TMUX_WHEEL_SCROLL_SPEED),
        "scroll-down",
    ]
    mouse_enabled = managed_tmux_mouse_enabled()
    commands = [
        [active_tmux_bin, "set-option", "-gq", "mouse", "on" if mouse_enabled else "off"],
        [active_tmux_bin, "set-option", "-t", session_name, "mouse", "on" if mouse_enabled else "off"],
        [active_tmux_bin, "set-option", "-gq", "history-limit", str(TMUX_HISTORY_LIMIT)],
        [active_tmux_bin, "set-option", "-gq", "default-terminal", default_terminal],
        [active_tmux_bin, "set-option", "-gq", "focus-events", "on"],
        [active_tmux_bin, "set-option", "-gq", "escape-time", "10"],
        [active_tmux_bin, "set-option", "-gq", "xterm-keys", "on"],
        [active_tmux_bin, "set-option", "-gq", "extended-keys", "on"],
        [active_tmux_bin, "set-option", "-gq", "allow-passthrough", "off"],
        [active_tmux_bin, "set-option", "-gqu", "terminal-overrides"],
        [
            active_tmux_bin,
            "set-option",
            "-gq",
            "terminal-overrides",
            "xterm*:RGB,screen*:RGB,tmux*:RGB,alacritty*:RGB,rxvt-unicode*:RGB",
        ],
        [active_tmux_bin, "set-option", "-pt", session_name, "remain-on-exit", "off"],
        [active_tmux_bin, "set-option", "-pt", session_name, "allow-rename", "off"],
        [active_tmux_bin, "set-window-option", "-t", session_name, "alternate-screen", "on"],
        [active_tmux_bin, "unbind-key", "-T", "root", "MouseDrag1Pane"],
        [active_tmux_bin, "unbind-key", "-T", "root", "DoubleClick1Pane"],
        [active_tmux_bin, "unbind-key", "-T", "root", "TripleClick1Pane"],
    ]
    if mouse_enabled:
        commands.extend(
            [
                [
                    active_tmux_bin,
                    "bind-key",
                    "-T",
                    "root",
                    "MouseDrag1Pane",
                    "copy-mode",
                    "-M",
                ],
                [
                    active_tmux_bin,
                    "bind-key",
                    "-T",
                    "root",
                    "DoubleClick1Pane",
                    "select-pane",
                    "-t=",
                    ";",
                    "copy-mode",
                    "-H",
                    ";",
                    "send-keys",
                    "-X",
                    "select-word",
                ],
                [
                    active_tmux_bin,
                    "bind-key",
                    "-T",
                    "root",
                    "TripleClick1Pane",
                    "select-pane",
                    "-t=",
                    ";",
                    "copy-mode",
                    "-H",
                    ";",
                    "send-keys",
                    "-X",
                    "select-line",
                ],
            ]
        )
    for key in (*TMUX_WHEEL_SCROLL_UP_KEYS, *TMUX_WHEEL_SCROLL_DOWN_KEYS, *TMUX_SCROLL_PASSTHROUGH_KEYS, *TMUX_SCROLL_STATUS_KEYS):
        commands.append([active_tmux_bin, "unbind-key", "-T", "root", key])
    for table in ("copy-mode-vi", "copy-mode"):
        for key in (*TMUX_WHEEL_SCROLL_UP_KEYS, *TMUX_WHEEL_SCROLL_DOWN_KEYS, *TMUX_SCROLL_PASSTHROUGH_KEYS):
            commands.append([active_tmux_bin, "unbind-key", f"-T{table}", key])
    for key in TMUX_WHEEL_SCROLL_UP_KEYS:
        commands.append([active_tmux_bin, "bind-key", "-T", "root", key, *root_scroll_up_action])
    for key in TMUX_WHEEL_SCROLL_DOWN_KEYS:
        commands.append([active_tmux_bin, "bind-key", "-T", "root", key, *root_scroll_down_action])
    for table in ("copy-mode-vi", "copy-mode"):
        for key in TMUX_COPY_MODE_SCROLL_UP_KEYS:
            commands.append([active_tmux_bin, "bind-key", f"-T{table}", key, *copy_scroll_up_action])
        for key in TMUX_COPY_MODE_SCROLL_DOWN_KEYS:
            commands.append([active_tmux_bin, "bind-key", f"-T{table}", key, *copy_scroll_down_action])
    for command in commands:
        subprocess.run(
            command,
            capture_output=True,
            text=True,
            check=False,
        )


def query_tmux_statuses() -> dict[str, str] | None:
    tmux_bin = shutil.which("tmux")
    if not tmux_bin:
        return None
    result = subprocess.run(
        [tmux_bin, "list-sessions", "-F", "#{session_name}\t#{session_attached}"],
        capture_output=True,
        text=True,
        check=False,
    )
    if result.returncode != 0:
        stderr = (result.stderr or "").strip().lower()
        stdout = (result.stdout or "").strip().lower()
        if "no server running" in stderr or "no sessions" in stderr or "no server running" in stdout or "no sessions" in stdout:
            return {}
        return None
    statuses: dict[str, str] = {}
    for line in result.stdout.splitlines():
        name, _, attached = line.partition("\t")
        if not name:
            continue
        try:
            attached_count = int(attached.strip() or "0")
        except ValueError:
            attached_count = 0
        statuses[name] = "attached" if attached_count > 0 else "detached"
    return statuses


def get_tmux_statuses() -> dict[str, str]:
    return query_tmux_statuses() or {}


def session_status(record: dict[str, object], statuses: dict[str, str] | None) -> str:
    if statuses is None:
        return "unknown"
    return statuses.get(record_session_name(record), "stopped")


def list_tmux_clients(session_name: str | None = None) -> list[dict[str, str]]:
    tmux_bin = shutil.which("tmux")
    if not tmux_bin:
        return []
    command = [tmux_bin, "list-clients", "-F", "#{client_tty}\t#{client_pid}"]
    if session_name:
        command.extend(["-t", session_name])
    result = subprocess.run(
        command,
        capture_output=True,
        text=True,
        check=False,
    )
    if result.returncode != 0:
        return []
    clients: list[dict[str, str]] = []
    for line in result.stdout.splitlines():
        tty, _, pid = line.partition("\t")
        clients.append(
            {
                "tty": tty.strip(),
                "pid": pid.strip(),
            }
        )
    return clients


def tmux_client_is_stale(client_tty: str, client_pid: str) -> bool:
    tty = client_tty.strip()
    if tty and not os.path.exists(tty):
        return True

    pid = client_pid.strip()
    if not pid:
        return False
    try:
        os.kill(int(pid), 0)
    except ProcessLookupError:
        return True
    except PermissionError:
        return False
    except ValueError:
        return False
    return False


def detach_stale_tmux_clients(session_name: str, *, tmux_bin: str | None = None) -> list[str]:
    active_tmux_bin = tmux_bin or shutil.which("tmux")
    if not active_tmux_bin:
        return []

    detached_targets: list[str] = []
    for client in list_tmux_clients(session_name):
        target = str(client.get("tty", "")).strip()
        if not target:
            continue
        if not tmux_client_is_stale(target, str(client.get("pid", ""))):
            continue
        result = subprocess.run(
            [active_tmux_bin, "detach-client", "-t", target],
            capture_output=True,
            text=True,
            check=False,
        )
        if result.returncode == 0:
            detached_targets.append(target)
    return detached_targets


def launch_tmux_session(
    *,
    global_id: str,
    session_name: str,
    workdir: str,
    script_path: str,
    python_bin: str,
    codex_args: list[str],
    launch_mode: str = LAUNCH_MODE_CODEX,
    detach: bool = False,
) -> int:
    tmux_bin = ensure_tmux_available()
    normalized_launch_mode = validate_launch_mode(launch_mode)
    managed_args = prepare_managed_codex_args(codex_args) if normalized_launch_mode == LAUNCH_MODE_CODEX else []
    env = dict(os.environ)
    session_env = managed_tmux_environment(global_id, launch_mode=normalized_launch_mode)
    env.update(session_env)
    command = [
        tmux_bin,
        "-u",
        "new-session",
    ]
    command.append("-d")
    for key, value in session_env.items():
        command.extend(["-e", f"{key}={value}"])
    command.extend(
        [
            "-s",
            session_name,
            "-c",
            workdir,
            python_bin,
            script_path,
            INTERNAL_RUN_COMMAND,
        ]
    )
    command += managed_args
    exit_code = subprocess.call(command, cwd=workdir, env=env)
    if exit_code != 0:
        return exit_code
    apply_managed_tmux_profile(session_name, launch_mode=normalized_launch_mode, tmux_bin=tmux_bin)
    if detach:
        return 0
    return attach_tmux_session(session_name, launch_mode=normalized_launch_mode)


def respawn_tmux_pane(
    *,
    global_id: str,
    target: str,
    workdir: str,
    script_path: str,
    python_bin: str,
    codex_args: list[str],
    launch_mode: str = LAUNCH_MODE_CODEX,
) -> int:
    tmux_bin = ensure_tmux_available()
    normalized_launch_mode = validate_launch_mode(launch_mode)
    managed_args = prepare_managed_codex_args(codex_args) if normalized_launch_mode == LAUNCH_MODE_CODEX else []
    env = dict(os.environ)
    session_env = managed_tmux_environment(global_id, launch_mode=normalized_launch_mode)
    env.update(session_env)
    command = [
        tmux_bin,
        "respawn-pane",
        "-k",
        "-t",
        target,
        "-c",
        workdir,
    ]
    for key, value in session_env.items():
        command.extend(["-e", f"{key}={value}"])
    command.extend(
        [
            python_bin,
            script_path,
            INTERNAL_RUN_COMMAND,
        ]
    )
    command += managed_args
    exit_code = subprocess.call(command, cwd=workdir, env=env)
    if exit_code == 0:
        session_name = target.split(":", 1)[0]
        apply_managed_tmux_profile(session_name, launch_mode=normalized_launch_mode, tmux_bin=tmux_bin)
    return exit_code


def attach_tmux_session(
    session_name: str,
    *,
    launch_mode: str = LAUNCH_MODE_CODEX,
    detach_others: bool | None = None,
) -> int:
    tmux_bin = ensure_tmux_available()
    detach_stale_tmux_clients(session_name, tmux_bin=tmux_bin)
    apply_managed_tmux_profile(session_name, launch_mode=launch_mode, tmux_bin=tmux_bin)
    apply_client_terminal_profile()
    subprocess.run(
        [tmux_bin, "send-keys", "-X", "-t", f"{session_name}:0.0", "cancel"],
        capture_output=True,
        text=True,
        check=False,
    )
    command = [tmux_bin, "-u", "attach-session"]
    should_detach_others = attach_detach_others_default() if detach_others is None else detach_others
    if should_detach_others:
        command.append("-d")
    command.extend(["-t", session_name])
    return subprocess.call(command)


def print_sessions(
    records: list[dict[str, object]],
    *,
    include_workdir: bool,
    statuses: dict[str, str] | None = None,
) -> None:
    if not records:
        print("当前范围内暂无会话。")
        return

    active_statuses = query_tmux_statuses() if statuses is None else statuses
    headers = ["Local", "Global", "Status", "Created", "LastActive"]
    if include_workdir:
        headers.append("Workdir")
    rows: list[list[str]] = []
    for record in records:
        row = [
            str(record.get("local_id", "")),
            str(record.get("global_id", "")),
            session_status(record, active_statuses),
            str(record.get("created_at", "")),
            str(record.get("last_active_at", "")),
        ]
        if include_workdir:
            row.append(str(record.get("workdir", "")))
        rows.append(row)

    widths = [len(header) for header in headers]
    for row in rows:
        for index, value in enumerate(row):
            widths[index] = max(widths[index], len(value))

    print("  ".join(header.ljust(widths[index]) for index, header in enumerate(headers)))
    for row in rows:
        print("  ".join(value.ljust(widths[index]) for index, value in enumerate(row)))


def dispatch_management_command(
    argv: list[str],
    *,
    script_path: str,
    python_bin: str | None = None,
    store: SessionStore | None = None,
) -> int | None:
    if not argv:
        return None

    command = argv[0]
    if command not in MANAGEMENT_COMMANDS:
        return None

    active_store = store or SessionStore()
    active_python = python_bin or sys.executable or shutil.which("python3") or "python3"
    tmux_statuses = query_tmux_statuses()
    if tmux_statuses is not None:
        active_store.prune_stopped_sessions(tmux_statuses)

    try:
        if command in {"ls", "list"}:
            print_sessions(
                active_store.list_sessions(normalize_workdir()),
                include_workdir=False,
                statuses=tmux_statuses,
            )
            return 0

        if command == "lsa":
            print_sessions(
                active_store.list_sessions(),
                include_workdir=True,
                statuses=tmux_statuses,
            )
            return 0

        if command == "a":
            if len(argv) != 2:
                print("用法: dx a <local_id>", file=sys.stderr)
                return 2
            record = active_store.get_by_local_id(normalize_workdir(), argv[1])
            if record is None:
                print(f"当前目录下找不到本地 ID {argv[1]}。", file=sys.stderr)
                return 1
            if tmux_statuses is not None and session_status(record, tmux_statuses) == "stopped":
                print(f"会话 {record['local_id']} / {record['global_id']} 已停止。", file=sys.stderr)
                return 1
            active_store.touch(str(record["global_id"]))
            return attach_tmux_session(
                record_session_name(record),
                launch_mode=str(record.get("launch_mode") or LAUNCH_MODE_CODEX),
            )

        if command == "aa":
            if len(argv) != 2:
                print("用法: dx aa <global_id>", file=sys.stderr)
                return 2
            record = active_store.get_by_global_id(argv[1])
            if record is None:
                print(f"找不到全局 ID {argv[1]}。", file=sys.stderr)
                return 1
            if tmux_statuses is not None and session_status(record, tmux_statuses) == "stopped":
                print(f"会话 {record['global_id']} 已停止。", file=sys.stderr)
                return 1
            active_store.touch(str(record["global_id"]))
            return attach_tmux_session(
                record_session_name(record),
                launch_mode=str(record.get("launch_mode") or LAUNCH_MODE_CODEX),
            )

        if command == "s":
            if len(argv) < 2:
                print("用法: dxs <local_id> [model_selector]", file=sys.stderr)
                return 2
            codex_args = expand_model_selector_args(argv[2:])
            record = active_store.create_session(
                workdir=normalize_workdir(),
                local_id=argv[1],
                global_id=None,
                codex_args=codex_args,
                launch_mode=LAUNCH_MODE_CODEX,
            )
            try:
                print(
                    "已创建会话:"
                    f" local={record['local_id']}"
                    f" global={record['global_id']}"
                    f" name={record_session_name(record)}"
                )
                return_code = launch_tmux_session(
                    global_id=str(record["global_id"]),
                    session_name=record_session_name(record),
                    workdir=str(record["workdir"]),
                    script_path=script_path,
                    python_bin=active_python,
                    codex_args=codex_args,
                    launch_mode=LAUNCH_MODE_CODEX,
                )
            except Exception:
                active_store.remove(str(record["global_id"]))
                raise
            return return_code

        if command == "sa":
            if len(argv) < 2:
                print("用法: dxsa <global_id> [model_selector]", file=sys.stderr)
                return 2
            codex_args = expand_model_selector_args(argv[2:])
            record = active_store.create_session(
                workdir=normalize_workdir(),
                local_id=None,
                global_id=argv[1],
                codex_args=codex_args,
                launch_mode=LAUNCH_MODE_CODEX,
            )
            try:
                print(
                    "已创建会话:"
                    f" local={record['local_id']}"
                    f" global={record['global_id']}"
                    f" name={record_session_name(record)}"
                )
                return_code = launch_tmux_session(
                    global_id=str(record["global_id"]),
                    session_name=record_session_name(record),
                    workdir=str(record["workdir"]),
                    script_path=script_path,
                    python_bin=active_python,
                    codex_args=codex_args,
                    launch_mode=LAUNCH_MODE_CODEX,
                )
            except Exception:
                active_store.remove(str(record["global_id"]))
                raise
            return return_code

        if command == "sd":
            if len(argv) != 2:
                print("用法: dxsd <local_id>", file=sys.stderr)
                return 2
            record = active_store.create_session(
                workdir=normalize_workdir(),
                local_id=argv[1],
                global_id=None,
                codex_args=[],
                launch_mode=LAUNCH_MODE_SHELL,
            )
            try:
                print(
                    "已创建 shell 会话:"
                    f" local={record['local_id']}"
                    f" global={record['global_id']}"
                    f" name={record_session_name(record)}"
                )
                return_code = launch_tmux_session(
                    global_id=str(record["global_id"]),
                    session_name=record_session_name(record),
                    workdir=str(record["workdir"]),
                    script_path=script_path,
                    python_bin=active_python,
                    codex_args=[],
                    launch_mode=LAUNCH_MODE_SHELL,
                )
            except Exception:
                active_store.remove(str(record["global_id"]))
                raise
            return return_code

        if command == "sda":
            if len(argv) != 2:
                print("用法: dxsda <global_id>", file=sys.stderr)
                return 2
            record = active_store.create_session(
                workdir=normalize_workdir(),
                local_id=None,
                global_id=argv[1],
                codex_args=[],
                launch_mode=LAUNCH_MODE_SHELL,
            )
            try:
                print(
                    "已创建 shell 会话:"
                    f" local={record['local_id']}"
                    f" global={record['global_id']}"
                    f" name={record_session_name(record)}"
                )
                return_code = launch_tmux_session(
                    global_id=str(record["global_id"]),
                    session_name=record_session_name(record),
                    workdir=str(record["workdir"]),
                    script_path=script_path,
                    python_bin=active_python,
                    codex_args=[],
                    launch_mode=LAUNCH_MODE_SHELL,
                )
            except Exception:
                active_store.remove(str(record["global_id"]))
                raise
            return return_code
    except (RuntimeError, ValueError, subprocess.CalledProcessError) as exc:
        print(f"dxcli: {exc}", file=sys.stderr)
        return 1

    return None
