#!/usr/bin/env python3
"""Profile-aware helpers for scripts/launch.sh.

This is intentionally a narrow CLI bridge: bash keeps the user-facing wizard,
while Python owns profile lookups and fits() diagnostics.
"""

from __future__ import annotations

import argparse
import logging
import os
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[3]
if str(REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(REPO_ROOT))

os.environ.setdefault("CLUB3090_LOG_LEVEL", "ERROR")

from scripts.lib.profiles.compat import (  # noqa: E402
    TOPOLOGY_ADVISORY,
    FitsResult,
    ProfileError,
    TopologyClass,
    classify_hardware_topology,
    fits,
    load_profiles,
    to_compose_name,
)
from scripts.lib.profiles.compose_registry import COMPOSE_REGISTRY  # noqa: E402


class LaunchCompatError(Exception):
    """User-facing launch compatibility failure."""


def _quiet_compat_logger() -> None:
    logger = logging.getLogger("compat")
    logger.setLevel(logging.ERROR)
    logger.propagate = False


def _normalize_name(name: str) -> str:
    normalized = name.lower()
    for token in ("nvidia", "geforce", "gpu"):
        normalized = normalized.replace(token, " ")
    return " ".join(normalized.replace("_", " ").replace("-", " ").split())


def _hardware_id_from_gpu(name: str, mem_mib: int, sm: float) -> str:
    normalized = _normalize_name(name)
    vram_gb = round(mem_mib / 1024)

    aliases = (
        # RTX PRO 6000 Blackwell reports as "RTX PRO 6000 Blackwell" -> normalizes
        # to "rtx pro 6000 blackwell" (PRO before 6000), so match "pro 6000".
        # ("6000" alone is avoided — it would swallow the sm_89 "RTX 6000 Ada".)
        ("rtx pro 6000", "rtx-6000-pro-blackwell"),
        ("pro 6000", "rtx-6000-pro-blackwell"),
        # DGX Spark's GB10 superchip reports as "GB10" / "NVIDIA GB10".
        ("dgx spark", "dgx-spark"),
        ("gb10", "dgx-spark"),
        ("rtx 3090 ti", "rtx-3090-ti"),
        ("3090 ti", "rtx-3090-ti"),
        ("rtx 3090", "rtx-3090"),
        ("3090", "rtx-3090"),
        ("rtx 4090", "rtx-4090"),
        ("4090", "rtx-4090"),
        ("rtx 5090", "rtx-5090"),
        ("5090", "rtx-5090"),
        ("rtx a5000", "rtx-a5000"),
        ("a5000", "rtx-a5000"),
        ("rtx 3060", "rtx-3060-12gb"),
        ("3060", "rtx-3060-12gb"),
        ("a100", "a100-40gb"),
        ("h100", "h100-80gb"),
    )
    for needle, hardware_id in aliases:
        if needle in normalized:
            return hardware_id

    # sm >= 12 Blackwell family, split by SM then VRAM (name aliases above are
    # the primary signal; these are the fallback when the name string is odd):
    #   GB10 / DGX Spark = sm_121 (unified 128 GB)
    #   RTX PRO 6000 Blackwell = sm_120, 96 GB
    #   RTX 5090 = sm_120, 32 GB
    if 12.05 <= sm <= 12.2:
        return "dgx-spark"
    if sm >= 12 and vram_gb >= 64:
        return "rtx-6000-pro-blackwell"
    if sm >= 12 and vram_gb >= 24:
        return "rtx-5090"
    if sm >= 9 and vram_gb >= 80:
        return "h100-80gb"
    if sm >= 8.9 and vram_gb >= 24:
        return "rtx-4090"
    if 8.55 <= sm <= 8.65 and vram_gb >= 24:
        return "rtx-3090"
    if 7.9 <= sm <= 8.1 and vram_gb >= 40:
        return "a100-40gb"
    if 8.55 <= sm <= 8.65 and 11 <= vram_gb <= 13:
        return "rtx-3060-12gb"
    raise LaunchCompatError(
        f"could not map GPU `{name}` ({vram_gb} GB, sm_{sm:g}) to a hardware profile"
    )


def _parse_gpu_specs(value: str, profiles) -> list:
    hardware = []
    for raw in value.split(";"):
        raw = raw.strip()
        if not raw:
            continue
        try:
            _idx, name, mem_mib, sm = raw.split("|", 3)
        except ValueError as exc:
            raise LaunchCompatError(f"invalid --gpu-spec entry `{raw}`") from exc
        hardware_id = _hardware_id_from_gpu(name, int(mem_mib), float(sm))
        try:
            hardware.append(profiles.hardware[hardware_id])
        except KeyError as exc:
            raise LaunchCompatError(f"hardware profile `{hardware_id}` is not installed") from exc
    if not hardware:
        raise LaunchCompatError("no GPU specs were provided for profile validation")
    return hardware


def _parse_gpu_specs_with_indices(value: str, profiles) -> list[tuple[str, object]]:
    hardware = []
    for raw in value.split(";"):
        raw = raw.strip()
        if not raw:
            continue
        try:
            idx, name, mem_mib, sm = raw.split("|", 3)
        except ValueError as exc:
            raise LaunchCompatError(f"invalid --gpu-spec entry `{raw}`") from exc
        hardware_id = _hardware_id_from_gpu(name, int(mem_mib), float(sm))
        try:
            hardware.append((idx, profiles.hardware[hardware_id]))
        except KeyError as exc:
            raise LaunchCompatError(f"hardware profile `{hardware_id}` is not installed") from exc
    if not hardware:
        raise LaunchCompatError("no GPU specs were provided for topology classification")
    return hardware


def _engine_family(engine_type: str) -> str:
    return "llamacpp" if engine_type == "llama.cpp" else engine_type


def _entry_objects(entry: dict, profiles):
    drafter = profiles.drafters[entry["drafter"]] if entry.get("drafter") else None
    return (
        profiles.models[entry["model"]],
        profiles.workloads[entry["workload"]],
        profiles.engines[entry["engine"]],
        drafter,
    )


# Non-vLLM docker-image engines → the compose env var their image is injected as.
# (vLLM is special-cased above: VLLM_IMAGE / VLLM_NIGHTLY_SHA.)
_ENGINE_IMAGE_ENV = {"beellama-local": "BEELLAMA_IMAGE"}


# --- #246 Phase 1: arch-aware KV dtype injection (pilot) ---------------------
# The launchers export KV_CACHE_DTYPE for these slugs when the detected cards'
# hardware profiles declare a different `kv_format_default.balanced` than the
# variant's registry kv_format. Expand this set only after the cross-rig A/B
# (issue #246 acceptance: >=15% on a volunteer 4090/5090, else close-with-data).
ARCH_KV_PILOT_VARIANTS = frozenset({"vllm/dual", "vllm/minimal"})

# The ONLY substitutions Phase 1 may make. Keyed by the variant's registry
# kv_format; values are the hardware-profile targets allowed to replace it.
# fp8_e5m2 -> fp8_e4m3 is the native-FP8-compute swap for sm_89+ cards.
# Nothing else is injectable: 3090-class profiles declare balanced=fp8_e5m2
# (the Ampere no-op is data equality), and their long_context default (TQ3)
# is a Genesis-era format that must never reach stock composes.
_ARCH_KV_ALLOWED = {"fp8_e5m2": frozenset({"fp8_e4m3"})}


def _arch_aware_env(profiles, variant: str, entry: dict, gpu_spec: str,
                    pin_exports: dict) -> dict[str, str]:
    """Arch-aware env for a variant (#246 Phase 1). Empty dict = no injection
    (compose ${VAR:-default} fallbacks apply, i.e. pre-#246 behavior)."""
    if not gpu_spec or variant not in ARCH_KV_PILOT_VARIANTS:
        return {}
    allowed = _ARCH_KV_ALLOWED.get(entry.get("kv_format") or "")
    if not allowed:
        return {}  # quant-specific KV (int8-PTH, turbo, bf16, ...) — never override
    if not ({"VLLM_IMAGE", "VLLM_NIGHTLY_SHA"} & set(pin_exports)):
        return {}  # vLLM-family variants only; KV_CACHE_DTYPE is a vLLM knob
    if os.environ.get("KV_CACHE_DTYPE"):
        return {}  # an explicit user pin always wins
    try:
        hardware = _parse_gpu_specs(gpu_spec, profiles)
    except LaunchCompatError:
        return {}  # unmapped card -> compose defaults (today's behavior)
    balanced = {hw.kv_format_default.get("balanced") for hw in hardware}
    if len(balanced) != 1:
        return {}  # heterogeneous rig -> no single right answer; don't guess
    target = balanced.pop()
    if (not target or target == entry["kv_format"] or target not in allowed
            or not all(target in hw.supported_kv_formats for hw in hardware)):
        return {}
    return {"KV_CACHE_DTYPE": target}


# --- #246 Phase 2: memory-envelope injection (concurrency-only first pass) ---
# Weights-invariant. Injects MAX_NUM_SEQS from a per-(slug, card-class) ceiling
# in envelopes.yml (validated soak OR computed kv-calc), to spend a bigger
# card's KV pool on concurrency. no-row / user-env-set -> no injection.
# Heterogeneous rigs clamp to the smallest-VRAM card (which is exactly the pool
# vLLM allocates); a 24 GB card in the mix therefore lands on the compose default.
_ENVELOPES_PATH = Path(__file__).with_name("envelopes.yml")


def _load_envelopes() -> dict:
    try:
        import yaml
        doc = yaml.safe_load(_ENVELOPES_PATH.read_text()) or {}
        return doc.get("envelopes") or {}
    except (OSError, ImportError):
        return {}


def _envelope_env(profiles, variant: str, gpu_spec: str) -> dict[str, str]:
    """Phase 2 concurrency injection. Empty dict = no injection (compose
    ${MAX_NUM_SEQS:-default} stands)."""
    if not gpu_spec:
        return {}
    if os.environ.get("MAX_NUM_SEQS"):
        return {}  # explicit user pin always wins
    row = _load_envelopes().get(variant)
    if not row:
        return {}
    try:
        hardware = _parse_gpu_specs(gpu_spec, profiles)
    except LaunchCompatError:
        return {}  # unmapped card -> compose default
    # Heterogeneous rigs: clamp to the SMALLEST-VRAM card. This mirrors vLLM
    # rather than guessing — with TP the KV cache is symmetric-sharded, so the
    # engine sizes the pool to min(free blocks) across ranks: the smallest card
    # already dictates the pool. A smallest-card ceiling is therefore the real
    # ceiling, not a conservative fudge. (Homogeneous rigs collapse to their one
    # class. TP=1 stays safe too: the ceiling fits whichever single card vLLM
    # runs on — all are >= the smallest.) No-op falls out for free when the
    # smallest card has no row: a 24 GB card in the mix -> compose default holds.
    smallest = min(hardware, key=lambda hw: hw.vram_gb)
    card_row = row.get(smallest.id)
    if not isinstance(card_row, dict):
        return {}
    seqs = card_row.get("max_num_seqs")
    default = card_row.get("compose_default")
    # only inject a validated value that actually raises the ceiling
    if not isinstance(seqs, int) or (isinstance(default, int) and seqs <= default):
        return {}
    return {"MAX_NUM_SEQS": str(seqs)}


def _mem_util_env(profiles, variant: str, gpu_spec: str) -> dict[str, str]:
    """Phase 2 memory-fraction safety floor. Injects GPU_MEMORY_UTILIZATION
    DOWNWARD only — when a detected card cannot safely give the compose's default
    fraction. Today that means unified-memory cards (DGX Spark: its LPDDR5X is
    shared with the Grace CPU/OS, so mem_util_safe=0.85 < the 0.92 default — boot
    at 0.92 would starve the OS). It NEVER raises above the tested compose
    default: a bigger discrete card *could* give more, but that touches Cliff-2b
    margin + boot-OOM, so the upward move stays a validated opt-in, not an
    automatic bump. Empty dict = no injection (compose default stands)."""
    if not gpu_spec:
        return {}
    if os.environ.get("GPU_MEMORY_UTILIZATION"):
        return {}  # explicit user pin always wins
    entry = COMPOSE_REGISTRY.get(variant)
    if not entry:
        return {}
    compose_gmu = entry.get("mem_util")  # the compose's default --gpu-memory-utilization
    if not isinstance(compose_gmu, (int, float)):
        return {}
    try:
        hardware = _parse_gpu_specs(gpu_spec, profiles)
    except LaunchCompatError:
        return {}  # unmapped card -> compose default
    # One GMU applies across every rank, so the safe fraction is the LOWEST card's
    # ceiling — a unified-memory card in a mixed rig forces the whole rig down.
    ceilings = [hw.mem_util_safe for hw in hardware if hw.mem_util_safe is not None]
    if not ceilings:
        return {}
    safe = min(ceilings)
    if safe < compose_gmu:  # downward only — never raise above the tested default
        return {"GPU_MEMORY_UTILIZATION": f"{safe:g}"}
    return {}


_DEEPGEMM_DISABLE_SM = {8.9, 12.0, 12.1}


def _deepgemm_env(profiles, variant: str, entry: dict, gpu_spec: str) -> dict[str, str]:
    """Disable vLLM's DeepGEMM fp8-GEMM path on CONSUMER cards that serve fp8
    weights or ModelOpt NVFP4 weights with FP8 linears. DeepGEMM is built for
    Hopper (sm_90) + datacenter Blackwell
    (sm_100/103). Consumer Blackwell (sm_120/121) hard-fails with 'recipe not
    found' (confirmed on a 5090 — disc #571 guybrush01); Ada (sm_89) never routes
    fp8 through DeepGEMM at all (Marlin/CUTLASS), so injecting 0 there is a
    harmless no-op that pre-empts the same wall for 4090 owners. Hopper/datacenter
    (where DeepGEMM is the fast path) are left untouched. Fires for the whole
    fp8-family weight set — "fp8" AND "fp8-dynamic" (compressed-tensors FP8, e.g.
    agents-a1) — and for "nvfp4" ModelOpt checkpoints, which still route FP8
    attention/linear layers through the same DeepGEMM path. Empty dict = no injection."""
    if not gpu_spec:
        return {}
    if os.environ.get("VLLM_USE_DEEP_GEMM"):
        return {}  # explicit user pin always wins
    weights_variant = ((entry or {}).get("weights_variant") or "")
    if not (weights_variant.startswith("fp8") or weights_variant == "nvfp4"):
        return {}  # FP8-family + ModelOpt NVFP4 only; INT4/AWQ/W8A8/bf16 skip.
    try:
        hardware = _parse_gpu_specs(gpu_spec, profiles)
    except LaunchCompatError:
        return {}
    if any(float(hw.sm) in _DEEPGEMM_DISABLE_SM for hw in hardware):
        return {"VLLM_USE_DEEP_GEMM": "0"}
    return {}


def resolve_engine_pin(profiles, engine_id: str) -> dict[str, str]:
    """Resolve EngineProfile.install into compose environment exports."""
    try:
        engine = profiles.engines[engine_id]
    except KeyError as exc:
        raise ProfileError(f"unknown engine profile `{engine_id}`") from exc

    spec = str(engine.install.get("spec", ""))
    if engine.install.get("method") != "docker_image":
        raise ProfileError(f"engine {engine_id!r} install.spec is not a docker image: {spec!r}")
    if engine.type == "vllm":
        if ":nightly-" in spec:
            sha = spec.rsplit(":nightly-", 1)[1].strip()
            if not sha or any(char.isspace() for char in sha):
                raise ProfileError(f"engine {engine_id!r} has an invalid nightly SHA in install.spec: {spec!r}")
            return {"VLLM_NIGHTLY_SHA": sha}
        if not spec or any(char.isspace() for char in spec):
            raise ProfileError(f"engine {engine_id!r} has an invalid docker image in install.spec: {spec!r}")
        return {"VLLM_IMAGE": spec}
    # Non-vLLM docker-image engines (e.g. beellama-local): inject a plain
    # <ENGINE>_IMAGE override, mirroring VLLM_IMAGE. The per-compose
    # ${<ENGINE>_IMAGE:-…} literal is then just a fallback for direct `docker compose`.
    env_key = _ENGINE_IMAGE_ENV.get(engine_id)
    if not env_key:
        raise ProfileError(f"engine {engine_id!r} install.spec is not a docker image: {spec!r}")
    if not spec or any(char.isspace() for char in spec):
        raise ProfileError(f"engine {engine_id!r} has an invalid docker image in install.spec: {spec!r}")
    return {env_key: spec}


def resolve_variant_pin(profiles, variant: str, gpu_spec: str = "") -> dict[str, str]:
    entry = COMPOSE_REGISTRY.get(variant)
    if not entry:
        raise ProfileError(f"unknown compose variant `{variant}`")
    exports = resolve_engine_pin(profiles, entry["engine"])
    # #246: arch-aware env rides the same export channel as the image pin.
    # Only emitted when a gpu_spec is passed (launchers do; the registry-emit
    # baselines join calls without one and sees pins only).
    exports.update(_arch_aware_env(profiles, variant, entry, gpu_spec, exports))
    exports.update(_envelope_env(profiles, variant, gpu_spec))   # Phase 2 concurrency
    exports.update(_mem_util_env(profiles, variant, gpu_spec))   # Phase 2 mem-fraction floor
    exports.update(_deepgemm_env(profiles, variant, entry, gpu_spec))  # fp8w consumer-Blackwell fix
    return exports


def _print_env(exports: dict[str, str], fmt: str) -> None:
    if fmt == "value":
        print(next(iter(exports.values())))
    elif fmt == "json":
        import json

        print(json.dumps(exports, sort_keys=True))
    else:
        for key, value in exports.items():
            print(f"{key}={value}")


def _run_fits_for_entry(
    entry: dict,
    profiles,
    hardware: list,
    *,
    tp: int,
    pp: int,
    nvlink_active: bool,
    project_vram: bool,
    include_compose_requirements: bool,
) -> FitsResult:
    model, workload, engine, drafter = _entry_objects(entry, profiles)
    return fits(
        hardware=hardware,
        model=model,
        workload=workload,
        engine=engine,
        drafter=drafter,
        tp=tp,
        pp=pp,
        kv_format=entry["kv_format"],
        max_ctx=entry["max_ctx"],
        max_num_seqs=entry["max_num_seqs"],
        mem_util=entry.get("mem_util"),
        weights_variant=entry["weights_variant"],
        nvlink_active=nvlink_active,
        requires_nvlink=bool(entry.get("requires_nvlink", False)) if include_compose_requirements else False,
        required_engine_features=list(entry.get("required_engine_features", [])) if include_compose_requirements else [],
        required_sm=entry.get("required_sm") if include_compose_requirements else None,
        project_vram=project_vram,
    )


def _format_reasons(result: FitsResult) -> list[str]:
    return [f"  - {reason}" for reason in result.reasons]


def _print_verbose_pass(label: str, result: FitsResult) -> None:
    diag = result.diagnostics
    passed = ", ".join(diag.get("constraints_passed", [])) or "(none)"
    skipped = ", ".join(diag.get("constraints_skipped", [])) or "(none)"
    if label:
        print(f"[wizard] {label}", file=sys.stderr)
    print(f"         constraints_passed: {passed}", file=sys.stderr)
    print(f"         constraints_skipped: {skipped}", file=sys.stderr)
    print(f"         kv_calc_invoked: {diag.get('kv_calc_invoked')}", file=sys.stderr)
    print(f"         elapsed_ms: {diag.get('elapsed_ms')}", file=sys.stderr)
    if result.kv_projection:
        kv = result.kv_projection
        print(
            "         verdict: "
            f"{kv.get('verdict')} — total {kv.get('total_gb')} GB/card, "
            f"budget {kv.get('budget_gb')} GB",
            file=sys.stderr,
        )
    for note in result.notes:
        print(f"         note: {note}", file=sys.stderr)


def _selected_runtime(tp: int, pp: int, entry: dict, use_runtime_parallelism: bool) -> tuple[int, int]:
    if use_runtime_parallelism:
        return tp, pp
    return int(entry["tp"]), int(entry.get("pp", 1))


def command_filter_candidates(args: argparse.Namespace) -> int:
    _quiet_compat_logger()
    profiles = load_profiles()
    hardware = _parse_gpu_specs(args.gpu_spec, profiles)
    selected = []
    variant_names = [name for name in args.variants.split(",") if name]

    for name in variant_names:
        entry = COMPOSE_REGISTRY.get(name)
        if not entry or entry["model"] != args.model:
            continue
        if args.workload and entry["workload"] != args.workload:
            continue
        if args.drafter != "__unset__":
            desired = None if args.drafter in ("none", "off") else args.drafter
            if entry.get("drafter") != desired:
                continue
        if args.weights_variant and entry["weights_variant"] != args.weights_variant:
            continue

        engine = profiles.engines[entry["engine"]]
        if args.engine:
            if args.engine in ("vllm", "llamacpp"):
                if _engine_family(engine.type) != args.engine:
                    continue
            elif entry["engine"] != args.engine:
                continue
        if args.stable and engine.stability != "stable":
            continue
        if engine.type == "llama.cpp" and len(hardware) != 1:
            continue

        tp, pp = _selected_runtime(args.tp, args.pp, entry, args.use_runtime_parallelism)
        result = _run_fits_for_entry(
            entry,
            profiles,
            hardware,
            tp=tp,
            pp=pp,
            nvlink_active=args.nvlink_active,
            project_vram=False,
            include_compose_requirements=True,
        )
        if result.valid:
            selected.append(name)
        elif args.verbose:
            print(f"[wizard] reject {name}: {'; '.join(result.reasons)}", file=sys.stderr)

    print("\n".join(selected))
    return 0


def command_validate_variant(args: argparse.Namespace) -> int:
    _quiet_compat_logger()
    profiles = load_profiles()
    entry = COMPOSE_REGISTRY.get(args.variant)
    if not entry:
        raise LaunchCompatError(f"unknown compose variant `{args.variant}`")

    hardware = _parse_gpu_specs(args.gpu_spec, profiles)
    tp = args.tp if args.tp > 0 else int(entry["tp"])
    pp = args.pp if args.pp > 0 else int(entry.get("pp", 1))
    model, workload, engine, drafter = _entry_objects(entry, profiles)

    pass1 = _run_fits_for_entry(
        entry,
        profiles,
        hardware,
        tp=tp,
        pp=pp,
        nvlink_active=args.nvlink_active,
        project_vram=args.project_vram,
        include_compose_requirements=False,
    )
    if args.verbose:
        print(
            "[wizard] Pass 1 fits() — "
            f"model={model.id} workload={workload.id} engine={engine.id} "
            f"drafter={drafter.id if drafter else 'none'} tp={tp} pp={pp}",
            file=sys.stderr,
        )
        _print_verbose_pass("", pass1)
    if not pass1.valid:
        print("[launch] ERROR: selected profile combination is invalid:", file=sys.stderr)
        print("\n".join(_format_reasons(pass1)), file=sys.stderr)
        return 2

    resolved = to_compose_name(
        model,
        engine,
        drafter,
        entry["kv_format"],
        tp,
        pp,
        workload=workload,
        weights_variant=entry["weights_variant"],
        nvlink_active=args.nvlink_active,
        max_ctx=entry["max_ctx"],
        max_num_seqs=entry["max_num_seqs"],
    )
    if args.verbose:
        print(f"[wizard] Resolved compose: {resolved or args.variant}", file=sys.stderr)

    pass2 = _run_fits_for_entry(
        entry,
        profiles,
        hardware,
        tp=tp,
        pp=pp,
        nvlink_active=args.nvlink_active,
        project_vram=args.project_vram,
        include_compose_requirements=True,
    )
    if args.verbose:
        features = entry.get("required_engine_features", [])
        print(
            "[wizard] Pass 2 fits() — "
            f"adding requires_nvlink={bool(entry.get('requires_nvlink', False))}, "
            f"required_engine_features={features}",
            file=sys.stderr,
        )
        _print_verbose_pass("", pass2)
    if not pass2.valid:
        print("[launch] ERROR: selected compose requirements are not satisfied:", file=sys.stderr)
        print("\n".join(_format_reasons(pass2)), file=sys.stderr)
        return 2

    return 0


def command_resolve_engine_pin(args: argparse.Namespace) -> int:
    _quiet_compat_logger()
    profiles = load_profiles()
    _print_env(resolve_engine_pin(profiles, args.engine_id), args.format)
    return 0


def command_resolve_variant_pin(args: argparse.Namespace) -> int:
    _quiet_compat_logger()
    profiles = load_profiles()
    _print_env(resolve_variant_pin(profiles, args.variant, args.gpu_spec), args.format)
    return 0


def _hardware_line(index: str, hardware) -> str:
    return f"  GPU {index}: {hardware.display_name} ({hardware.vram_gb:g} GB, sm {hardware.sm:g})"


def _standalone_recommendation(topology: TopologyClass, count: int) -> list[str]:
    if topology == TopologyClass.SINGLE_CARD:
        return [
            "Recommended:",
            "  1. Use the largest single-card compose your model fits.",
            "  2. Add another matched card for TP=2 when long-context concurrency matters.",
        ]
    if topology == TopologyClass.HOMOGENEOUS:
        return [
            "Recommended:",
            f"  1. TP={count} is the default path for matched cards; use the shipped vllm/dual* or multi-card composes.",
            "  2. Estate planner remains useful when you want separate models/endpoints instead of one larger TP instance.",
        ]
    if topology == TopologyClass.VRAM_MATCHED_COMPUTE_MISMATCHED:
        return [
            "Recommended:",
            f"  1. TP={count} works as-is. Compute mismatch means the faster card waits at every NCCL allreduce; effective throughput caps at the slower card's speed (~30% of faster card idle). Full per-card VRAM capacity preserved.",
            "  2. Estate planner — `bash scripts/launch.sh --estate` runs different models per card, each at full speed.",
            "",
            "Not recommended:",
            "  - PP=N: possible as a manual flag flip (`--pipeline-parallel-size N`) on a vllm/dual compose, but no PP compose ships today.",
        ]
    if topology == TopologyClass.VRAM_MISMATCHED:
        return [
            "Recommended:",
            "  1. llama.cpp `--tensor-split` for weighted layer split on mismatched VRAM.",
            "  2. PP=N as a manual vLLM flag flip (`--pipeline-parallel-size N`) if you are deliberately experimenting.",
            "  3. Estate planner — run different models per card or use the largest matched subset.",
            "",
            "Not recommended:",
            "  - TP=N on the full mismatched set: the smaller card caps usable model size and KV headroom.",
        ]
    return [
        "Recommended:",
        "  1. Manual selection. Use the largest matched subset for one model.",
        "  2. Estate planner — put different models on different card subsets.",
    ]


def command_topology(args: argparse.Namespace) -> int:
    _quiet_compat_logger()
    profiles = load_profiles()
    indexed_hardware = _parse_gpu_specs_with_indices(args.gpu_spec, profiles)
    hardware = [item[1] for item in indexed_hardware]
    topology = classify_hardware_topology(hardware)
    advisory = TOPOLOGY_ADVISORY.get(topology)

    if args.format == "wizard":
        if topology in (TopologyClass.SINGLE_CARD, TopologyClass.HOMOGENEOUS):
            return 0
        detected = " + ".join(
            f"1x {hw.display_name} ({hw.vram_gb:g} GB, sm {hw.sm:g})"
            for _idx, hw in indexed_hardware
        )
        print(f"Detected: {detected}")
        print("")
        print(f"Topology: {topology.value}")
        if advisory:
            print(f"  {advisory}")
        print("")
        print("Continue with the selected parallelism if that trade-off is acceptable.")
        return 0

    print("Detected hardware:")
    for idx, hw in indexed_hardware:
        print(_hardware_line(idx, hw))
    print("")
    print(f"Topology class: {topology.value}")
    print("")
    for line in _standalone_recommendation(topology, len(hardware)):
        print(line)
    print("")
    if advisory:
        print("Advisory:")
        print(f"  {advisory}")
        print("")
    print("For details, see docs/MULTI_CARD.md.")
    return 0


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Profile bridge for scripts/launch.sh")
    sub = parser.add_subparsers(dest="command", required=True)

    filter_cmd = sub.add_parser("filter-candidates")
    filter_cmd.add_argument("--variants", required=True)
    filter_cmd.add_argument("--model", required=True)
    filter_cmd.add_argument("--gpu-spec", required=True)
    filter_cmd.add_argument("--tp", type=int, required=True)
    filter_cmd.add_argument("--pp", type=int, required=True)
    filter_cmd.add_argument("--engine", default="")
    filter_cmd.add_argument("--workload", default="")
    filter_cmd.add_argument("--drafter", default="__unset__")
    filter_cmd.add_argument("--weights-variant", default="")
    filter_cmd.add_argument("--stable", action="store_true")
    filter_cmd.add_argument("--use-runtime-parallelism", action="store_true")
    filter_cmd.add_argument("--nvlink-active", action="store_true")
    filter_cmd.add_argument("--verbose", action="store_true")
    filter_cmd.set_defaults(func=command_filter_candidates)

    validate = sub.add_parser("validate-variant")
    validate.add_argument("--variant", required=True)
    validate.add_argument("--gpu-spec", required=True)
    validate.add_argument("--tp", type=int, default=0)
    validate.add_argument("--pp", type=int, default=0)
    validate.add_argument("--project-vram", action=argparse.BooleanOptionalAction, default=True)
    validate.add_argument("--nvlink-active", action="store_true")
    validate.add_argument("--verbose", action="store_true")
    validate.set_defaults(func=command_validate_variant)

    engine_pin = sub.add_parser("resolve-engine-pin")
    engine_pin.add_argument("--engine-id", required=True)
    engine_pin.add_argument("--format", choices=("shell", "json", "value"), default="shell")
    engine_pin.set_defaults(func=command_resolve_engine_pin)

    variant_pin = sub.add_parser("resolve-variant-pin")
    variant_pin.add_argument("--variant", required=True)
    variant_pin.add_argument("--format", choices=("shell", "json", "value"), default="shell")
    # optional: detected GPUs (idx|name|mem_mib|sm;...) — enables the #246
    # arch-aware env for pilot variants; omitted -> pin exports only.
    variant_pin.add_argument("--gpu-spec", dest="gpu_spec", default="")
    variant_pin.set_defaults(func=command_resolve_variant_pin)

    topology = sub.add_parser("topology")
    topology.add_argument("--gpu-spec", required=True)
    topology.add_argument("--format", choices=("standalone", "wizard"), default="standalone")
    topology.set_defaults(func=command_topology)

    return parser


def main(argv: list[str] | None = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)
    try:
        return int(args.func(args))
    except (LaunchCompatError, ProfileError) as exc:
        print(f"[launch] ERROR: {exc}", file=sys.stderr)
        return 2


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