#!/usr/bin/env python3
"""Reusable recursive MindCell object and deterministic fixed-point engine.

The module implements structural recursion and retained state. It deliberately
contains no authority-promotion, consciousness, moral-ranking, or phi-based
stopping behavior.
"""
from __future__ import annotations

import copy
import hashlib
import json
from dataclasses import dataclass
from typing import Callable, Literal, Mapping

RoleName = Literal["inside", "outside", "relation"]
CellState = Literal["instantiated", "blueprint_only", "partial"]
OCCUPANT_TYPES = {"identity", "identity_set", "mind_cell", "channel", "private_learner_state", "comparison_context"}


def stable_digest(value) -> str:
    encoded = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
    return hashlib.sha256(encoded).hexdigest()


@dataclass(frozen=True)
class MindRole:
    role: RoleName
    occupant_type: str
    refs: tuple[str, ...]
    description: str

    def __post_init__(self):
        if self.role not in {"inside", "outside", "relation"}:
            raise ValueError(f"Unsupported Mind role: {self.role}")
        if self.occupant_type not in OCCUPANT_TYPES:
            raise ValueError(f"Unsupported occupant type: {self.occupant_type}")
        if not self.refs or len(self.refs) != len(set(self.refs)):
            raise ValueError(f"Mind role {self.role} requires non-empty unique references")
        if not self.description.strip():
            raise ValueError(f"Mind role {self.role} requires a description")

    def to_dict(self):
        return {
            "role": self.role,
            "occupant_type": self.occupant_type,
            "refs": list(self.refs),
            "description": self.description,
        }


@dataclass(frozen=True)
class ChangeCycle:
    contact: str
    difference: str
    transformation: str
    residue: str
    return_path: str
    closure: str

    def __post_init__(self):
        values = (self.contact, self.difference, self.transformation, self.residue, self.return_path, self.closure)
        if any(not isinstance(value, str) or not value.strip() for value in values):
            raise ValueError("Every change-cycle stage requires a non-empty description")

    def to_dict(self):
        return {
            "contact": self.contact,
            "difference": self.difference,
            "transformation": self.transformation,
            "residue": self.residue,
            "return": self.return_path,
            "closure": self.closure,
        }


@dataclass(frozen=True)
class MindCell:
    identifier: str
    title: str
    reference_scope: str
    state: CellState
    inside: MindRole
    outside: MindRole
    relation: MindRole
    change_cycle: ChangeCycle
    child_mind_refs: tuple[str, ...]
    capability_boundary: str

    def __post_init__(self):
        if not self.identifier.startswith("MC-"):
            raise ValueError("MindCell identifiers must start with MC-")
        if not self.title.strip():
            raise ValueError("MindCell titles are required")
        if self.state not in {"instantiated", "blueprint_only", "partial"}:
            raise ValueError(f"Unsupported MindCell state: {self.state}")
        if (self.inside.role, self.outside.role, self.relation.role) != ("inside", "outside", "relation"):
            raise ValueError("A MindCell requires exactly one inside, outside, and relation role")
        if len(self.child_mind_refs) != len(set(self.child_mind_refs)):
            raise ValueError("Child MindCell references must be unique")
        if any(not identifier.startswith("MC-") for identifier in self.child_mind_refs):
            raise ValueError("Child MindCell references must start with MC-")
        if not self.reference_scope.strip() or not self.capability_boundary.strip():
            raise ValueError("MindCell scope and capability boundary are required")

    def recursive_children(self, registry: Mapping[str, "MindCell"]):
        """Resolve only declared child cell references; identity refs remain external."""
        missing = [identifier for identifier in self.child_mind_refs if identifier not in registry]
        if missing:
            raise KeyError(f"Unresolved child MindCell references for {self.identifier}: {missing}")
        return tuple(registry[identifier] for identifier in self.child_mind_refs)

    def validate_registry(self, registry: Mapping[str, "MindCell"]):
        children = self.recursive_children(registry)
        typed_refs = {
            reference
            for role in (self.inside, self.outside, self.relation)
            if role.occupant_type == "mind_cell"
            for reference in role.refs
        }
        unresolved = sorted(reference for reference in typed_refs if reference not in registry)
        undeclared = sorted(reference for reference in typed_refs if reference not in self.child_mind_refs)
        unowned = sorted(reference for reference in self.child_mind_refs if reference not in typed_refs)
        if unresolved:
            raise KeyError(f"Unresolved typed MindCell role references for {self.identifier}: {unresolved}")
        if undeclared:
            raise ValueError(f"Typed MindCell role references must be declared children for {self.identifier}: {undeclared}")
        if unowned:
            raise ValueError(f"Child MindCell references require a typed role owner for {self.identifier}: {unowned}")
        return children

    def to_dict(self):
        return {
            "id": self.identifier,
            "title": self.title,
            "reference_scope": self.reference_scope,
            "state": self.state,
            "roles": {
                "inside": self.inside.to_dict(),
                "outside": self.outside.to_dict(),
                "relation": self.relation.to_dict(),
            },
            "change_cycle": self.change_cycle.to_dict(),
            "child_mind_refs": list(self.child_mind_refs),
            "capability_boundary": self.capability_boundary,
        }


@dataclass(frozen=True)
class IterationResult:
    state: object
    state_digest: str
    passes: int
    converged: bool
    history: tuple[str, ...]


class FixedPointRunner:
    """Generic deterministic runner; it carries no Y-Theory Mind identity."""

    def __init__(self, transform: Callable[[object], object], max_passes: int = 32):
        if max_passes < 1:
            raise ValueError("max_passes must be positive")
        self.transform = transform
        self.max_passes = max_passes

    def run(self, initial_state) -> IterationResult:
        current = copy.deepcopy(initial_state)
        current_digest = stable_digest(current)
        history = [current_digest]
        for pass_number in range(1, self.max_passes + 1):
            following = self.transform(copy.deepcopy(current))
            following_digest = stable_digest(following)
            history.append(following_digest)
            if following_digest == current_digest:
                return IterationResult(following, following_digest, pass_number, True, tuple(history))
            current = following
            current_digest = following_digest
        return IterationResult(current, current_digest, self.max_passes, False, tuple(history))


class RecursiveMindEngine(FixedPointRunner):
    """A fixed-point runner bound to one instantiated engine MindCell."""

    def __init__(
        self,
        transform: Callable[[object], object],
        *,
        registry: Mapping[str, MindCell],
        engine_cell_id: str,
        max_passes: int = 32,
    ):
        mismatched_keys = sorted(key for key, cell in registry.items() if key != cell.identifier)
        if mismatched_keys:
            raise ValueError(f"MindCell registry keys must equal cell identifiers: {mismatched_keys}")
        if engine_cell_id not in registry:
            raise KeyError(f"Engine MindCell does not resolve: {engine_cell_id}")
        if registry[engine_cell_id].state != "instantiated":
            raise ValueError(f"Engine MindCell must be instantiated: {engine_cell_id}")
        for cell in registry.values():
            cell.validate_registry(registry)
        super().__init__(transform, max_passes=max_passes)
        self.registry = registry
        self.engine_cell_id = engine_cell_id
