#!/usr/bin/env python3
"""Canonical gotta entrypoints."""

from __future__ import annotations

from contextlib import contextmanager, redirect_stderr, redirect_stdout
import io
from importlib.metadata import PackageNotFoundError, version as package_version
import os
from pathlib import Path
import subprocess
import sys
import time
import json

from gotta.builtin import SessionAccessMode, get_plugin
from gotta.compat import UTC, datetime, tomllib
from gotta.actors import resolve_actor_context, seed_actor_context
from gotta.dispatch import (
    available_plugins,
    print_usage,
    run_plugin,
    system_exit_status,
)
from gotta.dispatch import SUPPRESS_MATERIALIZATION_ENV
from gotta.helptext import is_long_help_request, strip_long_help_boilerplate
from gotta.actor import (
    session_actor,
    supervisor_note_check_message,
    supervisor_stop_message,
    supervisor_stop_pending,
)
from gotta.content import (
    CONTENT_ENV,
    CONTEXT_ACTIVE_ENV,
    CONTEXT_ID_ENV,
    CONTEXT_SOURCE_ENV,
    current_context_binding,
    default_session_id,
    ensure_private_dir,
    SESSION_ACTIVATION_ENV,
    SESSION_CREATED_ENV,
    SESSION_ENV,
    SESSION_ID_ENV,
    SESSION_ACTOR_ENV,
    SESSION_REPO_ENV,
    SESSION_INITIALIZED_ENV,
    CommonOptions,
    DEFAULT_SESSION_ROOT,
    load_state_env_at_root,
    resolve_dirs,
    resolve_session_reference,
    session_actor_scope,
    session_id,
    session_identity as content_session_identity,
    session_is_initialized,
    shared_session_root,
    sh_quote,
    write_text_atomic,
    write_session_state,
)
from gotta import session as session_plugin
from gotta import topology
from gotta import binding as binding_helpers


def die(message: str, code: int = 2) -> int:
    print(message, file=sys.stderr)
    return code


def _silence_stdout() -> None:
    try:
        devnull = os.open(os.devnull, os.O_WRONLY)
    except OSError:
        return
    try:
        os.dup2(devnull, sys.stdout.fileno())
    except (AttributeError, OSError, ValueError):
        pass
    finally:
        os.close(devnull)


def normalize_help_aliases(argv: list[str]) -> list[str]:
    if not argv:
        return argv
    if argv[0] == "help":
        if len(argv) == 1:
            return ["--help-all"]
        if len(argv) >= 2 and argv[1] == "all":
            return ["--help-all", *argv[2:]]
        return [argv[1], "--help-all", *argv[2:]]
    normalized: list[str] = []
    index = 0
    while index < len(argv):
        token = argv[index]
        if token == "help":
            normalized.append("--help-all")
            if index + 1 < len(argv) and argv[index + 1] == "all":
                index += 2
                continue
            index += 1
            continue
        normalized.append(token)
        index += 1
    return normalized


_GLOBAL_FLAGS = {"--quiet", "--full-output"}


def _argv_without_global_flags(argv: list[str]) -> list[str]:
    return [token for token in argv if token not in _GLOBAL_FLAGS]


def _plugin_invocation(argv: list[str]) -> tuple[str, list[str]] | None:
    for index, token in enumerate(argv):
        if token in _GLOBAL_FLAGS:
            continue
        plugin = token
        plugin_argv = [*argv[:index], *argv[index + 1 :]]
        return plugin, plugin_argv
    return None


def _gotta_main(argv: list[str]) -> int:
    argv = normalize_help_aliases(argv)
    stripped = _argv_without_global_flags(argv)
    if _is_version_request(stripped):
        print(f"gotta {_gotta_version()}")
        return 0
    if not stripped or (len(stripped) == 1 and stripped[0] in {"-h", "--help"}):
        return print_usage()
    if is_long_help_request(stripped):
        print("# gotta")
        print("")
        print("usage: gotta <plugin> [args...]")
        print("")
        print("Canonical operator path: `gotta ...`")
        print("")
        print(
            "Builtin non-session surfaces: `gotta version`, `gotta --version`, `gotta search`"
        )
        print("")
        print(
            "Session synthesis surfaces live under `gotta session`: "
            "`manifest`, `timeline`, `graph`, `leads`, `analyze`, `scan`"
        )
        print("")
        print("This top-level long help shows only plugin root surfaces.")
        print("Use `gotta <plugin> --help-all` for recursive help within one plugin.")
        print("")
        print("available plugins:")
        for plugin in available_plugins():
            print(f"  - {plugin}")
        for plugin in available_plugins():
            print("")
            print(f"## gotta {plugin}")
            print("")
            buffer = io.StringIO()
            try:
                with redirect_stdout(buffer), redirect_stderr(buffer):
                    result = run_plugin(plugin, ["--help"])
            except SystemExit as exc:
                result = system_exit_status(exc, emit=False)
            if result != 0:
                return result
            rendered = strip_long_help_boilerplate(buffer.getvalue())
            if rendered:
                print(rendered)
        print("")
        print("---")
        print("")
        print("End of top-level long help for `gotta`.")
        print("Plugin subtrees were intentionally omitted at this level.")
        print("Use `gotta <plugin> --help-all` for recursive help within one plugin.")
        return 0

    invocation = _plugin_invocation(argv)
    if invocation is None:
        return print_usage()
    plugin, plugin_argv = invocation
    if plugin not in available_plugins():
        plugins = ", ".join(available_plugins())
        return die(f"unknown gotta plugin: {plugin}. available plugins: {plugins}")
    return run_plugin(plugin, plugin_argv)


def _session_token(context_id: str) -> str:
    return default_session_id(context_id)


_AMBIENT_SESSIONLESS_ENV = "GOTTA_AMBIENT_SESSIONLESS"


_SESSION_LOCK_TIMEOUT_SECONDS = 5.0
_SESSION_LOCK_SLEEP_SECONDS = 0.05
_AUTO_BOOTSTRAP_CONTEXT_SOURCES = {"codex_thread", "terminal_session"}


@contextmanager
def _session_creation_lock(base_dir: Path, context_id: str):
    ensure_private_dir(base_dir)
    lock_path = base_dir / f".{_session_token(context_id)}.lock"
    deadline = time.monotonic() + _SESSION_LOCK_TIMEOUT_SECONDS
    fd: int | None = None
    while True:
        try:
            fd = os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
            os.write(fd, f"{os.getpid()}\n".encode("utf-8"))
            break
        except FileExistsError:
            if time.monotonic() >= deadline:
                raise SystemExit(
                    f"timed out waiting for gotta session bind lock: {lock_path}"
                )
            time.sleep(_SESSION_LOCK_SLEEP_SECONDS)
    try:
        yield
    finally:
        if fd is not None:
            os.close(fd)
        try:
            lock_path.unlink()
        except FileNotFoundError:
            pass


def _discover_repo_root() -> Path | None:
    try:
        proc = subprocess.run(
            ["git", "rev-parse", "--show-toplevel"],
            check=True,
            capture_output=True,
            text=True,
        )
    except (FileNotFoundError, subprocess.CalledProcessError):
        return None
    raw = proc.stdout.strip()
    if not raw:
        return None
    return Path(raw).expanduser().resolve()


def _actor_stop_warning(root: Path) -> str:
    actor_name = session_actor(root)
    if not actor_name:
        return ""
    path = root / "state" / "actor.json"
    if not path.exists():
        return ""
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return ""
    if not isinstance(payload, dict):
        return ""
    if not supervisor_stop_pending(payload):
        return ""
    return supervisor_stop_message(
        actor_name,
        status_payload=payload,
    )


def _actor_note_check_warning(root: Path) -> str:
    actor_name = session_actor(root)
    if not actor_name:
        return ""
    try:
        payload = session_plugin._actor_status_payload(root, actor_name)
    except SystemExit:
        return ""
    return supervisor_note_check_message(actor_name, status_payload=payload)


def _should_emit_actor_note_check_warning(
    *,
    argv: list[str],
    root: Path,
    requested_root: Path | None,
    acting_actor: str,
    explicit_actor: str | None,
) -> bool:
    actor_name = session_actor(root)
    if not actor_name:
        return False
    if topology.normalize_identity(acting_actor) != actor_name:
        return False
    plugin_name = argv[0] if argv else ""
    if plugin_name == "session":
        return False
    if explicit_actor:
        return topology.normalize_identity(explicit_actor) == actor_name
    if requested_root is not None:
        if topology.parse_shared_session_root(requested_root) is not None:
            return False
        if (requested_root / "actors").is_dir():
            return False
    return True


def _iter_session_roots(base_dir: Path) -> list[Path]:
    if not base_dir.exists():
        return []
    roots: list[Path] = []
    for session_dir in base_dir.iterdir():
        if not session_dir.is_dir():
            continue
        actors_dir = session_dir / "actors"
        if not actors_dir.is_dir():
            continue
        for actor_dir in actors_dir.iterdir():
            if actor_dir.is_dir() and (actor_dir / "state" / "env").exists():
                roots.append(actor_dir.resolve())
    return sorted(roots)


def _create_session_root(
    root: Path,
    *,
    context_id: str,
    context_source: str,
) -> tuple[Path, bool]:
    resolved_root = root.expanduser().resolve()
    current_session_id = topology.shared_session_id(resolved_root)
    actor = content_session_identity(resolved_root)
    actor_branch = (
        resolved_root.parent.name == "actors"
        or topology.parse_grouped_session_root(resolved_root) is not None
    )
    session_dir = session_plugin._group_session_root(resolved_root)
    content_dir = session_dir / "content"
    ensure_private_dir(session_dir)
    ensure_private_dir(content_dir)
    dirs = resolve_dirs(
        CommonOptions(
            session_dir=str(resolved_root),
            content_dir=str(content_dir),
            actor=actor,
        ),
        create=True,
    )
    repo_root = _discover_repo_root()
    write_session_state(
        dirs,
        {
            SESSION_CREATED_ENV: load_state_env_at_root(root).get(
                SESSION_CREATED_ENV, ""
            )
            or datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
            SESSION_REPO_ENV: str(repo_root) if repo_root is not None else "",
            SESSION_ID_ENV: current_session_id,
            SESSION_ACTOR_ENV: actor,
        },
    )
    content_link = dirs.session_dir / "content"
    if content_link.resolve() != content_dir.resolve():
        if content_link.is_symlink() or content_link.is_file():
            content_link.unlink(missing_ok=True)
        elif content_link.exists():
            os.rmdir(content_link)
        content_link.symlink_to(os.path.relpath(content_dir, start=content_link.parent))
    session_link = dirs.session_dir / "session"
    if session_link.is_symlink() or session_link.is_file():
        session_link.unlink(missing_ok=True)
    metadata_path = session_dir / "session.json"
    payload: dict[str, object]
    if metadata_path.exists():
        try:
            existing = json.loads(metadata_path.read_text(encoding="utf-8"))
        except json.JSONDecodeError:
            existing = {}
        payload = existing if isinstance(existing, dict) else {}
    else:
        payload = {}
    members = payload.get("members")
    if not isinstance(members, list):
        members = []
    normalized_members = [
        topology.normalize_identity(str(item)) for item in members if str(item).strip()
    ]
    if actor_branch and actor and actor not in normalized_members:
        normalized_members.append(actor)
    actors = payload.get("actors")
    if not isinstance(actors, dict):
        actors = {}
    if actor_branch and actor:
        actors.setdefault(
            actor,
            {
                "label": actor,
                "model": session_plugin.ACTOR_DEFAULT_MODEL,
                "resume_uuid": "",
                "template": "",
            },
        )
    payload["session_id"] = current_session_id
    payload.setdefault(
        "created_at", datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
    )
    payload["updated_at"] = datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
    payload["members"] = sorted(dict.fromkeys(normalized_members))
    payload["actors"] = actors
    write_text_atomic(
        metadata_path,
        json.dumps(payload, indent=2, sort_keys=True) + "\n",
    )
    return dirs.session_dir.resolve(), True


def _bind_session_root(context_id: str, context_source: str) -> tuple[Path, bool]:
    fingerprint = _session_token(context_id)
    session_id = fingerprint
    root = topology.session_root_for(session_id, fingerprint)
    ensure_private_dir(shared_session_root(session_id))
    created = False
    with _session_creation_lock(
        DEFAULT_SESSION_ROOT.expanduser().resolve(), context_id
    ):
        if not session_is_initialized(root):
            root, _created = _create_session_root(
                root,
                context_id=context_id,
                context_source=context_source,
            )
            created = True
        dirs = resolve_dirs(CommonOptions(session_dir=str(root)), create=False)
        write_session_state(
            dirs,
            {
                SESSION_ID_ENV: session_id,
                SESSION_ACTOR_ENV: fingerprint,
            },
        )
        now = datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
        existing = topology.load_binding_record(fingerprint) or {}
        topology.write_binding(
            fingerprint,
            root,
            context_id=context_id,
            context_source=context_source,
            session_id=session_id,
            actor=fingerprint,
            created_at=str(existing.get("createdAt") or now),
            updated_at=now,
        )
    return root, created


def _resolve_existing_session_root(context_id: str, context_source: str) -> Path | None:
    binding = topology.resolve_binding(_session_token(context_id))
    if binding is not None and session_is_initialized(binding):
        return binding
    return None


def _hydrate_environment(root: Path, *, context_id: str, context_source: str) -> None:
    state = load_state_env_at_root(root)
    for key, value in state.items():
        os.environ[key] = value
    os.environ[SESSION_ENV] = str(root)
    os.environ[SESSION_ID_ENV] = str(state.get(SESSION_ID_ENV) or session_id(root))
    os.environ[CONTENT_ENV] = str(state.get(CONTENT_ENV) or (root / "content"))
    actor_scope = str(state.get(SESSION_ACTOR_ENV) or session_actor_scope(root))
    if actor_scope:
        os.environ[SESSION_ACTOR_ENV] = actor_scope
    else:
        os.environ.pop(SESSION_ACTOR_ENV, None)
    os.environ[CONTEXT_ACTIVE_ENV] = "1"
    os.environ[CONTEXT_ID_ENV] = context_id
    os.environ[CONTEXT_SOURCE_ENV] = context_source
    os.environ[SESSION_ACTIVATION_ENV] = "gotta"
    repo_root = state.get(SESSION_REPO_ENV, "").strip()
    if repo_root:
        os.environ[SESSION_REPO_ENV] = repo_root
        venv = Path(repo_root) / ".venv"
        venv_bin = venv / "bin"
        if venv_bin.is_dir():
            path_entries = [str(venv_bin)]
            current_path = os.environ.get("PATH", "")
            if current_path:
                path_entries.append(current_path)
            os.environ["PATH"] = ":".join(path_entries)
            os.environ["VIRTUAL_ENV"] = str(venv)


def _ensure_scaffolded_session(
    root: Path,
    *,
    context_id: str,
    context_source: str,
) -> tuple[Path, bool]:
    shared_session = topology.parse_shared_session_root(root)
    if shared_session is not None:
        raise RuntimeError(f"shared session roots require an actor: {root}")
    created = False
    if not session_is_initialized(root):
        root, created = _create_session_root(
            root,
            context_id=context_id,
            context_source=context_source,
        )
    state = load_state_env_at_root(root)
    if state.get(SESSION_INITIALIZED_ENV, "").strip() != "1":
        from gotta import session as sessionlib

        sessionlib.scaffold_session(root)
    return root, created


def _explicit_session_arg(argv: list[str]) -> str | None:
    for index, token in enumerate(argv):
        if token == "--session" and index + 1 < len(argv):
            return argv[index + 1]
        if token.startswith("--session="):
            return token.split("=", 1)[1]
    return None


def _flag_value(argv: list[str], flag: str) -> str | None:
    for index, token in enumerate(argv):
        if token == flag and index + 1 < len(argv):
            return argv[index + 1]
        if token.startswith(f"{flag}="):
            return token.split("=", 1)[1]
    return None


def _explicit_actor_arg(argv: list[str]) -> str | None:
    return _flag_value(argv, "--actor")


def _resolve_shared_explicit_session(raw: str) -> Path | None:
    normalized = raw.strip()
    if not normalized:
        return None
    candidate = Path(normalized).expanduser()
    if candidate.is_absolute():
        resolved = candidate.resolve()
        if topology.parse_shared_session_root(resolved) is not None:
            return resolved
        if topology.parse_grouped_session_root(resolved) is not None:
            return resolved
        if (resolved / "state" / "env").exists():
            return resolved
        return None
    if "/" in normalized:
        root = resolve_session_reference(normalized, allow_missing=False)
        if root is None:
            return None
        return root
    shared_root = topology.shared_session_root_for(normalized)
    if shared_root.exists() or shared_root.is_symlink():
        return shared_root.resolve()
    return None


def _prefer_bound_session_root() -> Path | None:
    explicit = os.environ.get(SESSION_ENV, "").strip()
    if explicit:
        root = resolve_session_reference(explicit, allow_missing=False)
        if root is not None and session_is_initialized(root):
            return root
    root = topology.resolve_binding(current_context_binding().binding_id)
    if root is not None and session_is_initialized(root):
        return root
    return None


def _active_identity(context_id: str) -> str:
    current = _prefer_bound_session_root()
    if current is not None:
        current_identity = topology.session_identity(current)
        if current_identity and not topology.is_placeholder_identity(current_identity):
            return current_identity
    explicit = os.environ.get(SESSION_ACTOR_ENV, "").strip()
    if explicit and not topology.is_placeholder_identity(explicit):
        return topology.normalize_identity(explicit)
    return _session_token(context_id)


def _should_auto_bootstrap_session(
    *,
    argv: list[str],
    context_source: str,
    explicit_session: str | None,
    session_access: SessionAccessMode,
) -> bool:
    if explicit_session:
        return False
    if session_access == "none":
        return False
    if context_source not in _AUTO_BOOTSTRAP_CONTEXT_SOURCES:
        return False
    if argv[:1] != ["read"]:
        return True
    try:
        from gotta.target import resolve_read_target

        resolved = resolve_read_target(argv[1:])
    except SystemExit:
        return False
    return resolved.should_materialize


def _is_nonbinding_help(argv: list[str]) -> bool:
    if not argv:
        return True
    if len(argv) == 1 and argv[0] in {"-h", "--help", "--help-all"}:
        return True
    return "--help" in argv or "--help-all" in argv


def _gotta_version() -> str:
    try:
        return package_version("gotta")
    except PackageNotFoundError:
        pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml"
        try:
            payload = tomllib.loads(pyproject.read_text(encoding="utf-8"))
        except (OSError, tomllib.TOMLDecodeError):
            return "unknown"
        project = payload.get("project")
        if not isinstance(project, dict):
            return "unknown"
        return str(project.get("version") or "").strip() or "unknown"


def _is_version_request(argv: list[str]) -> bool:
    if not argv:
        return False
    return len(argv) == 1 and argv[0] in {"version", "--version", "-V"}


def _creation_receipt_lines(
    root: Path,
    *,
    context_id: str,
    context_source: str,
    bound: bool,
) -> list[str]:
    shared_session_id = topology.parse_grouped_session_root(root)
    shared_id = (
        shared_session_id[0]
        if shared_session_id is not None
        else topology.parse_shared_session_root(root)
    )
    shared_topology = bool(shared_id)
    bind_target = shared_id or sh_quote(str(root))
    reuse_flag = "<shared-session-id>" if shared_topology else "<session-root>"
    lines = [
        "created a new gotta session:",
        f"- session root: {root}",
        f"- context id: {context_id}",
        f"- context source: {context_source}",
    ]
    bind_command = f"`gotta session bind {bind_target}`"
    if bound:
        lines.extend(
            [
                "- next: this context is now bound to that session root",
                "  same-context fresh-process commands should resolve here automatically",
                f"  to reuse from another context, pass `--session {reuse_flag}` or run",
                f"  {bind_command}",
            ]
        )
        return lines
    lines.extend(
        [
            "- next: this context is not yet bound to the created session",
            f"  future commands should pass `--session {reuse_flag}` or run",
            f"  {bind_command} to adopt it ambiently",
        ]
    )
    return lines


def _session_access_mode(argv: list[str]) -> SessionAccessMode:
    if not argv:
        return "none"
    spec = get_plugin(argv[0])
    if spec is None:
        return "none"
    access = spec.session_access
    if access is None:
        return "write"
    if callable(access):
        return access(argv[1:])
    return access


def _existing_actor_root_for_session(
    root: Path,
    *,
    preferred_identities: list[str],
) -> Path | None:
    actors_dir = session_plugin._group_session_root(root) / "actors"
    if not actors_dir.is_dir():
        return None
    initialized: dict[str, Path] = {}
    for actor_dir in sorted(actors_dir.iterdir()):
        if not actor_dir.is_dir():
            continue
        resolved = actor_dir.resolve()
        if not session_is_initialized(resolved):
            continue
        identity = topology.session_identity(resolved)
        if not identity or topology.is_placeholder_identity(identity):
            continue
        initialized[identity] = resolved
    for identity in preferred_identities:
        normalized = topology.normalize_identity(identity)
        match = initialized.get(normalized)
        if match is not None:
            return match
    return next(iter(initialized.values()), None)


def _preferred_read_only_session_identities(
    root: Path,
    *,
    explicit_actor: str | None,
) -> list[str]:
    preferred: list[str] = []
    if explicit_actor:
        preferred.append(explicit_actor)
    current = _prefer_bound_session_root()
    if current is None:
        return preferred
    if topology.shared_session_id(current) != topology.shared_session_id(root):
        return preferred
    current_identity = topology.session_identity(current)
    if current_identity and not topology.is_placeholder_identity(current_identity):
        preferred.append(current_identity)
    return preferred


def _is_shared_session_root(root: Path | None) -> bool:
    if root is None:
        return False
    resolved = root.expanduser().resolve()
    return topology.parse_shared_session_root(resolved) is not None


def _explicit_session_content_root(root: Path) -> Path:
    return (session_plugin._group_session_root(root) / "content").resolve()


def _looks_like_stored_read_target(argv: list[str], explicit_session: str) -> bool:
    if argv[:1] != ["read"]:
        return False
    shared_root = _resolve_shared_explicit_session(explicit_session)
    if shared_root is None:
        return False
    try:
        from gotta.target import resolve_read_target

        resolved = resolve_read_target(
            argv[1:],
            CommonOptions(
                session_dir=str(shared_root),
                content_dir=str(_explicit_session_content_root(shared_root)),
            ),
        )
    except SystemExit:
        return False
    target = str(resolved.request.target or "").strip()
    if resolved.kind in {"artifact_locator", "artifact_name"}:
        return True
    if target.startswith("content:"):
        return True
    return len(target) == 64 and all(ch in "0123456789abcdef" for ch in target)


def _uses_shared_session_root(
    *,
    argv: list[str],
    explicit_session: str | None,
    explicit_actor: str | None,
    session_access: SessionAccessMode,
) -> bool:
    if not explicit_session or explicit_actor:
        return False
    if argv[:1] == ["session"]:
        return True
    if argv[:1] == ["notes"]:
        return True
    if session_access == "ambient":
        return _looks_like_stored_read_target(argv, explicit_session)
    return False


def _prefers_primary_actor_root(argv: list[str]) -> bool:
    if not argv:
        return False
    plugin = argv[0]
    if plugin in {"want", "goal"}:
        return True
    if plugin == "session" and len(argv) >= 2 and argv[1] == "show":
        return True
    if plugin == "actor" and len(argv) >= 2 and argv[1] == "status":
        return True
    return False


def _resolve_primary_actor_root(root: Path) -> Path | None:
    session_root = root.expanduser().resolve()
    if not _is_shared_session_root(session_root):
        return session_root
    primary = session_plugin._primary_actor_name(session_root)
    if not primary:
        return None
    return session_plugin._actor_session_dir(session_root, primary)


def _hydrate_shared_session_environment(
    root: Path,
    *,
    context_id: str,
    context_source: str,
) -> None:
    session_root = root.expanduser().resolve()
    state: dict[str, object] = {}
    primary = session_plugin._primary_actor_name(session_root) or ""
    if primary:
        primary_root = session_plugin._actor_session_dir(session_root, primary)
        state = load_state_env_at_root(primary_root)
    for key, value in state.items():
        os.environ[key] = value
    os.environ[SESSION_ENV] = str(session_root)
    os.environ[SESSION_ID_ENV] = topology.shared_session_id(session_root)
    os.environ[CONTENT_ENV] = str(_explicit_session_content_root(session_root))
    if primary:
        os.environ[SESSION_ACTOR_ENV] = primary
    else:
        os.environ.pop(SESSION_ACTOR_ENV, None)
    os.environ[CONTEXT_ACTIVE_ENV] = "1"
    os.environ[CONTEXT_ID_ENV] = context_id
    os.environ[CONTEXT_SOURCE_ENV] = context_source
    os.environ[SESSION_ACTIVATION_ENV] = "gotta"
    repo_root = str(state.get(SESSION_REPO_ENV) or "").strip()
    if repo_root:
        os.environ[SESSION_REPO_ENV] = repo_root
        venv_bin = Path(repo_root) / ".venv" / "bin"
        if venv_bin.is_dir():
            current_path = os.environ.get("PATH", "")
            os.environ["PATH"] = ":".join(
                [str(venv_bin), current_path] if current_path else [str(venv_bin)]
            )
            os.environ["VIRTUAL_ENV"] = str(venv_bin.parent)


def main(argv: list[str] | None = None) -> int:
    args = list(sys.argv[1:] if argv is None else argv)
    normalized = normalize_help_aliases(args)
    effective = _argv_without_global_flags(normalized)
    try:
        if _is_nonbinding_help(effective) or _is_version_request(effective):
            return _gotta_main(normalized)
        plugin_name = effective[0] if effective else ""
        context = current_context_binding()
        context_id = context.context_id
        context_source = context.context_source
        explicit_session = _explicit_session_arg(effective)
        explicit_actor = _explicit_actor_arg(effective)
        explicit_target = bool(explicit_session or explicit_actor)
        session_access = _session_access_mode(effective)
        auto_bootstrap = _should_auto_bootstrap_session(
            argv=effective,
            context_source=context_source,
            explicit_session=explicit_session,
            session_access=session_access,
        )
        shared_root_command = _uses_shared_session_root(
            argv=effective,
            explicit_session=explicit_session,
            explicit_actor=explicit_actor,
            session_access=session_access,
        )
        created = False
        bound_current_context = False
        init_command = (
            plugin_name == "session" and len(effective) >= 2 and effective[1] == "init"
        )
        if (
            plugin_name == "session"
            and len(effective) >= 2
            and effective[1] in {"bind"}
        ):
            return _gotta_main(normalized)
        if session_access == "none":
            return _gotta_main(normalized)
        if explicit_session:
            if init_command:
                target_identity = topology.normalize_identity(
                    _active_identity(context_id)
                )
                root = resolve_session_reference(
                    explicit_session,
                    identity=target_identity,
                    allow_missing=True,
                )
            elif shared_root_command:
                root = _resolve_shared_explicit_session(explicit_session)
            elif (
                session_access == "read"
                and not explicit_actor
                and _prefers_primary_actor_root(effective)
            ):
                shared_root = _resolve_shared_explicit_session(explicit_session)
                if _is_shared_session_root(shared_root):
                    primary_root = _resolve_primary_actor_root(shared_root)
                    if primary_root is None:
                        return die(
                            "this shared session does not resolve to one canonical actor root; "
                            "pass `--actor <actor>` explicitly"
                        )
                    root = primary_root
                else:
                    root = shared_root
            else:
                target_identity = topology.normalize_identity(
                    explicit_actor or _active_identity(context_id)
                )
                explicit_root = resolve_session_reference(
                    explicit_session,
                    identity=target_identity,
                    allow_missing=False,
                )
                if explicit_root is not None and explicit_actor:
                    resolved_actor = session_plugin._resolve_bound_actor_name(
                        explicit_root,
                        explicit_actor,
                    )
                    root = session_plugin._actor_session_dir(
                        explicit_root, resolved_actor
                    )
                elif explicit_root is not None:
                    root = explicit_root
                else:
                    if explicit_actor:
                        return die(
                            "explicit actor targeting requires an existing shared session and a bound actor"
                        )
                    root = resolve_session_reference(
                        explicit_session,
                        identity=target_identity,
                        allow_missing=True,
                    )
            if root is None:
                return die(
                    "session references must be an absolute path, a shared session id, "
                    "or an explicit <session>/<actor> session reference"
                )
        elif explicit_actor:
            current = _prefer_bound_session_root()
            if current is None:
                current = _resolve_existing_session_root(context_id, context_source)
            if current is None:
                return die(
                    "explicit actor targeting requires an existing session; run "
                    "`gotta session bind` first or pass `--session <session-id>`"
                )
            resolved_actor = session_plugin._resolve_bound_actor_name(
                current,
                explicit_actor,
            )
            root = session_plugin._actor_session_dir(current, resolved_actor)
        else:
            root = _prefer_bound_session_root()
        if root is None:
            root = _resolve_existing_session_root(context_id, context_source)
        if root is None and (session_access == "write" or auto_bootstrap):
            root, created = _bind_session_root(context_id, context_source)
            bound_current_context = True
        if root is None and session_access != "ambient":
            return die(
                "this command requires an existing session; run `gotta session bind` "
                "first or pass `--session <session-id>`"
            )
        requested_root = root
        if (
            session_access == "ambient"
            and root is not None
            and not session_is_initialized(root)
            and not shared_root_command
        ):
            if explicit_target:
                return die(
                    "ambient retrieval requires an existing initialized actor root in the "
                    "target session; bind an actor there first or pass `--actor <actor>`"
                )
            root = None
        scaffold_created = False
        if (
            auto_bootstrap
            and root is not None
            and session_access in {"read", "ambient"}
        ):
            root, scaffold_created = _ensure_scaffolded_session(
                root,
                context_id=context_id,
                context_source=context_source,
            )
        if (
            session_access == "read"
            and root is not None
            and not session_is_initialized(root)
        ):
            if not shared_root_command:
                existing = _existing_actor_root_for_session(
                    root,
                    preferred_identities=_preferred_read_only_session_identities(
                        root,
                        explicit_actor=explicit_actor,
                    ),
                )
                if existing is not None:
                    root = existing
        if session_access == "read":
            if root is None:
                return die(
                    "explicit session inspection requires an initialized actor root in the "
                    "target shared session; bind an actor there first or pass --actor"
                )
            if not session_is_initialized(root) and not shared_root_command:
                if explicit_session and not _is_shared_session_root(root):
                    return die(
                        "explicit session inspection requires an existing initialized session "
                        "at that exact root"
                    )
                return die(
                    "explicit session inspection requires an initialized actor root in the "
                    "target shared session; bind an actor there first or pass --actor"
                )
        elif session_access == "ambient":
            scaffold_created = False
        else:
            root, scaffold_created = _ensure_scaffolded_session(
                root,
                context_id=context_id,
                context_source=context_source,
            )
            if init_command and root is not None and not bound_current_context:
                root = binding_helpers.bind_root_for_context(
                    root,
                    context_id=context_id,
                    context_source=context_source,
                )
                bound_current_context = True
        created = created or scaffold_created
        original_env = os.environ.copy()
        try:
            acting_actor = resolve_actor_context(
                default_speaker=_active_identity(context_id)
            ).speaker or _active_identity(context_id)
            if root is not None:
                if shared_root_command and _is_shared_session_root(root):
                    _hydrate_shared_session_environment(
                        root,
                        context_id=context_id,
                        context_source=context_source,
                    )
                else:
                    _hydrate_environment(
                        root, context_id=context_id, context_source=context_source
                    )
                    seed_actor_context(acting_actor)
                    if explicit_actor:
                        os.environ[SESSION_ACTOR_ENV] = content_session_identity(root)
                if created:
                    print(
                        "\n".join(
                            _creation_receipt_lines(
                                root,
                                context_id=context_id,
                                context_source=context_source,
                                bound=bound_current_context,
                            )
                        ),
                        file=sys.stderr,
                    )
                warning = _actor_stop_warning(root)
                if warning:
                    print(warning, file=sys.stderr)
                elif _should_emit_actor_note_check_warning(
                    argv=effective,
                    root=root,
                    requested_root=requested_root,
                    acting_actor=acting_actor,
                    explicit_actor=explicit_actor,
                ):
                    pulse_warning = _actor_note_check_warning(root)
                    if pulse_warning:
                        print(pulse_warning, file=sys.stderr)
            elif session_access == "ambient":
                os.environ[SUPPRESS_MATERIALIZATION_ENV] = "1"
                os.environ[_AMBIENT_SESSIONLESS_ENV] = "1"
            return _gotta_main(normalized)
        finally:
            os.environ.clear()
            os.environ.update(original_env)
    except BrokenPipeError:
        _silence_stdout()
        return 0
