# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import Any

class BackendError(RuntimeError): pass
class BackendNotAvailableError(BackendError): pass

def normalize_backend(backend: str | None = 'auto') -> str:
    return 'auto' if backend is None else str(backend).strip().lower()

def require_pymatgen() -> None:
    try:
        import pymatgen  # noqa
    except Exception as exc:
        raise BackendNotAvailableError(f'pymatgen backend is not available: {exc}') from exc

def is_pymatgen_structure(obj: Any) -> bool:
    try:
        from pymatgen.core import Structure
        return isinstance(obj, Structure)
    except Exception:
        return False

def dispatch_structure_backend(structure: Any, backend: str | None = 'auto') -> str:
    b = normalize_backend(backend)
    if b == 'pymatgen':
        require_pymatgen()
        if not is_pymatgen_structure(structure):
            raise TypeError("backend='pymatgen' requires pymatgen.core.Structure")
        return 'pymatgen'
    if b == 'auto':
        if is_pymatgen_structure(structure):
            return 'pymatgen'
        raise TypeError('Could not infer backend. Currently supported: pymatgen Structure')
    if b in {'ase','tkcrystal'}:
        raise BackendNotAvailableError(f"backend='{b}' is reserved but not implemented yet")
    raise BackendNotAvailableError(f'Unknown backend: {backend!r}')

def safe_float(x: Any, default=None):
    try: return float(x)
    except Exception: return default

def to_builtin(obj: Any) -> Any:
    try:
        import numpy as np
        if isinstance(obj, np.generic): return obj.item()
        if isinstance(obj, np.ndarray): return obj.tolist()
    except Exception:
        pass
    if isinstance(obj, (str,int,float,bool)) or obj is None: return obj
    if isinstance(obj, dict): return {str(k): to_builtin(v) for k,v in obj.items()}
    if isinstance(obj, (list,tuple)): return [to_builtin(v) for v in obj]
    return obj
