"""Resolve Unity GUIDs to files, and Unity .mat files to texture paths.

Unity keeps the real material assignment outside the FBX: the FBX's own embedded
texture paths point into the vendor's authoring tree and do not exist on disk.
What the scene actually renders with is the Renderer's `m_Materials` list, each
entry a GUID pointing at a `.mat` whose maps are themselves GUID references.
"""
import os
import re

_GUID_RE = re.compile(r'guid: ([0-9a-f]{32})')


def build_guid_index(assets_root):
    """Map every asset GUID to the file it names, by reading the .meta sidecars."""
    index = {}
    for base, _dirs, files in os.walk(assets_root):
        for f in files:
            if not f.endswith('.meta'):
                continue
            meta = os.path.join(base, f)
            try:
                with open(meta, encoding='utf-8', errors='ignore') as fh:
                    head = fh.read(400)
            except OSError:
                continue
            m = _GUID_RE.search(head)
            if m:
                index[m.group(1)] = meta[:-5]
    return index


# Unity's builtin and HDRP shaders name the same map differently; take whichever exists.
_TEX_SLOTS = {
    'baseColor': ('_BaseColorMap', '_MainTex', '_BaseMap'),
    'normal': ('_BumpMap', '_NormalMap'),
    'mask': ('_MaskMap', '_MetallicGlossMap', '_SpecGlossMap'),
    'emissive': ('_EmissiveColorMap', '_EmissionMap'),
}
_COLOR_SLOTS = {
    'baseColor': ('_BaseColor', '_Color'),
    'emissive': ('_EmissiveColor', '_EmissionColor'),
}

_FLOAT_SLOTS = {
    'metallic': ('_Metallic',),
    'smoothness': ('_Smoothness', '_Glossiness'),
    'emissiveIntensity': ('_EmissiveIntensity',),
}


def parse_material(path, guid_index):
    """Pull the maps, colours and scalars a .mat sets, resolving map GUIDs to files."""
    try:
        with open(path, encoding='utf-8', errors='ignore') as fh:
            text = fh.read()
    except OSError:
        return None

    name_m = re.search(r'^  m_Name: (.+)$', text, re.M)
    kw_m = re.search(r'm_ShaderKeywords: (.*?)\n  m_', text, re.S)
    out = {
        'name': name_m.group(1).strip() if name_m else os.path.basename(path)[:-4],
        'textures': {},
        'scale': {},
        'colors': {},
        'floats': {},
        'keywords': (kw_m.group(1).replace('\n', ' ').split() if kw_m else []),
    }

    # m_TexEnvs entries are `- _Slot:` followed by an indented m_Texture / m_Scale block.
    for m in re.finditer(
        r'- (_\w+):\s*\n\s*m_Texture: \{fileID: (-?\d+)(?:, guid: ([0-9a-f]{32}))?[^}]*\}'
        r'\s*\n\s*m_Scale: \{x: ([-\d.eE]+), y: ([-\d.eE]+)\}',
        text,
    ):
        slot, _fid, guid, sx, sy = m.groups()
        if not guid:
            continue
        for key, aliases in _TEX_SLOTS.items():
            if slot in aliases and key not in out['textures']:
                resolved = guid_index.get(guid)
                if resolved and os.path.exists(resolved):
                    out['textures'][key] = resolved
                    out['scale'][key] = (float(sx), float(sy))

    # Collect by exact property name first, then resolve by alias priority. Unity serialises
    # properties alphabetically, so picking whichever alias appears first in the file would take
    # the Standard shader's `_EmissionColor` over HDRP's `_EmissiveColor` on a material that sets
    # both — and on these assets that means white where the real value is black.
    raw_colors = {}
    for m in re.finditer(r'- (_\w+): \{r: ([-\d.eE]+), g: ([-\d.eE]+), b: ([-\d.eE]+), a: ([-\d.eE]+)\}', text):
        raw_colors.setdefault(m.group(1), tuple(float(v) for v in m.groups()[1:]))
    for key, aliases in _COLOR_SLOTS.items():
        for alias in aliases:
            if alias in raw_colors:
                out['colors'][key] = raw_colors[alias]
                break

    raw_floats = {}
    for m in re.finditer(r'- (_\w+): ([-\d.eE]+)\s*$', text, re.M):
        raw_floats.setdefault(m.group(1), float(m.group(2)))
    for key, aliases in _FLOAT_SLOTS.items():
        for alias in aliases:
            if alias in raw_floats:
                out['floats'][key] = raw_floats[alias]
                break
    # HDRP hides emission behind this toggle even while _EmissiveColor holds a value.
    out['emissiveEnabled'] = (
        '_EMISSION' in out['keywords']
        or raw_floats.get('_UseEmissiveIntensity', 0) > 0
    )

    return out
