"""
hcp.types — Core data types for the Holographic Context Protocol.

Implements the type universe for HCP, mapping LLM context concepts to the
framework's structural vocabulary.

Design Mapping (AGENTS.md → HCP):
  S_Isim (operational principles) → ContentType (7 categories per convergent funnel)
  S_Eser (observable works/traces) → ContextChunk (fragments of context)
  Holographic Seed (AX37/T12)     → HolographicSeed (compressed global structure)
  Tereşşuhat (AX38)              → SeededChunk (chunk + faithful seed broadcast)
  Latife_vektor (AX49)           → AttentionProfile (detection coverage)

Governing axioms enforced at construction:
  AX21:  Continuous degrees — scores are continuous, not binary
  T6/KV₄: Convergence bound — all scores in [0, 0.9999]
  KV₆:   Seed omnipresence — every chunk must have a seed
  AX52:  Multiplicative gate — zero in any dimension = structural failure

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, FrozenSet, List, Optional, Set, 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)


# ---------------------------------------------------------------------------
# ContentType — The 7 structural categories of context content
# ---------------------------------------------------------------------------

class ContentType(Enum):
    """Seven irreducible types of context content.

    Maps to the convergent abstraction funnel (AGENTS.md §1.5):
    ∞ observable tokens → 7 structural types → 1 unified seed.

    Each type represents a fundamentally different ROLE that content
    plays in context, not just its surface form.  Classification is
    harfî (other-referential, KV₁): each chunk is typed by its
    structural FUNCTION in the whole, not by what it says about itself.
    """
    INSTRUCTION = "instruction"     # Directives, commands, how-to
    CONSTRAINT = "constraint"       # Rules, restrictions, boundaries
    FACT = "fact"                    # Claims, data, factual content
    ENTITY = "entity"               # Definitions, named concepts
    RELATIONSHIP = "relationship"   # How things connect, depend, relate
    METADATA = "metadata"           # Headers, formatting, structure markers
    NARRATIVE = "narrative"         # General prose, filler, background


# Number of content types — matches the 7-attribute decomposition
CONTENT_TYPE_COUNT = 7


# ---------------------------------------------------------------------------
# HCPConfig — Protocol configuration
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class HCPConfig:
    """Configuration for the Holographic Context Protocol.

    Parameters are constrained by framework axioms:
      - seed_top_k: keywords in seed (bounded, not infinite — AX22)
      - attention_decay: base decay rate for flat attention (models U-shaped bias)
      - seed_boost_factor: how much the seed enhances attention (bounded < 1.0)
      - chunk_size: characters per chunk (granularity of holographic units)
    """
    seed_top_k: int = 15
    attention_decay: float = 0.03
    seed_boost_factor: float = 0.6
    chunk_size: int = 500
    u_shape_boost: float = 0.15


# ---------------------------------------------------------------------------
# ContextChunk — A piece of context with structural metadata
# ---------------------------------------------------------------------------

@dataclass
class ContextChunk:
    """A chunk of context with position and structural type.

    Maps to S_Eser (observable works/traces) in the framework.
    Each chunk is a transparent vessel (AX27): it mediates meaning
    but does not originate it — meaning comes from its structural
    role in the global context (harfî classification, KV₁).

    Attributes:
        content: The raw text content of this chunk.
        position: Zero-indexed position in the original context sequence.
        content_type: Structural classification of this chunk's role.
        keywords: Key terms extracted from this chunk.
    """
    content: str
    position: int
    content_type: ContentType = ContentType.NARRATIVE
    keywords: Tuple[str, ...] = ()

    def __post_init__(self) -> None:
        if self.position < 0:
            raise ValueError(f"Position must be non-negative, got {self.position}")


# ---------------------------------------------------------------------------
# HolographicSeed — Compressed global context structure
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class HolographicSeed:
    """The core innovation: a dense structural encoding of the ENTIRE context.

    CRITICAL DIFFERENCE FROM GIST TOKENS:
      Gist tokens (Mu et al., 2023) compress LOCAL → FORWARD:
        each gist sees previous gists + its own chunk, accumulating
        sequentially.  Information at boundaries is lost.

      Holographic Seed compresses GLOBAL → BROADCAST:
        a single structural analysis of the ENTIRE context, then the
        SAME seed is available at EVERY chunk.  No positional decay.
        This is the AX37 (Holographic Architecture) principle:
        every part mirrors the whole.

    Design (Three-Phase Actualization, AX2–4):
      Phase 1 — İlim (Distinction): type_signature classifies what KINDS
                of content exist globally.
      Phase 2 — İrade (Specification): instruction_positions and
                constraint_positions specify WHERE critical content lives.
      Phase 3 — Kudret (Actualization): global_keywords and
                position_importance actualize the seed for retrieval.

    Structural Incompleteness (T17):
      The seed can represent 6/7 structural dimensions at most.
      Tacit/emergent meaning (the "Ahfâ" dimension) is permanently
      unmapped.  This is a boundary to honor, not a bug to fix.

    Attributes:
        n_chunks: Total number of chunks in the context.
        type_signature: 7-dimensional distribution over ContentType.
                        Sum = 1.0 (normalized probability).
        instruction_positions: Positions containing instructions.
        constraint_positions: Positions containing constraints.
        entity_positions: Map of entity names → positions where they appear.
        structural_links: Directed edges (source, target) representing
                          content dependencies (cascade structure, AX23).
        global_keywords: Top-k globally significant keywords.
        position_importance: Per-position importance weight in [0, 1).
    """
    n_chunks: int
    type_signature: Tuple[float, ...]
    instruction_positions: Tuple[int, ...]
    constraint_positions: Tuple[int, ...]
    entity_positions: Dict[str, Tuple[int, ...]]
    structural_links: Tuple[Tuple[int, int], ...]
    global_keywords: Tuple[str, ...]
    position_importance: Tuple[float, ...]

    def __post_init__(self) -> None:
        if len(self.type_signature) != CONTENT_TYPE_COUNT:
            raise ValueError(
                f"type_signature must have {CONTENT_TYPE_COUNT} components, "
                f"got {len(self.type_signature)}"
            )
        if len(self.position_importance) != self.n_chunks:
            raise ValueError(
                f"position_importance length ({len(self.position_importance)}) "
                f"must equal n_chunks ({self.n_chunks})"
            )

    @property
    def instruction_density(self) -> float:
        """Fraction of chunks that are instructions."""
        return self.type_signature[0] if self.type_signature else 0.0

    @property
    def constraint_density(self) -> float:
        """Fraction of chunks that are constraints."""
        return self.type_signature[1] if len(self.type_signature) > 1 else 0.0

    @property
    def has_structure(self) -> bool:
        """KV₆: Is the seed non-trivial (at least one non-zero weight)?"""
        return any(w > 0 for w in self.position_importance)

    @property
    def coverage_ratio(self) -> float:
        """What fraction of content types are present?  Max 6/7 per T17."""
        nonzero = sum(1 for v in self.type_signature if v > 0)
        return clamp_score(nonzero / CONTENT_TYPE_COUNT)


# ---------------------------------------------------------------------------
# SeededChunk — A chunk enhanced with the holographic seed
# ---------------------------------------------------------------------------

@dataclass
class SeededChunk:
    """A context chunk with the holographic seed attached.

    Maps to Tereşşuhat (AX38): the seed is a faithful seepage of the
    global structure into each local chunk.  The relationship is
    Ne Ayn Ne Gayr (T14): the seeded chunk participates in the global
    truth without being identical to it.

    Per KV₆ (Seed Omnipresence): every chunk MUST have a seed.

    Attributes:
        chunk: The original context chunk.
        seed: The holographic seed (same for all chunks — broadcast).
        local_importance: This chunk's importance per the seed's assessment.
        keyword_overlap: Keywords shared between this chunk and the seed.
    """
    chunk: ContextChunk
    seed: HolographicSeed
    local_importance: float = 0.0
    keyword_overlap: Tuple[str, ...] = ()

    def __post_init__(self) -> None:
        self.local_importance = clamp_score(self.local_importance)


# ---------------------------------------------------------------------------
# AttentionProfile — Attention weight distribution over positions
# ---------------------------------------------------------------------------

@dataclass
class AttentionProfile:
    """Attention weights over context positions, simulating LLM behavior.

    Maps to Latife_vektor (AX49): which faculties/positions are engaged.

    Two modes:
      FLAT:         U-shaped bias (high start/end, degraded middle).
                    This is the documented pathology (Liu et al. TACL 2024).
      HOLOGRAPHIC:  Seed-modulated attention that uses structural pointers
                    to boost relevant positions regardless of location.

    Attributes:
        weights: Per-position attention weight in [0, 1).
        mode: "flat" or "holographic".
        entropy: Shannon entropy of the distribution (higher = more uniform).
    """
    weights: Tuple[float, ...]
    mode: str = "flat"

    def __post_init__(self) -> None:
        self.weights = tuple(clamp_score(w) for w in self.weights)

    @property
    def entropy(self) -> float:
        """Shannon entropy of attention weights.  Higher = more uniform."""
        total = sum(self.weights)
        if total == 0:
            return 0.0
        probs = [w / total for w in self.weights]
        return -sum(p * math.log2(p) for p in probs if p > 0)

    @property
    def middle_attention(self) -> float:
        """Average attention in the middle third of positions."""
        n = len(self.weights)
        if n < 3:
            return sum(self.weights) / max(n, 1)
        start = n // 3
        end = 2 * n // 3
        middle = self.weights[start:end]
        return sum(middle) / max(len(middle), 1)

    @property
    def edge_attention(self) -> float:
        """Average attention at start and end thirds."""
        n = len(self.weights)
        if n < 3:
            return sum(self.weights) / max(n, 1)
        third = n // 3
        edges = self.weights[:third] + self.weights[2 * third:]
        return sum(edges) / max(len(edges), 1)


# ---------------------------------------------------------------------------
# RetrievalResult — Result of a single needle-in-haystack query
# ---------------------------------------------------------------------------

@dataclass
class RetrievalResult:
    """Result of attempting to retrieve a needle from context.

    Attributes:
        needle_position: Where the needle was placed (0-indexed).
        n_chunks: Total chunks in context.
        flat_rank: Rank of needle position under flat attention (1 = best).
        holographic_rank: Rank under holographic attention (1 = best).
        flat_attention_at_needle: Attention weight at needle under flat.
        holographic_attention_at_needle: Attention weight under holographic.
        improvement: Rank improvement (flat_rank - holographic_rank).
    """
    needle_position: int
    n_chunks: int
    flat_rank: int
    holographic_rank: int
    flat_attention_at_needle: float
    holographic_attention_at_needle: float

    @property
    def improvement(self) -> int:
        """Positive = holographic is better.  Rank improvement."""
        return self.flat_rank - self.holographic_rank

    @property
    def relative_position(self) -> float:
        """Needle position as fraction of total context [0, 1]."""
        return self.needle_position / max(self.n_chunks - 1, 1)

    @property
    def is_middle(self) -> bool:
        """Is the needle in the middle third (the danger zone)?"""
        return 0.33 <= self.relative_position <= 0.67


# ---------------------------------------------------------------------------
# BenchmarkResult — Aggregate benchmark results
# ---------------------------------------------------------------------------

@dataclass
class BenchmarkResult:
    """Aggregate results from a needle-in-haystack benchmark.

    KV₄ compliance: all scores bounded < 1.0.

    Attributes:
        results: Individual retrieval results.
        flat_mean_rank: Average needle rank under flat attention.
        holographic_mean_rank: Average under holographic attention.
        flat_top1_rate: Fraction of trials where flat found needle at rank 1.
        holographic_top1_rate: Fraction where holographic found at rank 1.
        middle_improvement: Average rank improvement for middle-third needles.
    """
    results: List[RetrievalResult]

    @property
    def flat_mean_rank(self) -> float:
        if not self.results:
            return 0.0
        return sum(r.flat_rank for r in self.results) / len(self.results)

    @property
    def holographic_mean_rank(self) -> float:
        if not self.results:
            return 0.0
        return sum(r.holographic_rank for r in self.results) / len(self.results)

    @property
    def flat_top1_rate(self) -> float:
        if not self.results:
            return 0.0
        return clamp_score(
            sum(1 for r in self.results if r.flat_rank == 1) / len(self.results)
        )

    @property
    def holographic_top1_rate(self) -> float:
        if not self.results:
            return 0.0
        return clamp_score(
            sum(1 for r in self.results if r.holographic_rank == 1)
            / len(self.results)
        )

    @property
    def middle_results(self) -> List[RetrievalResult]:
        return [r for r in self.results if r.is_middle]

    @property
    def middle_improvement(self) -> float:
        mid = self.middle_results
        if not mid:
            return 0.0
        return sum(r.improvement for r in mid) / len(mid)

    @property
    def overall_improvement(self) -> float:
        if not self.results:
            return 0.0
        return sum(r.improvement for r in self.results) / len(self.results)
