#!/usr/bin/env python3
"""Shared process metric helpers for UI panels."""

from typing import Any, Dict, List, Tuple

from ...core.gpu.base import GPUInfo


def _safe_float(value: Any) -> float:
    """Convert arbitrary values to float with safe fallback."""
    try:
        return float(value)
    except (TypeError, ValueError):
        return 0.0


def _parse_percent(value: Any) -> float:
    """Parse percentage-like values from strings or numeric types."""
    if isinstance(value, str):
        stripped = value.strip()
        if stripped.endswith("%"):
            stripped = stripped[:-1]
        value = stripped
    return _safe_float(value)


def _normalize_memory_mb(memory_value: Any, gpu_memory_total_mb: float) -> float:
    """Normalize process memory value to MB.

    Most backends already provide MB, but some AMD paths can return bytes.
    """
    value = _safe_float(memory_value)
    if value <= 0:
        return 0.0
    if gpu_memory_total_mb > 0 and value > gpu_memory_total_mb * 2:
        return value / (1024 * 1024)
    return value


def calculate_process_gpu_metrics(
    process: Dict[str, Any], gpus: List[GPUInfo]
) -> Tuple[float, float]:
    """Calculate per-process GPU and VRAM percentages.

    Returns:
        Tuple of (gpu_percent, vram_percent), using the highest matching GPU values.
    """
    pid = process.get("pid")
    if pid is None or not gpus:
        return 0.0, 0.0

    gpu_percent = 0.0
    vram_percent = 0.0

    for gpu in gpus:
        total_memory_mb = _safe_float(gpu.memory_total)
        if total_memory_mb <= 0:
            total_memory_mb = 0.0

        # Precompute normalized memory sum for fallback distribution.
        normalized_total_proc_memory = sum(
            _normalize_memory_mb(item.get("memory", 0.0), total_memory_mb)
            for item in gpu.processes
        )

        for gpu_proc in gpu.processes:
            if gpu_proc.get("pid") != pid:
                continue

            proc_memory_mb = _normalize_memory_mb(
                gpu_proc.get("memory", 0.0),
                total_memory_mb,
            )

            # Prefer direct per-process utilization when available.
            if gpu_proc.get("sm_util") is not None:
                current_gpu = _parse_percent(gpu_proc.get("sm_util"))
            elif gpu_proc.get("cu_occupancy") is not None:
                current_gpu = _parse_percent(gpu_proc.get("cu_occupancy"))
            elif gpu_proc.get("gpu_util") is not None:
                current_gpu = _parse_percent(gpu_proc.get("gpu_util"))
            else:
                current_gpu = 0.0
                if normalized_total_proc_memory > 0:
                    memory_ratio = proc_memory_mb / normalized_total_proc_memory
                    current_gpu = _safe_float(gpu.utilization) * memory_ratio

            current_vram = 0.0
            if total_memory_mb > 0:
                current_vram = (proc_memory_mb / total_memory_mb) * 100.0

            gpu_percent = max(gpu_percent, current_gpu)
            vram_percent = max(vram_percent, current_vram)

    return max(0.0, gpu_percent), max(0.0, vram_percent)
