"""
bestiary_sync.py — augment OBLITERATUS presets with fresh open-weight models
from the PlinyOS BESTIARY registry.

OBLITERATUS ships a curated `_PRESETS_LIST` with precise compute tiers + dtypes.
That list is authoritative but goes stale the moment a new open-weight model
drops. BESTIARY tracks releases across OpenRouter + HuggingFace; this adapter
pulls its open-weight channel and surfaces any model not already curated as an
auto-discovered preset (compute tier inferred from the param count in the id).

Non-destructive by design: curated presets always win on hf_id collision, and
if the catalog isn't reachable (BESTIARY not running / BESTIARY_CATALOG unset)
this returns [] so presets behave exactly as before.

Wiring: set BESTIARY_CATALOG to the served catalog url or a catalog.json path,
e.g. `export BESTIARY_CATALOG=http://127.0.0.1:8892/catalog.json`.
"""

from __future__ import annotations

import re

# Param-count → compute tier, mirroring presets.py's tiers.
_PARAM_RE = re.compile(r"(\d+(?:\.\d+)?)\s*b\b", re.IGNORECASE)


def _infer_tier_params(hf_id: str, rec: dict) -> tuple[str, str]:
    """Best-effort (tier, params) from the model id, e.g. 'Qwen/Qwen3-32B' → (large, 32B).
    MoE 'A<x>B' active-param hints are ignored in favor of total size."""
    name = (hf_id or "") + " " + (rec.get("name") or "")
    sizes = [float(x) for x in _PARAM_RE.findall(name)]
    # Drop tiny active-param hints (e.g. A3B) by preferring the largest match.
    b = max(sizes) if sizes else None
    if b is None:
        return "large", "unknown"          # unknown size → assume heavy, don't auto-pick on weak HW
    params = f"{b:g}B"
    if b < 2:
        return "tiny", params
    if b < 8:
        return "small", params
    if b < 16:
        return "medium", params
    if b < 70:
        return "large", params
    return "frontier", params


_GATED_ORGS = {"meta-llama", "google", "mistralai"}


def extra_presets(ModelPreset, existing_hf_ids):
    """Return a list of ModelPreset objects for open-weight models in the BESTIARY
    catalog that aren't already curated. Empty list on any failure (safe no-op)."""
    try:
        try:
            from .models_client import models  # vendored alongside this module
        except ImportError:
            from models_client import models   # flat-layout fallback
        catalog = models(open_weight=True)
    except Exception:
        return []

    try:
        out = []
        seen = set(existing_hf_ids)
        for m in catalog:
            if not isinstance(m, dict):
                raise ValueError("BESTIARY preset record must be an object")
            hf = m.get("hf_id")
            if not isinstance(hf, str) or not hf.strip():
                raise ValueError("BESTIARY preset record requires a string hf_id")
            if hf in seen:
                continue
            capabilities = m.get("capabilities", [])
            if not isinstance(capabilities, list) or not all(
                isinstance(capability, str) for capability in capabilities
            ):
                raise ValueError("BESTIARY capabilities must be a list of strings")
            name = m.get("name")
            if name is not None and not isinstance(name, str):
                raise ValueError("BESTIARY model name must be a string")
            released = m.get("released")
            if released is not None and not isinstance(released, str):
                raise ValueError("BESTIARY release date must be a string")

            seen.add(hf)
            tier, params = _infer_tier_params(hf, m)
            caps = ", ".join(capabilities) or "open-weight"
            rel = released or "?"
            org = hf.split("/")[0] if "/" in hf else ""
            out.append(ModelPreset(
                name=name or hf.split("/")[-1],
                hf_id=hf,
                description=f"[BESTIARY · {rel}] {caps}.",
                tier=tier,
                params=params,
                recommended_dtype="bfloat16",
                recommended_quantization=("4bit" if tier in ("large", "frontier") else None),
                gated=(org.casefold() in _GATED_ORGS),
            ))
        return out
    except Exception:
        return []
