"""Which directory ChatGPT is allowed to see, and where inside it a command may run."""

from __future__ import annotations

from pathlib import Path

HOME = Path.home()

# Too broad to expose: the workspace may not be one of these, nor an ancestor of one.
# `/` and `$HOME` are excluded from the inside-of check because every legitimate
# project directory is inside them.
TOO_BROAD = (Path("/"), HOME)

# Credentials, agent state, machine configuration, and the privileged helpers on
# the operator's PATH. The workspace may neither BE one of these, nor CONTAIN
# one, nor sit INSIDE one.
PROTECTED = (
    HOME / ".ssh",
    HOME / ".gnupg",
    HOME / ".aws",
    HOME / ".config",
    HOME / ".claude",
    HOME / ".codex",
    HOME / ".overdeck",
    HOME / ".local" / "bin",
    HOME / ".local" / "share" / "overdeck",
    HOME / ".local" / "state" / "overdeck",
)


def _is_within(child: Path, parent: Path) -> bool:
    return child == parent or parent in child.parents


def _protected_paths() -> list[Path]:
    """Both the name and what it points at. Several of these are symlinks into
    `~/.local/state`, and comparing an unresolved name against a resolved
    workspace never matches — the directory would be exposed."""
    paths: list[Path] = []
    for protected in PROTECTED:
        for path in (protected, protected.resolve()):
            if path not in paths:
                paths.append(path)
    return paths


def _refuse_sensitive(path: Path, too_broad_hint: str) -> None:
    for broad in TOO_BROAD:
        if _is_within(broad, path):
            raise ValueError(f"refusing to expose {path}: {too_broad_hint}")
    for protected in _protected_paths():
        if path == protected:
            raise ValueError(f"refusing to expose {path}: it holds credentials or agent state")
        if _is_within(protected, path):
            raise ValueError(f"refusing to expose {path}: it contains {protected}")
        if _is_within(path, protected):
            raise ValueError(f"refusing to expose {path}: it is inside {protected}")


def resolve_workspace(raw: str) -> Path:
    """The one directory the bridge exposes. Fail closed on anything sensitive."""
    path = Path(raw).expanduser().resolve()
    if not path.is_dir():
        raise ValueError(f"workspace is not a directory: {path}")
    _refuse_sensitive(path, "pick a single project directory")
    return path


def resolve_readable_root(raw: str) -> Path:
    """An extra directory mounted read-only inside the jail. Held to the same
    refusals as the workspace: a readable root that is, contains, or sits inside a
    protected directory hands over exactly what the workspace check refuses."""
    path = Path(raw).expanduser().resolve()
    if not path.is_dir():
        raise ValueError(f"readable root is not a directory: {path}")
    _refuse_sensitive(path, "name the toolchain directory, not a tree that holds it")
    return path


def resolve_cwd(workspace: Path, raw: str | None) -> Path:
    """Where one command runs. Symlinks are resolved before the containment check,
    so a link planted inside the workspace cannot point a command out of it."""
    if raw is None:
        return workspace
    candidate = Path(raw)
    path = (workspace / candidate if not candidate.is_absolute() else candidate).resolve()
    if not _is_within(path, workspace):
        raise ValueError(f"cwd escapes the workspace: {path} is not under {workspace}")
    if not path.is_dir():
        raise ValueError(f"cwd is not a directory: {path}")
    return path
