import math
import shutil
from pathlib import Path


def compute_rel_err(val1: float | None, val2: float | None) -> float | None:
    """
    Relative error:
      rel = |v1 - v2| / max(|v1|, |v2|)
    """
    if val1 is None or val2 is None:
        return None
    den = max(abs(val1), abs(val2))
    if den == 0.0:
        return 0.0
    return abs(val1 - val2) / den


def matching_digits_from_rel_err(
    rel_err: float | None,
) -> int | None:
    """
    Convert relative error to matching decimal digits.
    - rel_err == 0 => cap digits (perfect match)
    - rel_err < 0 or NaN/inf => None
    """
    cap: int = 16
    if rel_err is None:
        return None
    if not math.isfinite(rel_err) or rel_err < 0:
        return None
    if rel_err == 0.0:
        return cap

    d = int(math.floor(-math.log10(rel_err)))
    if d < 0:
        d = 0
    if d > cap:
        d = cap
    return d


def clean_output_files(output_dir: str, engines_cfg: dict) -> None:
    """
    Clean only the files inside output_dir,
    using cleanup rules from engines_cfg (dda_codes.json).
    """
    out = Path(output_dir)
    if not out.exists():
        return

    remove_names: set[str] = set()
    remove_globs: list[str] = []

    # collect cleanup rules from all engines
    for _, cfg in engines_cfg.items():
        cleanup = cfg.get("cleanup", {})
        remove_names.update(cleanup.get("remove_names", []))
        remove_globs.extend(cleanup.get("remove_globs", []))

    # 1) exact names
    for p in sorted(out.rglob("*"), reverse=True):
        if p.name not in remove_names:
            continue
        try:
            if p.is_symlink() or p.is_file():
                p.unlink()
            elif p.is_dir():
                shutil.rmtree(p)
        except Exception:
            pass

    # 2) glob patterns
    for pattern in remove_globs:
        for p in sorted(out.rglob(pattern), reverse=True):
            try:
                if p.is_symlink() or p.is_file():
                    p.unlink()
                elif p.is_dir():
                    shutil.rmtree(p)
            except Exception:
                pass
