"""
Belief Revision System - Core Types and Utilities

Domain-agnostic foundation for belief revision. All types are generic and
can be specialized for any domain (historical writing systems, biological
taxonomies, legal precedent networks, etc.).
"""
from __future__ import annotations

import dataclasses
import hashlib
import json
from dataclasses import dataclass, field
from typing import Any, Dict, FrozenSet, List, Optional, Tuple, Union

# =============================================================================
# Canonical Serialization
# =============================================================================

def canonical_json(obj: Any) -> str:
    """
    Canonical JSON serialization for content-addressable storage.
    - Sorted keys for determinism
    - No whitespace for compactness
    - Consistent across Python versions
    """
    return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)


def content_hash(obj: Any) -> str:
    """SHA-256 hash of canonical JSON representation."""
    s = canonical_json(obj)
    return hashlib.sha256(s.encode("utf-8")).hexdigest()


def to_dict(obj: Any) -> Any:
    """Convert dataclass to dict for serialization."""
    if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
        return dataclasses.asdict(obj)
    return obj


# =============================================================================
# Generic Domain Types
# =============================================================================

@dataclass(frozen=True)
class TimeRange:
    """
    A temporal range for provenance tracking.

    Can represent:
    - Precise dates (start_year == end_year)
    - Ranges (start_year != end_year)
    - Open-ended (start_year or end_year is None)
    """
    start_year: Optional[int] = None
    end_year: Optional[int] = None
    precision: str = "year"  # year | decade | century | approximate

    def __str__(self) -> str:
        if self.start_year == self.end_year and self.start_year is not None:
            return f"{self.start_year}"
        s = str(self.start_year) if self.start_year else "?"
        e = str(self.end_year) if self.end_year else "?"
        return f"{s}–{e}"


@dataclass(frozen=True)
class Evidence:
    """
    A piece of evidence supporting beliefs in the system.

    Generic evidence structure that can represent:
    - Academic citations
    - Physical artifacts
    - Observational data
    - Expert testimony
    """
    id: str
    citation: str
    kind: str                      # Domain-specific type (e.g., "inscription", "specimen", "case_law")
    reliability: str               # A | B | C or domain-specific scale
    date: Optional[TimeRange] = None
    location: Optional[str] = None
    extract: Optional[str] = None  # Relevant excerpt
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True)
class Node:
    """
    A belief node in the knowledge graph.

    Generic node that can represent any domain concept:
    - Writing systems: scripts, glyphs, orthographies
    - Biology: species, genera, families
    - Law: cases, statutes, principles
    """
    id: str
    name: str
    node_type: str                 # Domain-specific categorization
    properties: Dict[str, Any] = field(default_factory=dict)
    first_attested: Optional[TimeRange] = None
    regions: Tuple[str, ...] = ()
    notes: str = ""

    def __hash__(self) -> int:
        return hash(self.id)


@dataclass(frozen=True)
class Edge:
    """
    A directed relationship between nodes.

    Represents derivation, influence, or other relationships with
    confidence scoring and evidence backing.
    """
    id: str
    parent_id: str
    child_id: str
    relation: str                  # Domain-specific relation type
    tier: int = 0                  # 0 = strongest, 5 = weakest
    confidence: float = 1.0        # 0.0 to 1.0
    evidence_ids: Tuple[str, ...] = ()
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True)
class Pattern:
    """
    A pattern or invariant that holds across the domain.

    Patterns capture regularities, rules, or constraints that the
    belief system should respect.
    """
    id: str
    name: str
    summary: str
    invariants: Tuple[str, ...]    # Constraint descriptions
    operators: Tuple[str, ...] = () # Transformation operators
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True)
class WorldBundle:
    """
    A complete world state for a domain at a specific version.

    World bundles are immutable snapshots. New versions are created
    by forking and modifying, enabling non-destructive hypothesis testing.
    """
    domain_id: str
    version_label: str             # e.g., "green", "blue", "_shadow_eval"
    node_ids: Tuple[str, ...]
    edge_ids: Tuple[str, ...]
    evidence_ids: Tuple[str, ...]
    pattern_ids: Tuple[str, ...]
    created_utc: str
    notes: str = ""
    metadata: Dict[str, Any] = field(default_factory=dict)


# =============================================================================
# Maturity Scoring
# =============================================================================

@dataclass
class Maturity:
    """
    Maturity assessment for a belief system state.

    Combines multiple factors:
    - coverage: How much of the domain is represented
    - compression: Ratio of explanatory power to complexity
    - stability: How well the system passes invariant tests
    - contradiction_debt: Accumulated inconsistencies
    """
    coverage: float
    compression: float
    stability: float
    contradiction_debt: float

    # Configurable weights (can be overridden per-domain)
    COVERAGE_WEIGHT: float = 0.4
    COMPRESSION_WEIGHT: float = 0.3
    STABILITY_WEIGHT: float = 0.2
    DEBT_WEIGHT: float = 0.3

    @property
    def score(self) -> float:
        """Compute weighted maturity score. Higher is better."""
        return (
            self.coverage * self.COVERAGE_WEIGHT +
            self.compression * self.COMPRESSION_WEIGHT +
            self.stability * self.STABILITY_WEIGHT -
            self.contradiction_debt * self.DEBT_WEIGHT
        )

    def __str__(self) -> str:
        return (
            f"Maturity(score={self.score:.3f}, "
            f"coverage={self.coverage:.2f}, "
            f"compression={self.compression:.2f}, "
            f"stability={self.stability:.2f}, "
            f"debt={self.contradiction_debt:.2f})"
        )


def estimate_maturity(
    num_nodes: int,
    num_edges: int,
    num_tests: int = 1,
    num_failures: int = 0,
    target_nodes: int = 50
) -> Maturity:
    """
    Estimate maturity from basic counts.

    Args:
        num_nodes: Number of nodes in the world
        num_edges: Number of edges in the world
        num_tests: Number of smoke tests run
        num_failures: Number of test failures
        target_nodes: Expected node count for full coverage (domain-specific)

    Returns:
        Maturity assessment
    """
    coverage = min(1.0, num_nodes / target_nodes)
    compression = min(1.0, (num_edges / max(1, num_nodes)) * 0.8)
    stability = max(0.0, 1.0 - (num_failures / max(1, num_tests)))
    contradiction_debt = 0.0  # Future: compute from inconsistency detection
    return Maturity(coverage, compression, stability, contradiction_debt)


# =============================================================================
# Proposal Types
# =============================================================================

@dataclass(frozen=True)
class Proposal:
    """
    A proposal for belief revision.

    Generic container for any revision hypothesis:
    - Analog proposals (cross-domain pattern transfer)
    - Node additions
    - Edge modifications
    - Pattern imports
    """
    id: str
    kind: str                      # "AnalogProposal", "NodeProposal", etc.
    source_domain: Optional[str]
    target_domain: str
    payload: Dict[str, Any]        # Kind-specific data
    status: str                    # "new", "accepted", "rejected", "archived"
    score: float
    created_utc: str
    notes: str = ""


# =============================================================================
# Type Aliases
# =============================================================================

ObjectKind = str
ObjectHash = str
DomainId = str
VersionLabel = str
