#!/usr/bin/env python3
"""Resolve and execute Cargo commands in bounded, signature-stable build lanes.

This module intentionally uses only the Python standard library.  It is also
imported by build_storage.py, so lane identity and sentinel validation have one
implementation.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import os
import re
import shlex
import shutil
import subprocess
import sys
import tempfile
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Iterable, Mapping, Sequence

try:
    import tomllib
except ModuleNotFoundError as error:  # pragma: no cover - Python < 3.11
    raise SystemExit("cargo_lane.py requires Python 3.11 or newer") from error


SCHEMA_VERSION = 1
SENTINEL_NAME = ".blacksite-cargo-lane.json"
LANE_RE = re.compile(r"^[a-z0-9][a-z0-9-]{0,63}$")
SEPARATE_BUILD_DIR_MINIMUM = (1, 91, 0)

DEFAULT_BUILD_STORAGE: dict[str, Any] = {
    "cache_root": "cargo-cache-home",
    "workspace_partition": "canonical-path-hash",
    "dev_target_dir": "target",
    "exceptional_target_root": "target/lanes",
    "persistent_lane": "dev",
    "candidate_lane": "candidate",
    "hot_reload_lane": "hot-reload",
    "full_debug_lane": "full-debug",
    "package_lane": "package",
}


class LaneError(RuntimeError):
    """A lane cannot be resolved or safely reused."""


def utc_now() -> str:
    return datetime.now(timezone.utc).isoformat(timespec="seconds")


def canonical_path(path: Path | str) -> Path:
    return Path(path).expanduser().resolve(strict=False)


def workspace_root(start: Path | None = None) -> Path:
    override = os.environ.get("BLACKSITE_WORKSPACE_ROOT")
    if override:
        candidate = canonical_path(override)
    elif start is not None:
        candidate = canonical_path(start)
    else:
        candidate = canonical_path(Path(__file__).parents[2])

    for current in (candidate, *candidate.parents):
        if (current / "Cargo.toml").is_file() and (current / ".git").exists():
            return current
    raise LaneError(f"could not locate a Cargo workspace from {candidate}")


def workspace_hash(root: Path) -> str:
    return hashlib.sha256(os.fsencode(str(canonical_path(root)))).hexdigest()[:20]


def cargo_home() -> Path:
    return canonical_path(os.environ.get("CARGO_HOME", Path.home() / ".cargo"))


def load_workflow(root: Path) -> dict[str, Any]:
    path = root / ".codex" / "workflow.toml"
    document: dict[str, Any] = {}
    if path.is_file():
        with path.open("rb") as handle:
            loaded = tomllib.load(handle)
        if not isinstance(loaded, dict):
            raise LaneError(f"workflow config is not a TOML table: {path}")
        document = loaded

    storage = dict(DEFAULT_BUILD_STORAGE)
    configured = document.get("build_storage", {})
    if configured:
        if not isinstance(configured, dict):
            raise LaneError("[build_storage] must be a TOML table")
        storage.update(configured)

    verification = document.get("verification", {})
    if isinstance(verification, dict):
        for source, destination in (
            ("persistent_lane", "persistent_lane"),
            ("candidate_lane", "candidate_lane"),
            ("hot_reload_lane", "hot_reload_lane"),
            ("full_debug_lane", "full_debug_lane"),
            ("package_lane", "package_lane"),
        ):
            if source in verification:
                storage[destination] = verification[source]

    document["build_storage"] = storage
    return document


def _configured_path(root: Path, value: str, *, base: Path | None = None) -> Path:
    expanded = Path(os.path.expandvars(os.path.expanduser(value)))
    if expanded.is_absolute():
        return canonical_path(expanded)
    return canonical_path((base or root) / expanded)


def cache_root(root: Path, workflow: Mapping[str, Any]) -> Path:
    override = os.environ.get("BLACKSITE_BUILD_CACHE_ROOT")
    if override:
        return canonical_path(override)

    storage = workflow["build_storage"]
    configured = str(storage.get("cache_root", "cargo-cache-home"))
    if configured == "cargo-cache-home":
        return cargo_home() / "blacksite-build"
    if configured == "workspace-parent-cache":
        return canonical_path(root.parent / ".blacksite-build-cache")
    return _configured_path(root, configured)


def parse_version(text: str) -> tuple[int, int, int]:
    match = re.search(r"\b(\d+)\.(\d+)\.(\d+)", text)
    if not match:
        return (0, 0, 0)
    return tuple(int(part) for part in match.groups())  # type: ignore[return-value]


def _capture(command: Sequence[str]) -> str:
    try:
        completed = subprocess.run(
            command,
            check=False,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
        )
    except OSError as error:
        return f"unavailable: {error}"
    return completed.stdout.strip()


def toolchain_info() -> dict[str, Any]:
    cargo_text = _capture(["cargo", "-Vv"])
    rustc_text = _capture(["rustc", "-Vv"])
    host = "unknown"
    for line in rustc_text.splitlines():
        if line.startswith("host:"):
            host = line.partition(":")[2].strip()
            break
    version = parse_version(cargo_text)
    return {
        "cargo": cargo_text.splitlines()[0] if cargo_text else "unavailable",
        "cargo_version": ".".join(str(part) for part in version),
        "rustc": rustc_text.splitlines()[0] if rustc_text else "unavailable",
        "host": host,
        "separate_build_dir": version >= SEPARATE_BUILD_DIR_MINIMUM,
    }


def _repo_target_config(root: Path, target: str) -> dict[str, Any]:
    path = root / ".cargo" / "config.toml"
    result: dict[str, Any] = {"linker": None, "rustflags": []}
    if path.is_file():
        with path.open("rb") as handle:
            config = tomllib.load(handle)
        target_table = config.get("target", {})
        if isinstance(target_table, dict):
            values = target_table.get(target, {})
            if isinstance(values, dict):
                result["linker"] = values.get("linker")
                flags = values.get("rustflags", [])
                if isinstance(flags, list):
                    result["rustflags"] = [str(flag) for flag in flags]
    encoded_flags = os.environ.get("CARGO_ENCODED_RUSTFLAGS")
    if encoded_flags:
        result["rustflags"] = [flag for flag in encoded_flags.split("\x1f") if flag]
        result["rustflags_source"] = "CARGO_ENCODED_RUSTFLAGS"
    elif os.environ.get("RUSTFLAGS"):
        result["rustflags"] = shlex.split(os.environ["RUSTFLAGS"])
        result["rustflags_source"] = "environment"
    else:
        result["rustflags_source"] = str(path) if path.is_file() else "cargo-default"
    result["rustc_wrapper"] = os.environ.get("RUSTC_WRAPPER")
    linker_variable = "CARGO_TARGET_" + target.upper().replace("-", "_") + "_LINKER"
    if os.environ.get(linker_variable):
        result["linker"] = os.environ[linker_variable]
    return result


def validate_lane_name(lane: str) -> str:
    if not LANE_RE.fullmatch(lane):
        raise LaneError(
            f"invalid lane {lane!r}; use lowercase letters, digits, and hyphens"
        )
    return lane


def profile_directory(profile: str) -> str:
    if profile in {"dev", "test"}:
        return "debug"
    if profile in {"release", "bench"}:
        return "release"
    return profile


@dataclass(frozen=True)
class CommandContext:
    profile: str
    profile_dir: str
    feature_signature: str
    cargo_subcommand: str | None
    target_triple: str | None


PROJECT_ALIAS_SUBCOMMANDS = {
    "bake-navigation",
    "package-project",
    "process-assets",
    "upgrade-project",
    "validate-levels",
    "validate-samples",
}


def command_context(command: Sequence[str], lane: str) -> CommandContext:
    args = list(command)
    cargo_index: int | None = None
    for index, value in enumerate(args[:2]):
        if Path(value).name == "cargo":
            cargo_index = index
            break

    cargo_args = args[cargo_index + 1 :] if cargo_index is not None else []
    subcommand = next((arg for arg in cargo_args if not arg.startswith("-")), None)
    # Arguments following Blacksite's Cargo aliases are forwarded to the xtask
    # binary by the alias's trailing `--`. They are not Cargo options. In
    # particular, `cargo package-project --profile package-qa` selects the
    # package manifest profile and must not repartition the Cargo build lane.
    cargo_options = (
        cargo_args[: cargo_args.index(subcommand) + 1]
        if subcommand in PROJECT_ALIAS_SUBCOMMANDS
        else cargo_args
    )
    if "--release" in cargo_options:
        profile = "release"
    else:
        profile = "test" if subcommand == "test" else "dev"
        for index, value in enumerate(cargo_options):
            if value.startswith("--profile="):
                profile = value.partition("=")[2]
            elif value == "--profile" and index + 1 < len(cargo_options):
                profile = cargo_options[index + 1]

    features: set[str] = set()
    all_features = "--all-features" in cargo_options
    no_default = "--no-default-features" in cargo_options
    for index, value in enumerate(cargo_options):
        raw: str | None = None
        if value.startswith("--features="):
            raw = value.partition("=")[2]
        elif value.startswith("-F") and value != "-F":
            raw = value[2:]
        elif value in {"--features", "-F"} and index + 1 < len(cargo_options):
            raw = cargo_options[index + 1]
        if raw:
            features.update(part for part in re.split(r"[ ,]+", raw) if part)

    target_triple: str | None = None
    for index, value in enumerate(cargo_options):
        if value.startswith("--target="):
            target_triple = value.partition("=")[2]
        elif value == "--target" and index + 1 < len(cargo_options):
            target_triple = cargo_options[index + 1]

    if all_features:
        feature_signature = "all-features"
    else:
        parts = ["no-default" if no_default else "default"]
        if features:
            parts.append("features=" + ",".join(sorted(features)))
        feature_signature = ";".join(parts)

    # These disposable lanes intentionally contain a bounded family of
    # package-specific feature sets. Hot reload needs the editor's
    # `dev,hot-reload` build and `game_hot`'s `dylib` build in one isolated
    # runtime lane; package aliases likewise expand to different internal
    # features. They must never spill into the persistent default-feature lane.
    if lane == "hot-reload":
        if all_features:
            raise LaneError("all-features belongs in the candidate lane")
        feature_signature = "hot-reload-family"
    elif lane == "package":
        feature_signature = "package-family"

    if lane == "dev" and (all_features or "hot-reload" in features):
        raise LaneError(
            "the persistent dev lane cannot be used for all-features or hot-reload"
        )
    if lane == "candidate" and subcommand is not None and not all_features:
        raise LaneError("the candidate lane requires --all-features")

    return CommandContext(
        profile=profile,
        profile_dir=profile_directory(profile),
        feature_signature=feature_signature,
        cargo_subcommand=subcommand,
        target_triple=target_triple,
    )


@dataclass(frozen=True)
class LaneLayout:
    workspace: str
    workspace_hash: str
    lane: str
    mode: str
    cache_root: str
    lane_root: str
    target_dir: str
    build_dir: str
    profile: str
    profile_dir: str
    runtime_deps: str
    feature_signature: str
    toolchain: dict[str, Any]
    linker: str | None
    rustflags: list[str]
    rustflags_source: str
    rustc_wrapper: str | None
    target_triple: str


def lane_layout(
    root: Path,
    lane: str,
    workflow: Mapping[str, Any],
    command: Sequence[str] = (),
    *,
    toolchain: Mapping[str, Any] | None = None,
) -> LaneLayout:
    lane = validate_lane_name(lane)
    root = canonical_path(root)
    info = dict(toolchain or toolchain_info())
    context = command_context(command, lane)
    storage = workflow["build_storage"]
    cache = canonical_path(cache_root(root, workflow))
    partition = cache / workspace_hash(root)
    lane_root = partition / lane
    separate = bool(info.get("separate_build_dir"))
    effective_target = context.target_triple or str(info.get("host", "unknown"))
    if (
        context.target_triple
        and context.target_triple != info.get("host")
        and not lane.startswith("cross-")
    ):
        raise LaneError("cross-target Cargo commands require a cross-* disposable lane")

    persistent = str(storage.get("persistent_lane", "dev"))
    if separate:
        if lane == persistent:
            target = _configured_path(
                root, str(storage.get("dev_target_dir", "target"))
            )
        else:
            exceptional = _configured_path(
                root, str(storage.get("exceptional_target_root", "target/lanes"))
            )
            target = exceptional / lane
        build = lane_root
        mode = "separate-build-dir"
    else:
        target = lane_root / "target"
        build = target
        mode = "external-target-dir-fallback"

    target_config = _repo_target_config(root, effective_target)
    profile_path = Path(context.profile_dir)
    if context.target_triple:
        profile_path = Path(context.target_triple) / profile_path
    return LaneLayout(
        workspace=str(root),
        workspace_hash=workspace_hash(root),
        lane=lane,
        mode=mode,
        cache_root=str(cache),
        lane_root=str(lane_root),
        target_dir=str(target),
        build_dir=str(build),
        profile=context.profile,
        profile_dir=context.profile_dir,
        runtime_deps=str(build / profile_path / "deps"),
        feature_signature=context.feature_signature,
        toolchain=info,
        linker=target_config.get("linker"),
        rustflags=list(target_config.get("rustflags", [])),
        rustflags_source=str(target_config.get("rustflags_source")),
        rustc_wrapper=target_config.get("rustc_wrapper"),
        target_triple=effective_target,
    )


def lane_environment(layout: LaneLayout) -> dict[str, str]:
    environment = {
        "CARGO_TARGET_DIR": layout.target_dir,
        "BLACKSITE_CARGO_LANE": layout.lane,
        "BLACKSITE_WORKSPACE_HASH": layout.workspace_hash,
    }
    if layout.mode == "separate-build-dir":
        environment["CARGO_BUILD_BUILD_DIR"] = layout.build_dir
    if layout.lane in {"candidate", "package", "full-debug"}:
        environment["CARGO_INCREMENTAL"] = "0"
    if layout.lane == "full-debug":
        environment["CARGO_PROFILE_DEV_DEBUG"] = "full"
        environment["CARGO_PROFILE_TEST_DEBUG"] = "full"
    return environment


def sentinel_path(path: Path) -> Path:
    return path / SENTINEL_NAME


def read_sentinel(path: Path) -> dict[str, Any] | None:
    marker = sentinel_path(path)
    if not marker.is_file() or marker.is_symlink():
        return None
    try:
        with marker.open("r", encoding="utf-8") as handle:
            value = json.load(handle)
    except (OSError, json.JSONDecodeError):
        return None
    return value if isinstance(value, dict) else None


def validate_sentinel(
    path: Path,
    document: Mapping[str, Any],
    *,
    root: Path,
    lane: str,
    role: str,
) -> list[str]:
    errors: list[str] = []
    expected = {
        "schema_version": SCHEMA_VERSION,
        "workspace": str(canonical_path(root)),
        "workspace_hash": workspace_hash(root),
        "lane": lane,
        "role": role,
        "path": str(canonical_path(path)),
    }
    for key, value in expected.items():
        if document.get(key) != value:
            errors.append(f"{key}: expected {value!r}, found {document.get(key)!r}")
    return errors


def _signature(layout: LaneLayout) -> dict[str, Any]:
    # The persistent development and candidate lanes intentionally serve both
    # Cargo's `dev` and `test` profiles. Cargo fingerprints those profiles
    # independently, while keeping them in one bounded ordinary/candidate lane
    # avoids multiplying the Bevy dependency graph into permanent caches.
    profile_signature = "dev-test" if layout.profile in {"dev", "test"} else layout.profile
    return {
        "toolchain": layout.toolchain,
        "target_triple": layout.target_triple,
        "linker": layout.linker,
        "rustflags": layout.rustflags,
        "rustc_wrapper": layout.rustc_wrapper,
        "feature_signature": layout.feature_signature,
        "profile_signature": profile_signature,
        "mode": layout.mode,
        "lane_environment": lane_environment(layout),
    }


def _signature_compatible(existing: object, requested: Mapping[str, Any]) -> bool:
    if existing == requested:
        return True
    if not isinstance(existing, Mapping):
        return False
    previous = dict(existing)
    current = dict(requested)
    previous_profile = previous.pop("profile_signature", None)
    current_profile = current.pop("profile_signature", None)
    return (
        previous == current
        and previous_profile in {"dev", "test"}
        and current_profile == "dev-test"
    )


def _atomic_json(path: Path, document: Mapping[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    descriptor, temporary = tempfile.mkstemp(
        prefix=f".{path.name}.", suffix=".tmp", dir=path.parent
    )
    try:
        with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
            json.dump(document, handle, indent=2, sort_keys=True)
            handle.write("\n")
            handle.flush()
            os.fsync(handle.fileno())
        os.replace(temporary, path)
    finally:
        try:
            os.unlink(temporary)
        except FileNotFoundError:
            pass


def _prepare_marker(
    path: Path, layout: LaneLayout, role: str, *, allow_create: bool
) -> dict[str, Any]:
    if path.is_symlink():
        raise LaneError(f"refusing symlinked lane path: {path}")
    if allow_create:
        path.mkdir(parents=True, exist_ok=True)
    if not path.is_dir():
        raise LaneError(f"lane path does not exist: {path}")

    existing = read_sentinel(path)
    now = utc_now()
    signature = _signature(layout)
    if existing is not None:
        errors = validate_sentinel(
            path, existing, root=Path(layout.workspace), lane=layout.lane, role=role
        )
        if errors:
            raise LaneError(
                f"invalid lane sentinel at {path}: " + "; ".join(errors)
            )
        if not _signature_compatible(existing.get("signature"), signature):
            raise LaneError(
                "lane signature changed; select the correct exceptional lane or reset "
                f"the whole lane after a dry run: {layout.lane}"
            )
        document = dict(existing)
        document["signature"] = signature
        document["last_use"] = now
        document["last_profile"] = layout.profile
    else:
        document = {
            "schema_version": SCHEMA_VERSION,
            "workspace": layout.workspace,
            "workspace_hash": layout.workspace_hash,
            "lane": layout.lane,
            "role": role,
            "path": str(canonical_path(path)),
            "target_dir": layout.target_dir,
            "build_dir": layout.build_dir,
            "signature": signature,
            "created_at": now,
            "last_use": now,
            "last_profile": layout.profile,
        }
    _atomic_json(sentinel_path(path), document)
    return document


def prepare_lane(layout: LaneLayout) -> None:
    lane_root = Path(layout.lane_root)
    _prepare_marker(lane_root, layout, "build", allow_create=True)

    target = Path(layout.target_dir)
    workspace_target = Path(layout.workspace) / "target"
    if target != workspace_target and target != Path(layout.build_dir):
        _prepare_marker(target, layout, "target", allow_create=True)


def require_prepared_lane(layout: LaneLayout) -> None:
    lane_root = Path(layout.lane_root)
    marker = read_sentinel(lane_root)
    if marker is None:
        raise LaneError(
            f"lane {layout.lane!r} has not been built through cargo_lane.py"
        )
    errors = validate_sentinel(
        lane_root,
        marker,
        root=Path(layout.workspace),
        lane=layout.lane,
        role="build",
    )
    if errors:
        raise LaneError("invalid prepared lane: " + "; ".join(errors))


def layout_document(layout: LaneLayout) -> dict[str, Any]:
    document = asdict(layout)
    document["environment"] = lane_environment(layout)
    document["sentinel"] = str(sentinel_path(Path(layout.lane_root)))
    return document


def _shell_environment(values: Mapping[str, str]) -> str:
    return "\n".join(
        f"export {name}={shlex.quote(value)}" for name, value in sorted(values.items())
    )


def _run_command(layout: LaneLayout, command: Sequence[str], *, dry_run: bool) -> int:
    if not command:
        raise LaneError("missing command after --")
    if dry_run:
        print(
            json.dumps(
                {"layout": layout_document(layout), "command": list(command)},
                indent=2,
                sort_keys=True,
            )
        )
        return 0
    prepare_lane(layout)
    environment = os.environ.copy()
    environment.update(lane_environment(layout))
    return subprocess.run(command, cwd=layout.workspace, env=environment).returncode


def _resolve_runtime_command(layout: LaneLayout, command: Sequence[str]) -> list[str]:
    if not command:
        raise LaneError("missing runtime command after --")
    resolved = list(command)
    executable = Path(resolved[0])
    if not executable.is_absolute():
        parts = executable.parts
        if parts and parts[0] == "target":
            executable = Path(layout.target_dir).joinpath(*parts[1:])
        elif "/" in resolved[0]:
            executable = Path(layout.workspace) / executable
    resolved[0] = str(canonical_path(executable)) if "/" in str(executable) else str(executable)
    return resolved


def _run_runtime(layout: LaneLayout, command: Sequence[str], *, dry_run: bool) -> int:
    require_prepared_lane(layout)
    resolved = _resolve_runtime_command(layout, command)
    environment = os.environ.copy()
    dependencies = layout.runtime_deps
    existing = environment.get("LD_LIBRARY_PATH")
    environment["LD_LIBRARY_PATH"] = (
        dependencies if not existing else dependencies + os.pathsep + existing
    )
    environment.update(lane_environment(layout))
    if dry_run:
        print(
            json.dumps(
                {
                    "layout": layout_document(layout),
                    "command": resolved,
                    "LD_LIBRARY_PATH": environment["LD_LIBRARY_PATH"],
                },
                indent=2,
                sort_keys=True,
            )
        )
        return 0
    os.chdir(layout.workspace)
    os.execvpe(resolved[0], resolved, environment)
    raise AssertionError("os.execvpe unexpectedly returned")  # pragma: no cover


def _self_test() -> dict[str, Any]:
    checks: list[str] = []
    assert parse_version("cargo 1.97.0 (abc)") == (1, 97, 0)
    checks.append("cargo-version-parsing")
    assert profile_directory("test") == "debug"
    assert profile_directory("package-qa") == "package-qa"
    checks.append("profile-directory-mapping")
    try:
        validate_lane_name("../escape")
    except LaneError:
        checks.append("lane-name-traversal-rejected")
    else:  # pragma: no cover
        raise AssertionError("unsafe lane name accepted")

    with tempfile.TemporaryDirectory(prefix="blacksite-lane-self-test-") as temporary:
        root = Path(temporary) / "workspace"
        (root / ".git").mkdir(parents=True)
        (root / "Cargo.toml").write_text("[workspace]\nmembers = []\n", encoding="utf-8")
        workflow = {"build_storage": dict(DEFAULT_BUILD_STORAGE)}
        workflow["build_storage"]["cache_root"] = str(Path(temporary) / "cache")
        fake_toolchain = {
            "cargo": "cargo 1.97.0",
            "cargo_version": "1.97.0",
            "rustc": "rustc 1.97.0",
            "host": "x86_64-unknown-linux-gnu",
            "separate_build_dir": True,
        }
        layout = lane_layout(root, "dev", workflow, ["cargo", "check"], toolchain=fake_toolchain)
        prepare_lane(layout)
        marker = read_sentinel(Path(layout.lane_root))
        assert marker is not None
        assert not validate_sentinel(
            Path(layout.lane_root), marker, root=root, lane="dev", role="build"
        )
        checks.append("sentinel-round-trip")
        assert Path(layout.target_dir) == root / "target"
        assert Path(layout.build_dir) != Path(layout.target_dir)
        checks.append("separate-build-dir-layout")
        test_layout = lane_layout(
            root, "dev", workflow, ["cargo", "test"], toolchain=fake_toolchain
        )
        prepare_lane(test_layout)
        assert read_sentinel(Path(layout.lane_root))["signature"]["profile_signature"] == "dev-test"
        checks.append("dev-and-test-share-persistent-lane")
        hot_editor = lane_layout(
            root,
            "hot-reload",
            workflow,
            ["cargo", "build", "-p", "editor", "--features", "dev,hot-reload"],
            toolchain=fake_toolchain,
        )
        hot_game = lane_layout(
            root,
            "hot-reload",
            workflow,
            ["cargo", "build", "-p", "game_hot", "--features", "dylib"],
            toolchain=fake_toolchain,
        )
        assert hot_editor.feature_signature == hot_game.feature_signature == "hot-reload-family"
        checks.append("hot-reload-feature-family-isolated")
        package = lane_layout(
            root,
            "package",
            workflow,
            ["cargo", "package-project", "--project", ".", "--profile", "package-qa"],
            toolchain=fake_toolchain,
        )
        assert package.profile == "dev"
        assert package.feature_signature == "package-family"
        checks.append("project-alias-options-not-cargo-options")

        fallback_toolchain = dict(fake_toolchain)
        fallback_toolchain["cargo_version"] = "1.90.0"
        fallback_toolchain["separate_build_dir"] = False
        fallback = lane_layout(
            root,
            "candidate",
            workflow,
            ["cargo", "test", "--all-features"],
            toolchain=fallback_toolchain,
        )
        assert fallback.mode == "external-target-dir-fallback"
        assert Path(fallback.target_dir) == Path(fallback.lane_root) / "target"
        assert fallback.build_dir == fallback.target_dir
        checks.append("external-target-dir-fallback")

        cross = lane_layout(
            root,
            "cross-wasm",
            workflow,
            ["cargo", "check", "--target", "wasm32-unknown-unknown"],
            toolchain=fake_toolchain,
        )
        assert cross.target_triple == "wasm32-unknown-unknown"
        assert "wasm32-unknown-unknown" in cross.runtime_deps
        checks.append("cross-target-partition")

    return {"ok": True, "checks": checks}


def _command_tail(values: Iterable[str]) -> list[str]:
    result = list(values)
    if result and result[0] == "--":
        result.pop(0)
    return result


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--workspace", type=Path, help="workspace root override")
    subparsers = parser.add_subparsers(dest="action", required=True)

    env_parser = subparsers.add_parser("env", help="describe a lane environment")
    env_parser.add_argument("lane")
    env_parser.add_argument("--json", action="store_true")

    exec_parser = subparsers.add_parser("exec", help="execute a command in a lane")
    exec_parser.add_argument("lane")
    exec_parser.add_argument("--dry-run", action="store_true")
    exec_parser.add_argument("command", nargs=argparse.REMAINDER)

    run_parser = subparsers.add_parser("run", help="run an existing lane artifact")
    run_parser.add_argument("lane")
    run_parser.add_argument("--dry-run", action="store_true")
    run_parser.add_argument("command", nargs=argparse.REMAINDER)

    subparsers.add_parser("self-test", help="run temp-directory safety checks")
    return parser


def main(argv: Sequence[str] | None = None) -> int:
    arguments = build_parser().parse_args(argv)
    if arguments.action == "self-test":
        print(json.dumps(_self_test(), indent=2, sort_keys=True))
        return 0

    try:
        root = workspace_root(arguments.workspace)
        workflow = load_workflow(root)
        raw_command = list(getattr(arguments, "command", []))
        # argparse.REMAINDER intentionally preserves arbitrary child-command
        # flags. Accept our one wrapper flag after the lane as well as before it.
        if arguments.action in {"exec", "run"} and "--dry-run" in raw_command:
            separator = raw_command.index("--") if "--" in raw_command else len(raw_command)
            if raw_command.index("--dry-run") < separator:
                arguments.dry_run = True
                raw_command.remove("--dry-run")
        command = _command_tail(raw_command)
        layout = lane_layout(root, arguments.lane, workflow, command)

        if arguments.action == "env":
            document = layout_document(layout)
            if arguments.json:
                print(json.dumps(document, indent=2, sort_keys=True))
            else:
                print(_shell_environment(document["environment"]))
            return 0
        if arguments.action == "exec":
            return _run_command(layout, command, dry_run=arguments.dry_run)
        if arguments.action == "run":
            return _run_runtime(layout, command, dry_run=arguments.dry_run)
    except LaneError as error:
        print(f"cargo-lane: {error}", file=sys.stderr)
        return 2
    return 2


if __name__ == "__main__":
    raise SystemExit(main())
