"""
fw_server/adapters.py — Stateless adapters: JSON dict → lens yakinlasma → LensResult.

Design invariants:
  KV₇:  Each adapter creates a FRESH instance per call — zero shared state.
  AX27: Adapters are transparent vessels; they mediate but don't originate.
  AX56: Grade never reaches Hakkalyakîn.
  T6:   Score clamped < 1.0.

Each ``run_*`` function accepts a plain ``dict`` (from MCP JSON) and
returns a ``bileshke.types.LensResult``.  The adapters translate between
the wire format and the heterogeneous yakinlasma signatures.
"""

from __future__ import annotations

from typing import Any, Callable, Dict, Optional

from bileshke.types import EpistemicGrade, LensId, LensResult, clamp_score
from fw_server.grade_map import pass_ratio_to_grade, score_to_grade


# =====================================================================
# Lens 1 — Ontoloji (kavram_sozlugu)
# =====================================================================

def run_ontoloji(params: Dict[str, Any]) -> LensResult:
    """Run the Ontoloji lens (kavram_sozlugu).

    params:
        kavramlar: list[dict]  — optional pre-built Kavram dicts
            Each dict: {"name": str, "hakikat": [{"isim": str, "derece": float}],
                        "mahiyet": str, "dual_class": "harfi"|"ismi",
                        "is_living": bool}

    If kavramlar is empty, runs a default self-check on the registry.
    KV₇: fresh KavramSozlugu per call.
    """
    from kavram_sozlugu.registry import KavramSozlugu
    from kavram_sozlugu.types import Kavram, Tecelli

    registry = KavramSozlugu(preload=True)  # fresh instance

    kavramlar_raw = params.get("kavramlar", [])
    for kd in kavramlar_raw:
        hakikat = tuple(
            Tecelli(isim=t["isim"], derece=t.get("derece", 0.5))
            for t in kd.get("hakikat", [])
        )
        if not hakikat:
            # KV₈: must ground in >= 1 Name — provide minimal default
            hakikat = (Tecelli(isim="el-Hakîm", derece=0.1),)
        k = Kavram(
            name=kd["name"],
            hakikat=hakikat,
            mahiyet=kd.get("mahiyet", ""),
            dual_class=kd.get("dual_class", "harfi"),
            is_living=kd.get("is_living", False),
        )
        registry.register_kavram(k)

    score = clamp_score(registry.yakinlasma())
    checks_total = max(len(registry._kavramlar), 1)
    checks_passed = int(score * checks_total)
    grade = score_to_grade(score)

    return LensResult(
        lens_id=LensId.ONTOLOJI,
        score=score,
        grade=grade,
        checks_passed=checks_passed,
        checks_total=checks_total,
        detail=f"Registry size: {len(registry._kavramlar)} kavram(s)",
    )


# =====================================================================
# Lens 2 — Mereoloji
# =====================================================================

def run_mereoloji(params: Dict[str, Any]) -> LensResult:
    """Run the Mereoloji lens.

    params:
        parts: list[dict]  — optional part definitions
            Each dict: {"id": str, "type": str, "telos": str|None}
        relations: list[dict]  — optional relations
            Each dict: {"whole": str, "part": str}

    KV₇: fresh MereologicalStructure per call.
    """
    from mereoloji.relations import MereologicalStructure

    ms = MereologicalStructure()  # fresh instance

    for p in params.get("parts", []):
        ms.add_part(
            part_id=p["id"],
            part_type=p.get("type", "generic"),
            telos=p.get("telos"),
        )
    for r in params.get("relations", []):
        ms.add_relation(whole=r["whole"], part=r["part"])

    score = clamp_score(ms.yakinlasma())
    result = ms.verify_all()
    checks_passed = sum(1 for v in result.values() if v)
    checks_total = len(result)
    grade = pass_ratio_to_grade(checks_passed, checks_total)

    return LensResult(
        lens_id=LensId.MEREOLOJI,
        score=score,
        grade=grade,
        checks_passed=checks_passed,
        checks_total=checks_total,
        detail=f"Parts: {len(params.get('parts', []))}; checks: {checks_passed}/{checks_total}",
    )


# =====================================================================
# Lens 3 — FOL (fol_formalizasyon)
# =====================================================================

def run_fol(params: Dict[str, Any]) -> LensResult:
    """Run the FOL lens.

    params:
        text: str   — source text to scan for AX/T/KV references
        model: dict | None — optional Model for axiom satisfaction checks

    KV₇: pure function, no shared state.
    """
    from fol_formalizasyon import yakinlasma, extract_axiom_refs

    text = params.get("text", "")
    model = params.get("model")  # None is valid

    score = clamp_score(yakinlasma(text, model))
    refs = extract_axiom_refs(text)
    total_refs = sum(len(v) for v in refs.values())

    grade = score_to_grade(score)

    return LensResult(
        lens_id=LensId.FOL,
        score=score,
        grade=grade,
        checks_passed=total_refs,
        checks_total=max(total_refs, 1),
        detail=f"AX refs: {len(refs.get('axioms', []))}; T refs: {len(refs.get('theorems', []))}; KV refs: {len(refs.get('kavaid', []))}",
    )


# =====================================================================
# Lens 4 — Bayes (bayes_analiz)
# =====================================================================

def run_bayes(params: Dict[str, Any]) -> LensResult:
    """Run the Bayes lens.

    params:
        model: dict | None — serialised BayesModel, or None for default check.

    KV₇: fresh computation, no shared state.
    """
    from bayes_analiz import yakinlasma

    model = params.get("model")  # None → score 0.0 (expected)

    score = clamp_score(yakinlasma(model))
    grade = score_to_grade(score)

    return LensResult(
        lens_id=LensId.BAYES,
        score=score,
        grade=grade,
        checks_passed=1 if score > 0 else 0,
        checks_total=1,
        detail=f"Bayes yakinlasma: {score:.4f}",
    )


# =====================================================================
# Lens 5 — OyunTeorisi (oyun_teorisi)
# =====================================================================

def run_oyun_teorisi(params: Dict[str, Any]) -> LensResult:
    """Run the OyunTeorisi lens.

    params:
        model: dict | None — serialised GameModel, or None for default check.

    KV₇: fresh computation, no shared state.
    """
    from oyun_teorisi import yakinlasma

    model = params.get("model")  # None → score 0.0

    score = clamp_score(yakinlasma(model))
    grade = score_to_grade(score)

    return LensResult(
        lens_id=LensId.OYUN_TEORISI,
        score=score,
        grade=grade,
        checks_passed=1 if score > 0 else 0,
        checks_total=1,
        detail=f"OyunTeorisi yakinlasma: {score:.4f}",
    )


# =====================================================================
# Lens 6 — KategoriTeorisi (kategori_teorisi)
# =====================================================================

def run_kategori_teorisi(params: Dict[str, Any]) -> LensResult:
    """Run the KategoriTeorisi lens.

    params:
        cat: dict | None     — serialised Category
        functor: dict | None — serialised Functor
        eta: dict | None     — serialised NaturalTransformation

    KV₇: fresh computation, no shared state.
    """
    from kategori_teorisi import yakinlasma

    cat = params.get("cat")
    functor = params.get("functor")
    eta = params.get("eta")

    score = clamp_score(yakinlasma(cat, functor, eta))
    grade = score_to_grade(score)

    return LensResult(
        lens_id=LensId.KATEGORI_TEORISI,
        score=score,
        grade=grade,
        checks_passed=1 if score > 0 else 0,
        checks_total=1,
        detail=f"KategoriTeorisi yakinlasma: {score:.4f}",
    )


# =====================================================================
# Lens 7 — Topoloji + Holografik (holografik)
# =====================================================================

def run_holografik(params: Dict[str, Any]) -> LensResult:
    """Run the Holografik lens.

    params:
        model: dict | None — serialised HolographicModel, or None for default.

    KV₇: fresh computation, no shared state.
    """
    from holografik import yakinlasma

    model = params.get("model")  # None → score 0.0

    score = clamp_score(yakinlasma(model))
    grade = score_to_grade(score)

    return LensResult(
        lens_id=LensId.TOPOLOJI_HOLOGRAFIK,
        score=score,
        grade=grade,
        checks_passed=1 if score > 0 else 0,
        checks_total=1,
        detail=f"Holografik yakinlasma: {score:.4f}",
    )


# =====================================================================
# Dispatch map: lens_id string → adapter function
# =====================================================================

ADAPTER_MAP: Dict[str, Callable[[Dict[str, Any]], LensResult]] = {
    "Ontoloji": run_ontoloji,
    "Mereoloji": run_mereoloji,
    "FOL": run_fol,
    "Bayes": run_bayes,
    "OyunTeorisi": run_oyun_teorisi,
    "KategoriTeorisi": run_kategori_teorisi,
    "Topoloji + Holografik": run_holografik,
}


def run_lens(lens_id: str, params: Optional[Dict[str, Any]] = None) -> LensResult:
    """Execute a single lens by ID.

    Args:
        lens_id: One of the 7 valid lens ID strings.
        params: Lens-specific parameters (default: empty dict).

    Returns:
        LensResult with score, grade, and checks.

    Raises:
        ValueError: If lens_id is not recognised.
    """
    if params is None:
        params = {}
    adapter = ADAPTER_MAP.get(lens_id)
    if adapter is None:
        raise ValueError(
            f"Unknown lens_id: {lens_id!r}. "
            f"Valid IDs: {list(ADAPTER_MAP.keys())}"
        )
    return adapter(params)
