"""
holografik.types — Core data types for the Topoloji + Holografik Lens (Lens #7 of 7).

Implements a topological / holographic instrument for the multi-lens
analytical apparatus defined in AGENTS.md §4.1.

  Lens: Topoloji + Holografik | Faculty: Ruh + Hafî
  Instruments: holografik_sabitler + holografik_analiz
  Domain: Topological/holographic analysis, B01–B22 dimensions

Sorts implemented (mapping to AGENTS.md Appendix C):
  - BesmeleBoyut     — 22 holographic seed dimensions (B01–B22)
  - DimensionType    — cemâlî (B01–B20) vs celâlî (B21–B22, N-2/AX59)
  - CompositionMode  — tesanüd / infirâd / independent (AX63)
  - SeedVector       — 22-dimensional continuous vector in [0, 1)
  - HolographicUnit  — A text unit with its seed vector
  - FidelityPair     — A (source, derivative) pair with 0 < fidelity < 1
  - IsomorphismResult— Result of holographic structure comparison
  - HolographicModel — Complete holographic analysis model

Governing axioms enforced at construction:
  AX37:     Holographic Structure — every part mirrors the whole
  AX38:     Tereşşuhat — faithful seepage, ne ayn ne gayr
  T12:      Besmele Seed — triple isomorphism (Besmele ≅ Fatiha ≅ Kur'an)
  T14:      Ne Ayn Ne Gayr Chain — 0 < fidelity < 1 at every level
  KV₆:     Holographic Seed Omnipresence — every module has seed > 0
  AX52:     Multiplicative gate — zero in any dimension = collapse
  N-2/AX59: Celâl Inclusion — B21–B22 must not be excluded
  T6/KV₄:  Convergence bound — all scores in [0, 1)
  AX57:     Transparency — every output must disclose its epistemic status

KV₇ compliance: This module imports ONLY from the standard library.
"""

from __future__ import annotations

import math
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, List, Optional, Tuple


# ---------------------------------------------------------------------------
# Score clamping — T6/KV₄: strict upper bound < 1.0
# ---------------------------------------------------------------------------

def clamp_score(value: float) -> float:
    """Clamp a score to [0, 0.9999].

    Per T6 (Convergence Bound) and KV₄ (Ne Ayn Ne Gayr):
    no instrument achieves convergence = 1.0.
    """
    return min(max(value, 0.0), 0.9999)


# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------

class DimensionType(Enum):
    """Type of a holographic dimension.

    Per N-2/AX59, the Besmele seed has two classes:
      - CEMALI (B01–B20): beauty-related dimensions
      - CELALI (B21–B22): majesty/severity dimensions

    Analysis MUST NOT exclude celâlî dimensions.
    """
    CEMALI = "cemali"
    CELALI = "celali"


class CompositionMode(Enum):
    """How multiple holographic findings compose.

    Per AX63 (Tesanüd/İnfirâd):
      - TESANUD: affirmations compose super-additively
      - INFIRAD: denials remain isolated
      - INDEPENDENT: neutral, neither reinforcing nor isolated
    """
    TESANUD = "tesanud"
    INFIRAD = "infirad"
    INDEPENDENT = "independent"


# ---------------------------------------------------------------------------
# Besmele Boyutları — B01–B22 dimension catalogue
# ---------------------------------------------------------------------------

class BesmeleBoyut(Enum):
    """The 22 holographic seed dimensions (Besmele Boyutları).

    B01–B20 are cemâlî (beauty-related) dimensions grounded in the
    Name ontology (AX17, AX19: |Hakikat(x)| ≥ 20 for living beings).
    B21–B22 are celâlî (majesty/severity) dimensions (N-2/AX59).

    Each dimension corresponds to a detectable structural pattern
    in holographic analysis.
    """
    # ---- Cemâlî dimensions (B01–B20) ---------------------------------
    B01 = "Rahman"          # Universal Mercy / Provision
    B02 = "Rahim"           # Particular Mercy / Compassion
    B03 = "Muhyi"           # Life-Giving
    B04 = "Musavvir"        # Form-Giving / Shaping
    B05 = "Rezzak"          # Sustenance / Nourishment
    B06 = "Hakim"           # Wisdom / Purposiveness
    B07 = "Alim"            # Knowledge / Distinction
    B08 = "Semi"            # Hearing / Receptivity
    B09 = "Basir"           # Seeing / Observation
    B10 = "Murid"           # Will / Selection
    B11 = "Kadir"           # Power / Actualization
    B12 = "Hayy"            # Life / Integration
    B13 = "Vedud"           # Love / Affection
    B14 = "Cemil"           # Beauty / Aesthetic Perfection
    B15 = "Kerim"           # Generosity / Abundance
    B16 = "Latif"           # Subtlety / Gentleness
    B17 = "Hafiz"           # Preservation / Memory
    B18 = "Mukaddir"        # Measure / Proportion
    B19 = "Mudabbir"        # Governance / Administration
    B20 = "Muni"            # Illumination / Manifestation

    # ---- Celâlî dimensions (B21–B22) — N-2/AX59 ---------------------
    B21 = "Celal_Adalet"    # Majesty / Justice
    B22 = "Kahhar_Tathir"   # Subduing / Purification (on behalf of Kuddüs)


# Dimension to type mapping
DIMENSION_TYPE: Dict[BesmeleBoyut, DimensionType] = {
    **{d: DimensionType.CEMALI for d in list(BesmeleBoyut)[:20]},
    BesmeleBoyut.B21: DimensionType.CELALI,
    BesmeleBoyut.B22: DimensionType.CELALI,
}

# Number of dimensions
NUM_DIMENSIONS = 22
NUM_CEMALI = 20
NUM_CELALI = 2

# Ordered list of all dimensions (for vector indexing)
ALL_DIMENSIONS: Tuple[BesmeleBoyut, ...] = tuple(BesmeleBoyut)
CEMALI_DIMENSIONS: Tuple[BesmeleBoyut, ...] = ALL_DIMENSIONS[:20]
CELALI_DIMENSIONS: Tuple[BesmeleBoyut, ...] = ALL_DIMENSIONS[20:]


# ---------------------------------------------------------------------------
# SeedVector — 22-dimensional holographic seed
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class SeedVector:
    """A 22-dimensional holographic seed vector.

    Each component is a continuous value in [0, 1) representing the
    degree of manifestation of the corresponding Besmele dimension
    (AX21 — infinite degrees, T6/KV₄ — bounded below 1.0).

    The vector encodes the holographic signature of a structural unit.
    Per KV₆, every valid module must have at least one non-zero component.
    """
    values: Tuple[float, ...]

    def __post_init__(self) -> None:
        if len(self.values) != NUM_DIMENSIONS:
            raise ValueError(
                f"SeedVector requires exactly {NUM_DIMENSIONS} components, "
                f"got {len(self.values)}"
            )
        for i, v in enumerate(self.values):
            if not isinstance(v, (int, float)):
                raise TypeError(
                    f"Component {i} must be numeric, got {type(v).__name__}"
                )

    @property
    def cemali(self) -> Tuple[float, ...]:
        """First 20 components (B01–B20): cemâlî dimensions."""
        return self.values[:NUM_CEMALI]

    @property
    def celali(self) -> Tuple[float, ...]:
        """Last 2 components (B21–B22): celâlî dimensions."""
        return self.values[NUM_CEMALI:]

    @property
    def has_celal(self) -> bool:
        """N-2/AX59: Are celâlî dimensions non-zero?"""
        return any(v > 0 for v in self.celali)

    @property
    def is_omnipresent(self) -> bool:
        """KV₆: Is the holographic seed detectable (at least one > 0)?"""
        return any(v > 0 for v in self.values)

    @property
    def nonzero_count(self) -> int:
        """Number of dimensions with positive value."""
        return sum(1 for v in self.values if v > 0)

    @property
    def zero_count(self) -> int:
        """Number of dimensions with zero value."""
        return sum(1 for v in self.values if v == 0)

    @property
    def magnitude(self) -> float:
        """Euclidean magnitude of the vector."""
        return math.sqrt(sum(v * v for v in self.values))

    def dimension_value(self, dim: BesmeleBoyut) -> float:
        """Get value for a specific dimension."""
        idx = ALL_DIMENSIONS.index(dim)
        return self.values[idx]

    def gate_score(self) -> float:
        """AX52 multiplicative gate: product of indicator functions.

        Returns 1.0 if ALL dimensions > 0, else 0.0.
        A zero in any single dimension collapses the entire gate.
        """
        for v in self.values:
            if v <= 0:
                return 0.0
        return 1.0

    def to_dict(self) -> dict:
        """Serialize for AX57 transparency."""
        return {
            dim.name: val
            for dim, val in zip(ALL_DIMENSIONS, self.values)
        }

    @staticmethod
    def zero() -> "SeedVector":
        """Create a zero vector (useful for comparison baseline)."""
        return SeedVector(values=tuple(0.0 for _ in range(NUM_DIMENSIONS)))

    @staticmethod
    def uniform(value: float) -> "SeedVector":
        """Create a uniform vector (all dimensions same value)."""
        return SeedVector(values=tuple(value for _ in range(NUM_DIMENSIONS)))


# ---------------------------------------------------------------------------
# HolographicUnit — a structural entity with its seed
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class HolographicUnit:
    """A structural unit with its 22-dimensional holographic seed.

    Represents any analysable entity (text, module, structural part)
    along with its computed holographic signature.

    Per AX37: every part mirrors the whole. The seed captures this.
    Per M3:   İnsan is the holographic seed of the cosmos (T13).
    """
    name: str
    seed: SeedVector
    level: str = ""         # optional: text level (besmele, fatiha, etc.)
    description: str = ""   # optional: textual description

    def __post_init__(self) -> None:
        if not self.name or not self.name.strip():
            raise ValueError("HolographicUnit name must be non-empty")


# ---------------------------------------------------------------------------
# FidelityPair — ne ayn ne gayr participation (T14)
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class FidelityPair:
    """A pair of entities with a fidelity measurement.

    Per T14 (Universal Ne Ayn Ne Gayr Chain):
        0 < Fidelity(source, derivative) < 1

    Fidelity measures the degree of structural participation:
    - 0 would mean total disconnect (impossible in the chain)
    - 1 would mean identity (impossible per KV₄)
    - Intermediate values: genuine participation without identity
    """
    source_name: str
    derivative_name: str
    fidelity: float

    def __post_init__(self) -> None:
        if not self.source_name or not self.source_name.strip():
            raise ValueError("source_name must be non-empty")
        if not self.derivative_name or not self.derivative_name.strip():
            raise ValueError("derivative_name must be non-empty")
        if not isinstance(self.fidelity, (int, float)):
            raise TypeError("fidelity must be numeric")

    @property
    def is_valid(self) -> bool:
        """T14: Fidelity must be strictly between 0 and 1."""
        return 0 < self.fidelity < 1


# ---------------------------------------------------------------------------
# IsomorphismResult — holographic structural comparison (AX37, T12)
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class IsomorphismResult:
    """Result of holographic isomorphism comparison.

    Per AX37: Structure(Part) ≅ Structure(Whole)
    Per T12: Structure(Besmele) ≅ Structure(Fatiha) ≅ Structure(Kur'an)

    The similarity score measures the degree of structural isomorphism
    between two units' seed vectors.
    """
    unit_a_name: str
    unit_b_name: str
    similarity: float       # cosine similarity in [0, 1)
    shared_dimensions: int   # how many dimensions both have > 0
    is_isomorphic: bool      # whether they meet the isomorphism threshold

    def __post_init__(self) -> None:
        if not isinstance(self.similarity, (int, float)):
            raise TypeError("similarity must be numeric")
        if not isinstance(self.shared_dimensions, int):
            raise TypeError("shared_dimensions must be int")


# ---------------------------------------------------------------------------
# HolographicModel — the aggregate root
# ---------------------------------------------------------------------------

class HolographicModel:
    """Complete holographic analysis model.

    Aggregates holographic units, fidelity pairs, isomorphism results,
    and composition information for the Topoloji + Holografik lens.

    Dual faculty coverage: Ruh (topological) + Hafî (holographic).
    Dual instrument: holografik_sabitler + holografik_analiz.

    Structural constraints:
      AX37:  Holographic structure — parts mirror whole
      KV₆:   Seed omnipresence — every unit must have seed > 0
      AX52:  Multiplicative gate — zero in any dimension = collapse
      T14:   Ne ayn ne gayr — 0 < fidelity < 1
      AX63:  Tesanüd/İnfirâd composition
      T17:   Coverage ≤ 6/7 (Ahfâ permanently unmapped)
    """

    def __init__(self, name: str = "") -> None:
        self.name = name
        self._units: Dict[str, HolographicUnit] = {}
        self._fidelity_pairs: List[FidelityPair] = []
        self._isomorphism_results: List[IsomorphismResult] = []
        self.composition_mode: CompositionMode = CompositionMode.INDEPENDENT

    # ---- units --------------------------------------------------------

    @property
    def units(self) -> List[HolographicUnit]:
        return list(self._units.values())

    def add_unit(self, unit: HolographicUnit) -> None:
        """Add a holographic unit to the model."""
        if not isinstance(unit, HolographicUnit):
            raise TypeError("Expected HolographicUnit")
        self._units[unit.name] = unit

    def get_unit(self, name: str) -> Optional[HolographicUnit]:
        return self._units.get(name)

    # ---- fidelity pairs -----------------------------------------------

    @property
    def fidelity_pairs(self) -> List[FidelityPair]:
        return list(self._fidelity_pairs)

    def add_fidelity_pair(self, pair: FidelityPair) -> None:
        if not isinstance(pair, FidelityPair):
            raise TypeError("Expected FidelityPair")
        self._fidelity_pairs.append(pair)

    # ---- isomorphism results ------------------------------------------

    @property
    def isomorphism_results(self) -> List[IsomorphismResult]:
        return list(self._isomorphism_results)

    def add_isomorphism(self, result: IsomorphismResult) -> None:
        if not isinstance(result, IsomorphismResult):
            raise TypeError("Expected IsomorphismResult")
        self._isomorphism_results.append(result)

    # ---- computed properties ------------------------------------------

    @property
    def all_seeds_omnipresent(self) -> bool:
        """KV₆: every unit has a detectable seed (at least one dim > 0)."""
        if not self._units:
            return True  # vacuously true
        return all(u.seed.is_omnipresent for u in self._units.values())

    @property
    def all_fidelities_valid(self) -> bool:
        """T14: all fidelity values strictly between 0 and 1."""
        if not self._fidelity_pairs:
            return True  # vacuously true
        return all(p.is_valid for p in self._fidelity_pairs)

    @property
    def celal_included(self) -> bool:
        """N-2/AX59: at least one unit has non-zero celâlî dimensions."""
        if not self._units:
            return True  # vacuously true
        return any(u.seed.has_celal for u in self._units.values())

    @property
    def mean_similarity(self) -> float:
        """Average isomorphism similarity across all results."""
        if not self._isomorphism_results:
            return 0.0
        total = sum(r.similarity for r in self._isomorphism_results)
        return total / len(self._isomorphism_results)

    @property
    def tesanud_strength(self) -> float:
        """AX63: super-additive composition strength.

        When units converge on similar seeds (tesanüd), their combined
        evidence is *greater* than the sum of individual detections.
        """
        if not self._units or len(self._units) < 2:
            return 0.0
        seeds = [u.seed for u in self._units.values()]
        n = len(seeds)
        total_sim = 0.0
        pairs = 0
        for i in range(n):
            for j in range(i + 1, n):
                total_sim += _cosine_similarity(seeds[i], seeds[j])
                pairs += 1
        if pairs == 0:
            return 0.0
        return clamp_score(total_sim / pairs)

    def compute_isomorphism(
        self,
        unit_a_name: str,
        unit_b_name: str,
        threshold: float = 0.5,
    ) -> Optional[IsomorphismResult]:
        """Compute holographic isomorphism between two units (AX37).

        Uses cosine similarity of seed vectors. A similarity above
        ``threshold`` is considered isomorphic.
        """
        a = self.get_unit(unit_a_name)
        b = self.get_unit(unit_b_name)
        if a is None or b is None:
            return None

        sim = _cosine_similarity(a.seed, b.seed)
        shared = sum(
            1 for va, vb in zip(a.seed.values, b.seed.values)
            if va > 0 and vb > 0
        )
        result = IsomorphismResult(
            unit_a_name=unit_a_name,
            unit_b_name=unit_b_name,
            similarity=clamp_score(sim),
            shared_dimensions=shared,
            is_isomorphic=sim >= threshold,
        )
        self._isomorphism_results.append(result)
        return result

    def compute_fidelity(
        self, source_name: str, derivative_name: str,
    ) -> Optional[FidelityPair]:
        """Compute fidelity between source and derivative (T14).

        Fidelity = cosine similarity, constrained to (0, 1).
        """
        src = self.get_unit(source_name)
        der = self.get_unit(derivative_name)
        if src is None or der is None:
            return None
        sim = _cosine_similarity(src.seed, der.seed)
        # Clamp to (0, 1) exclusive — T14: 0 < fidelity < 1
        fidelity = max(0.0001, min(sim, 0.9999))
        pair = FidelityPair(
            source_name=source_name,
            derivative_name=derivative_name,
            fidelity=fidelity,
        )
        self._fidelity_pairs.append(pair)
        return pair

    # ---- serialization (AX57 transparency) ----------------------------

    def to_dict(self) -> dict:
        """Serialize the entire model for AX57 transparency."""
        return {
            "name": self.name,
            "units": [
                {
                    "name": u.name,
                    "level": u.level,
                    "description": u.description,
                    "seed": u.seed.to_dict(),
                }
                for u in self._units.values()
            ],
            "fidelity_pairs": [
                {
                    "source": p.source_name,
                    "derivative": p.derivative_name,
                    "fidelity": p.fidelity,
                }
                for p in self._fidelity_pairs
            ],
            "isomorphism_results": [
                {
                    "unit_a": r.unit_a_name,
                    "unit_b": r.unit_b_name,
                    "similarity": r.similarity,
                    "shared_dimensions": r.shared_dimensions,
                    "is_isomorphic": r.is_isomorphic,
                }
                for r in self._isomorphism_results
            ],
            "composition_mode": self.composition_mode.value,
            "all_seeds_omnipresent": self.all_seeds_omnipresent,
            "all_fidelities_valid": self.all_fidelities_valid,
            "celal_included": self.celal_included,
            "tesanud_strength": self.tesanud_strength,
        }


# ---------------------------------------------------------------------------
# Module-level helpers
# ---------------------------------------------------------------------------

def _cosine_similarity(a: SeedVector, b: SeedVector) -> float:
    """Compute cosine similarity between two seed vectors.

    Returns a value in [0, 1] (since all components are ≥ 0).
    """
    dot = sum(va * vb for va, vb in zip(a.values, b.values))
    mag_a = a.magnitude
    mag_b = b.magnitude
    if mag_a == 0 or mag_b == 0:
        return 0.0
    return dot / (mag_a * mag_b)
