"""
kavram_sozlugu.types — Core data types for the Ontology Lens (Lens #1).

Implements the fundamental sorts from AGENTS.md Appendix C:
  - Sifat  (S_Sifat)  — Attributes of the Essence (cardinality = 7)
  - Isim   (S_Isim)   — Operational principles / Names (cardinality ≤ 1001)
  - Tecelli            — Manifestation degree (continuous, AX21)
  - Kavram (S_Kavram)  — Concepts extracted from source text

Governing axioms enforced at construction:
  AX17: Hakikat(x) = {n ∈ S_Isim | Tecelli(n,x) > 0}
  AX18: Mahiyet(x) = Shadow(Hakikat(x))
  AX19: ∀x ∈ Living: |Hakikat(x)| ≥ 20
  AX21: Tecelli(n,x) ∈ ℝ≥₀ (continuous, not binary)
  AX22: ∀n,w: mertebe(n,w) < ekmel(n) (supremum never reached)
  KV₁:  ∀c: DualClass(c) ∈ {harfî, ismî} (default: harfî)
  KV₈:  ∀c: |esma_mapping(c)| ≥ 1
"""

from dataclasses import dataclass
from typing import Optional
import json


# ---------------------------------------------------------------------------
# Sifat — Attribute (S_Sifat)
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class Sifat:
    """An attribute of the Essence (S_Sifat, cardinality = 7).

    Per AGENTS.md §1.3:
      Sifat_Seba = {Hayat} ⊔ VucudTriad ⊔ BekaTriad
                 = {Hayat} ⊔ {Ilim, Irade, Kudret} ⊔ {Sem, Basar, Kelam}

    Attributes:
        name: Attribute name (e.g. "Hayat", "Ilim")
        role: "root" | "vucud" | "beka"
    """
    name: str
    role: str  # "root" | "vucud" | "beka"

    def __post_init__(self):
        if self.role not in ("root", "vucud", "beka"):
            raise ValueError(
                f"Sifat role must be 'root', 'vucud', or 'beka', got '{self.role}'"
            )


# ---------------------------------------------------------------------------
# Isim — Name (S_Isim)
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class Isim:
    """An operational principle / governing pattern (S_Isim, cardinality ≤ 1001).

    Per AGENTS.md §2.4 (AX17): Names are realia, not nominalia.
    Per Appendix C.4: sifat_of(n) → P(S_Sifat)

    Attributes:
        name:  Name string (e.g. "el-Hakim")
        sifat: Parent attribute names via sifat_of() — must be non-empty
        ekmel: Unreachable supremum (AX22). Default: infinity
    """
    name: str
    sifat: frozenset[str]  # parent attribute names
    ekmel: float = float('inf')  # AX22: supremum, never attained

    def __post_init__(self):
        if not self.sifat:
            raise ValueError(
                f"Isim '{self.name}' must map to >= 1 Sifat (sifat_of constraint)"
            )
        if self.ekmel <= 0:
            raise ValueError(
                f"Isim '{self.name}' ekmel must be > 0, got {self.ekmel}"
            )


# ---------------------------------------------------------------------------
# Tecelli — Manifestation Degree
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class Tecelli:
    """A manifestation degree — how strongly a Name expresses in a concept.

    Per AGENTS.md §2.6:
      AX21: Tecelli(n,x) ∈ ℝ≥₀ (continuous, not binary)
      AX22: ∀n,w: mertebe(n,w) < ekmel(n) (never reaches supremum)

    Attributes:
        isim:   Name reference string
        derece: Manifestation degree, must be > 0 (concept IS constituted by this Name)
    """
    isim: str
    derece: float

    def __post_init__(self):
        # Degree must be positive — zero means no manifestation,
        # but Tecelli entries only exist for manifesting Names (AX17)
        if self.derece <= 0:
            object.__setattr__(self, 'derece', 0.001)
        # Upper bound (< ekmel) checked by registry, since ekmel is Name-specific


# ---------------------------------------------------------------------------
# Kavram — Concept (S_Kavram)
# ---------------------------------------------------------------------------

@dataclass
class Kavram:
    """A concept extracted from source text (S_Kavram).

    Per AGENTS.md:
      AX17: Hakikat(x) = {n ∈ S_Isim | Tecelli(n,x) > 0}
      AX18: Mahiyet(x) = Shadow(Hakikat(x))
      KV₁:  Every concept classified harfi/ismi (default: harfi)
      KV₈:  Every concept grounded in >= 1 Name
      AX19: Living beings → |Hakikat(x)| >= 20

    Attributes:
        name:       Concept identifier
        hakikat:    Constitutive Name-set — tuple of Tecelli entries (AX17)
        mahiyet:    Surface description — always derivative of hakikat (AX18)
        dual_class: "harfi" | "ismi" — KV₁ enforced, default harfi (K-8)
        is_living:  Gates AX19 (>= 20 Names required for living concepts)
    """
    name: str
    hakikat: tuple[Tecelli, ...]  # Constitutive Name-set — AX17
    mahiyet: str = ""             # Surface description — AX18 (shadow)
    dual_class: str = "harfi"     # KV₁: default harfi (K-8: harfi priority)
    is_living: bool = False

    def __post_init__(self):
        # KV₁: Must be classified
        if self.dual_class not in ("harfi", "ismi"):
            raise ValueError(
                f"Kavram '{self.name}': dual_class must be 'harfi' or 'ismi', "
                f"got '{self.dual_class}' (KV1 violation)"
            )
        # KV₈: Must ground in >= 1 Name
        if not self.hakikat:
            raise ValueError(
                f"Kavram '{self.name}': hakikat must contain >= 1 Tecelli "
                f"(KV8 violation: every concept must ground in >= 1 Name)"
            )
        # AX19: Living beings need >= 20 Names
        if self.is_living and len(self.hakikat) < 20:
            raise ValueError(
                f"Kavram '{self.name}': living concept requires >= 20 Names "
                f"in hakikat, got {len(self.hakikat)} (AX19 violation)"
            )

    def to_dict(self) -> dict:
        """Serialize to dict (for ai_assert JSON validation)."""
        return {
            "name": self.name,
            "dual_class": self.dual_class,
            "hakikat": [
                {"isim": t.isim, "derece": t.derece}
                for t in self.hakikat
            ],
            "mahiyet": self.mahiyet,
            "is_living": self.is_living,
        }

    def to_json(self) -> str:
        """Serialize to JSON string."""
        return json.dumps(self.to_dict(), ensure_ascii=False)

    @classmethod
    def from_dict(cls, data: dict) -> 'Kavram':
        """Deserialize from dict (for ai_assert output parsing)."""
        hakikat = tuple(
            Tecelli(isim=e["isim"], derece=e["derece"])
            for e in data.get("hakikat", [])
        )
        return cls(
            name=data["name"],
            hakikat=hakikat,
            mahiyet=data.get("mahiyet", ""),
            dual_class=data.get("dual_class", "harfi"),
            is_living=data.get("is_living", False),
        )

    @classmethod
    def from_json(cls, json_str: str) -> 'Kavram':
        """Deserialize from JSON string."""
        return cls.from_dict(json.loads(json_str))
