"""Mac architecture + capability detection.

Lirelle picks a transcription backend at startup based on what this Mac can run:
- Apple Silicon (M1+) → mlx-whisper, Metal-accelerated, fastest
- Intel x86_64       → whisper.cpp via pywhispercpp, CPU-only fallback

We resolve this once per process and cache. The check is cheap (uname syscall)
but the decision drives a ~1.5 GB model download, so we don't want to thrash.

Public API:
    cpu_arch()              -> "arm64" | "x86_64" | "unknown"
    is_apple_silicon()      -> bool
    recommended_backend()   -> "mlx" | "whisper_cpp"
    system_summary()        -> {"arch": ..., "backend": ..., "ram_gb": ...}
"""

from __future__ import annotations

import platform
import subprocess
from functools import lru_cache


BACKEND_MLX = "mlx"
BACKEND_WHISPER_CPP = "whisper_cpp"


@lru_cache(maxsize=1)
def cpu_arch() -> str:
    """Returns 'arm64' for Apple Silicon, 'x86_64' for Intel. We trust
    platform.machine() — under Rosetta it would return 'x86_64' on an M-chip,
    which is correct: a Rosetta-translated process can't load native arm64
    extensions like MLX. So if we got booted under Rosetta we should fall
    back to the Intel path, which is what the value already implies."""
    m = platform.machine().lower()
    if m in ("arm64", "aarch64"):
        return "arm64"
    if m in ("x86_64", "amd64"):
        return "x86_64"
    return "unknown"


def is_apple_silicon() -> bool:
    return cpu_arch() == "arm64"


@lru_cache(maxsize=1)
def physical_ram_gb() -> int | None:
    """Used to decide between the medium model (~1.5 GB) and the large one
    (~3 GB). Tiny Macs (8 GB RAM) should stay on medium even if the user
    overrides it. Returns None on failure — caller should default to medium."""
    try:
        out = subprocess.run(
            ["sysctl", "-n", "hw.memsize"],
            capture_output=True, text=True, timeout=2.0, check=True,
        ).stdout.strip()
        return int(out) // (1024 ** 3)
    except (subprocess.SubprocessError, ValueError, OSError):
        return None


@lru_cache(maxsize=1)
def mac_model() -> str:
    """Friendly identifier shown in onboarding ('MacBook Pro · M2 Pro · 16 GB').
    Best-effort; returns '' if any step fails."""
    try:
        out = subprocess.run(
            ["sysctl", "-n", "hw.model"],
            capture_output=True, text=True, timeout=2.0, check=True,
        ).stdout.strip()
        return out or ""
    except (subprocess.SubprocessError, OSError):
        return ""


def recommended_backend() -> str:
    return BACKEND_MLX if is_apple_silicon() else BACKEND_WHISPER_CPP


def system_summary() -> dict:
    """Single dict the onboarding window + transcriber both consume.
    Stable shape — add new keys at the end, don't rename existing ones."""
    return {
        "arch": cpu_arch(),
        "is_apple_silicon": is_apple_silicon(),
        "backend": recommended_backend(),
        "ram_gb": physical_ram_gb(),
        "mac_model": mac_model(),
        "python": platform.python_version(),
        "macos": platform.mac_ver()[0],
    }


if __name__ == "__main__":
    # `python -m src.system_info` prints the summary — handy for debugging
    # a customer's machine without making them open a terminal.
    import json
    print(json.dumps(system_summary(), indent=2))
