"""OrcaSlicer Filament-Profil Parser.

Shared between the generator (tools/gen_orca_filament_list.py) and the
Custom-Profile-Import-Endpoint (bridge/kobrax_moonraker_bridge.py).

Reads Orca filament JSON files (system or user profiles) and returns
them as a normalized list with (id, name, vendor, type, color).
"""
from __future__ import annotations

import json
import re


def first_str(value, default: str = "") -> str:
    """Orca profiles store some fields as ['value']. Returns the first
    element as a string."""
    if isinstance(value, list):
        return str(value[0]) if value else default
    if isinstance(value, str):
        return value
    return default


def clean_name(raw: str) -> str:
    """Strippt printer-/varianten-spezifische Suffixe:
      'PolyTerra PLA @base'                       → 'PolyTerra PLA'
      'Anycubic PLA @Anycubic Kobra X 0.4 nozzle' → 'Anycubic PLA'
      'Anker Generic PLA 0.4 nozzle'              → 'Anker Generic PLA'
    """
    name = re.sub(r"\s*@.*$", "", raw).strip()
    name = re.sub(r"\s+\d+(\.\d+)?\s*nozzle\s*$", "", name, flags=re.IGNORECASE).strip()
    return name or raw


def parse_profile(data: dict, by_name: dict | None = None,
                  path_vendor: str | None = None,
                  source_path: str = "",
                  system_index: list | None = None) -> dict | None:
    """Parses a single Orca filament profile into the bridge schema.

    `by_name` is optionally a {name: [profile, ...]} index for inherits resolution
    from the raw source tree (generator). For single-file imports (user file
    from the OrcaSlicer user dir) we pass `system_index` instead -
    the finished system profile list from orca_filaments.json. This lets
    us derive filament_id/vendor/type/color via the `inherits` chain from
    the system parent even when the user profile does not set these
    fields itself (typically: user override profiles only contain tweaks).

    Returns {id, name, vendor, type, color} or None when the profile
    has no filament_id (e.g. abstract @base templates).
    """
    if not isinstance(data, dict):
        return None
    # User profiles from the OrcaSlicer user dir often set NO "type" field -
    # it comes from the system parent. We accept that when either "type"
    # is explicitly "filament" OR an "inherits" points to another profile.
    if data.get("type") not in (None, "filament") and not data.get("inherits"):
        return None
    if data.get("type") == "filament" and data.get("inherits") is None and not data.get("filament_id"):
        # type=filament but no parent + no ID -> worthless stub
        return None
    inst = data.get("instantiation", "true")
    if isinstance(inst, str) and inst.lower() == "false":
        return None

    # Build the system name index for the fallback lookup when system_index is set.
    sys_by_name: dict[str, dict] = {}
    if system_index:
        for p in system_index:
            if isinstance(p, dict) and p.get("name"):
                sys_by_name[p["name"]] = p

    def _resolve(key: str, depth: int = 5):
        cur_list = [data]
        for _ in range(depth):
            for cur in cur_list:
                v = cur.get(key)
                if v not in ("", [], None, [""]) and v is not None:
                    return v
            # Erst raw-Inherits via by_name (Generator-Pfad)
            if by_name:
                next_list: list[dict] = []
                for cur in cur_list:
                    parent_name = cur.get("inherits")
                    if parent_name and parent_name in by_name:
                        next_list.extend(by_name[parent_name])
                if next_list:
                    cur_list = next_list
                    continue
            break
        return None

    def _resolve_via_system_index(key: str):
        """Inherits chain via system_index (clean_name match)."""
        parent_raw = data.get("inherits")
        if not parent_raw or not sys_by_name:
            return None
        parent_clean = clean_name(parent_raw)
        sys_p = sys_by_name.get(parent_clean)
        if not sys_p:
            return None
        # The system JSON already uses the normalized schema
        mapping = {
            "filament_id":              "id",
            "filament_vendor":          "vendor",
            "filament_type":            "type",
            "default_filament_colour":  "color",
        }
        return sys_p.get(mapping.get(key, key))

    def _resolve_full(key: str):
        v = _resolve(key)
        if v not in ("", [], None, [""]) and v is not None:
            return v
        return _resolve_via_system_index(key)

    fid = _resolve_full("filament_id")
    if not fid or not isinstance(fid, str):
        return None

    name_raw = data.get("name", fid)
    name = clean_name(name_raw)
    vendor = first_str(_resolve_full("filament_vendor")) or (path_vendor or "Generic")
    ftype  = first_str(_resolve_full("filament_type"), "")
    color  = first_str(_resolve_full("default_filament_colour"), "")

    return {
        "id":     fid,
        "name":   name,
        "vendor": vendor,
        "type":   ftype,
        "color":  color,
    }


def parse_profile_bytes(blob: bytes, source_name: str = "",
                        system_index: list | None = None) -> dict | None:
    """Reads a single profile from JSON bytes. For the file upload path.
    `system_index` is optionally the finished list from orca_filaments.json -
    used for the inherits resolution of user profiles that do not carry the full
    schema from the system parent."""
    try:
        data = json.loads(blob.decode("utf-8", errors="replace"))
    except Exception:
        return None
    return parse_profile(data, source_path=source_name, system_index=system_index)
