# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import Any
from .backends import dispatch_structure_backend, safe_float, to_builtin
AMU_TO_G = 1.66053906660e-24
ANG3_TO_CM3 = 1.0e-24

def get_lattice_inf(structure: Any, *, backend: str='auto') -> dict[str, Any]:
    b = dispatch_structure_backend(structure, backend)
    if b == 'pymatgen':
        l = structure.lattice; a,bv,c = l.abc; al,be,ga = l.angles
        return {'backend':'pymatgen','a':float(a),'b':float(bv),'c':float(c),
                'alpha':float(al),'beta':float(be),'gamma':float(ga),
                'abc':[float(a),float(bv),float(c)], 'angles':[float(al),float(be),float(ga)],
                'volume':float(l.volume),'matrix':to_builtin(l.matrix),
                'reciprocal_matrix':to_builtin(l.reciprocal_lattice.matrix)}
    raise RuntimeError(f'unsupported backend {b!r}')

def _species_occ_list(site: Any) -> list[dict[str, Any]]:
    ret=[]
    for sp,occ in site.species.items():
        d={'symbol':getattr(sp,'symbol',str(sp)),'species':str(sp),'occupancy':float(occ)}
        try: d['Z']=int(sp.Z)
        except Exception: pass
        try: d['oxidation_state']=float(sp.oxi_state)
        except Exception: pass
        ret.append(d)
    return ret

def _species_string(site: Any) -> str:
    parts=[]
    for s in _species_occ_list(site):
        occ=s['occupancy']; parts.append(s['species'] if abs(occ-1)<1e-12 else f"{s['species']}:{occ:.6g}")
    return ','.join(parts)

def _site_to_dict(site: Any, index: int, *, include_cartesian=True) -> dict[str, Any]:
    sp=_species_occ_list(site)
    d={'index':int(index),'species_string':_species_string(site),'species':sp,
       'frac_coords':[float(x) for x in site.frac_coords],
       'occupancy_sum':float(sum(x['occupancy'] for x in sp))}
    if include_cartesian: d['cart_coords']=[float(x) for x in site.coords]
    try: d['label']=str(site.label)
    except Exception: pass
    try: d['properties']=to_builtin(site.properties)
    except Exception: pass
    return d

def get_site_inf(structure: Any, *, backend: str='auto', max_sites: int|None=None, include_cartesian=True) -> dict[str, Any]:
    b=dispatch_structure_backend(structure, backend)
    if b == 'pymatgen':
        sites=structure.sites if max_sites is None else structure.sites[:int(max_sites)]
        return {'backend':'pymatgen','n_sites':len(structure.sites),'n_returned_sites':len(sites),
                'sites':[_site_to_dict(site,i,include_cartesian=include_cartesian) for i,site in enumerate(sites)]}
    raise RuntimeError(f'unsupported backend {b!r}')

def get_composition_inf(structure: Any, *, backend: str='auto') -> dict[str, Any]:
    b=dispatch_structure_backend(structure, backend)
    if b == 'pymatgen':
        c=structure.composition; elems=[]
        for el,amt in c.items():
            d={'symbol':getattr(el,'symbol',str(el)),'amount':float(amt)}
            try: d['Z']=int(el.Z)
            except Exception: pass
            elems.append(d)
        ret={'backend':'pymatgen','formula':c.formula,'alphabetical_formula':c.alphabetical_formula,
             'reduced_formula':c.reduced_formula,'num_atoms':float(c.num_atoms),
             'elements':elems,'as_dict':{str(k):float(v) for k,v in c.as_dict().items()}}
        for name in ['chemical_system','anonymized_formula']:
            try: ret[name]=getattr(c,name)
            except Exception: pass
        return ret
    raise RuntimeError(f'unsupported backend {b!r}')

def _site_mass(site: Any) -> tuple[float,float]:
    occ_total=0.0; mass=0.0
    for sp,amt in site.species.items():
        occ=float(amt); occ_total += occ
        try: mass += occ*float(sp.atomic_mass)
        except Exception: pass
    return occ_total, mass

def get_density_inf(structure: Any, *, backend: str='auto') -> dict[str, Any]:
    b=dispatch_structure_backend(structure, backend)
    if b == 'pymatgen':
        eff=0.0; mass_amu=0.0
        for site in structure.sites:
            o,m=_site_mass(site); eff+=o; mass_amu+=m
        vol=float(structure.volume); mass_g=mass_amu*AMU_TO_G; vol_cm3=vol*ANG3_TO_CM3
        ret={'backend':'pymatgen','n_sites':len(structure.sites),'effective_atoms':eff,
             'total_mass_amu':mass_amu,'volume_ang3':vol,
             'atomic_density_atom_per_ang3': eff/vol if vol>0 else None,
             'mass_density_g_cm3': mass_g/vol_cm3 if vol_cm3>0 else None}
        try: ret['pymatgen_density_g_cm3']=float(structure.density)
        except Exception: pass
        return ret
    raise RuntimeError(f'unsupported backend {b!r}')

def get_crystal_inf(structure: Any, *, backend: str='auto', include_sites=True, max_sites: int|None=None, include_symmetry=True, symprec: float=1e-3) -> dict[str, Any]:
    b=dispatch_structure_backend(structure, backend)
    if b != 'pymatgen': raise RuntimeError(f'unsupported backend {b!r}')
    ret={'backend':'pymatgen','source_type':type(structure).__name__,
         'composition':get_composition_inf(structure,backend='pymatgen'),
         'lattice':get_lattice_inf(structure,backend='pymatgen'),
         'density':get_density_inf(structure,backend='pymatgen'),
         'charge':safe_float(getattr(structure,'charge',None)),
         'is_ordered':bool(getattr(structure,'is_ordered',False))}
    if include_sites: ret['site_info']=get_site_inf(structure,backend='pymatgen',max_sites=max_sites)
    if include_symmetry:
        try:
            from .symmetry import get_spg_inf
            ret['spacegroup']=get_spg_inf(structure,backend='pymatgen',symprec=symprec)
        except Exception as exc:
            ret['spacegroup']={'ok':False,'error':f'{type(exc).__name__}: {exc}'}
    return ret

def get_neighbor_inf(structure: Any, *, site_index: int=0, radius: float=5.0, backend: str='auto') -> dict[str, Any]:
    b=dispatch_structure_backend(structure, backend)
    if b == 'pymatgen':
        idx=int(site_index); center=structure.sites[idx]; nbs=structure.get_neighbors(center, float(radius))
        return {'backend':'pymatgen','site_index':idx,'center':_site_to_dict(center,idx),
                'radius':float(radius),'n_neighbors':len(nbs),
                'neighbors':[{'index':i,'species_string':str(nn.species_string),'distance':float(nn.nn_distance),
                              'frac_coords':[float(x) for x in nn.frac_coords],'cart_coords':[float(x) for x in nn.coords]}
                             for i,nn in enumerate(nbs)]}
    raise RuntimeError(f'unsupported backend {b!r}')
