# -*- coding: utf-8 -*-
"""
DungLang Civilization v2.1
===========================
Extended version: Temporal / Archaeology / Civilization Federation

  v2.0:
    - OdorOptimizer
    - Kont Federation
    - History Federation
    - Ghost Odor
    - Gorilla Incompleteness
    - Replay Civilization

  v2.1:
    - Civilization Event Store
    - Temporal Banana Rewrite
    - Official History vs Archaeological Trace
    - Ghost Odor Archaeology
    - Civilization Federation Comparator
    - Causality Leak Detector

This is a presentation / research-toy prototype.
Not a complete compiler, formal proof, or distributed transaction system.
UHO 🦍📜🔥🍌💨🌍
"""
from __future__ import annotations

import json
import sys
from dataclasses import dataclass, field
from typing import Any, Optional


if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8")
    sys.stderr.reconfigure(encoding="utf-8")


# ══════════════════════════════════════════════════════════════
# §0 Exceptions / Values
# ══════════════════════════════════════════════════════════════

class ScatError(Exception):
    pass

class StepLimitError(ScatError):
    pass

class PhilosophyGorillaError(ScatError):
    pass

class CrossBorderOdorError(ScatError):
    pass

class TemporalParadoxWarning(Warning):
    pass


class Value:
    def pretty(self) -> str:
        raise NotImplementedError

    def to_int(self) -> int:
        raise NotImplementedError


@dataclass(frozen=True)
class PoopZero(Value):
    def pretty(self) -> str:
        return "💩₀"

    def to_int(self) -> int:
        return 0


@dataclass(frozen=True)
class PoopSucc(Value):
    inner: Value

    def pretty(self) -> str:
        return f"💩×{self.to_int()}"

    def to_int(self) -> int:
        return 1 + self.inner.to_int()


@dataclass(frozen=True)
class Underflow(Value):
    def pretty(self) -> str:
        return "💩∅"

    def to_int(self) -> int:
        return 0


def int_to_poop(n: int) -> Value:
    if n < 0:
        raise ValueError("DungLang natural odor cannot be negative")
    v: Value = PoopZero()
    for _ in range(n):
        v = PoopSucc(v)
    return v


TRUE = PoopSucc(PoopZero())
FALSE = PoopZero()


# ══════════════════════════════════════════════════════════════
# §1 Odor IR
# ══════════════════════════════════════════════════════════════

class Expr:
    pass

class Stmt:
    pass


@dataclass(frozen=True)
class PoopZeroExpr(Expr):
    pass

@dataclass(frozen=True)
class PoopSuccExpr(Expr):
    expr: Expr

@dataclass(frozen=True)
class OdorLevelExpr(Expr):
    level: int

@dataclass(frozen=True)
class Var(Expr):
    name: str

@dataclass(frozen=True)
class OdorAdd(Expr):
    left: Expr
    right: Expr

@dataclass(frozen=True)
class OdorSub(Expr):
    left: Expr
    right: Expr

@dataclass(frozen=True)
class OdorNeq(Expr):
    left: Expr
    right: Expr

@dataclass(frozen=True)
class PhilosophyExpr(Expr):
    question: str


@dataclass(frozen=True)
class Assign(Stmt):
    name: str
    expr: Expr

@dataclass(frozen=True)
class Flush(Stmt):
    expr: Expr

@dataclass(frozen=True)
class OdorFlush(Stmt):
    expr: Expr
    source: str = "unknown"

@dataclass(frozen=True)
class DiplomaticFlush(Stmt):
    expr: Expr
    from_tribe: str
    to_tribe: str

@dataclass(frozen=True)
class While(Stmt):
    left: Expr
    right: Expr
    body: tuple[Stmt, ...]


def int_to_expr(n: int) -> Expr:
    e: Expr = PoopZeroExpr()
    for _ in range(n):
        e = PoopSuccExpr(e)
    return e


def expr_to_int(e: Expr) -> Optional[int]:
    if isinstance(e, PoopZeroExpr):
        return 0
    if isinstance(e, PoopSuccExpr):
        inner = expr_to_int(e.expr)
        return None if inner is None else inner + 1
    if isinstance(e, OdorLevelExpr):
        return e.level
    return None


# ══════════════════════════════════════════════════════════════
# §2 Optimizer
# ══════════════════════════════════════════════════════════════

@dataclass(frozen=True)
class OptimizeReport:
    original_nodes: int
    optimized_nodes: int
    folded_nodes: int
    philosophy_calls: int
    axiom_break_prob: float

    def to_dict(self) -> dict:
        return {
            "original_nodes": self.original_nodes,
            "optimized_nodes": self.optimized_nodes,
            "folded_nodes": self.folded_nodes,
            "philosophy_calls": self.philosophy_calls,
            "axiom_break_prob": self.axiom_break_prob,
        }


def count_nodes(e: Expr) -> int:
    if isinstance(e, (PoopZeroExpr, OdorLevelExpr, Var, PhilosophyExpr)):
        return 1
    if isinstance(e, PoopSuccExpr):
        return 1 + count_nodes(e.expr)
    if isinstance(e, (OdorAdd, OdorSub, OdorNeq)):
        return 1 + count_nodes(e.left) + count_nodes(e.right)
    return 1


def optimize_expr(e: Expr) -> Expr:
    folded = expr_to_int(e)
    if folded is not None:
        return OdorLevelExpr(folded)

    if isinstance(e, PoopSuccExpr):
        inner = optimize_expr(e.expr)
        v = expr_to_int(inner)
        return OdorLevelExpr(v + 1) if v is not None else PoopSuccExpr(inner)

    if isinstance(e, OdorAdd):
        left = optimize_expr(e.left)
        right = optimize_expr(e.right)
        lv, rv = expr_to_int(left), expr_to_int(right)
        if lv is not None and rv is not None:
            return OdorLevelExpr(lv + rv)
        return OdorAdd(left, right)

    if isinstance(e, OdorSub):
        left = optimize_expr(e.left)
        right = optimize_expr(e.right)
        lv, rv = expr_to_int(left), expr_to_int(right)
        if lv is not None and rv is not None:
            return OdorLevelExpr(max(lv - rv, 0))
        return OdorSub(left, right)

    if isinstance(e, OdorNeq):
        return OdorNeq(optimize_expr(e.left), optimize_expr(e.right))

    return e


def count_philosophy(prog: tuple[Stmt, ...]) -> int:
    n = 0
    for stmt in prog:
        if hasattr(stmt, "expr") and isinstance(getattr(stmt, "expr"), PhilosophyExpr):
            n += 1
    return n


def optimize_program(prog: tuple[Stmt, ...]) -> tuple[tuple[Stmt, ...], OptimizeReport]:
    original_nodes = sum(count_nodes(s.expr) for s in prog if hasattr(s, "expr"))
    optimized: list[Stmt] = []

    for s in prog:
        if isinstance(s, Assign):
            optimized.append(Assign(s.name, optimize_expr(s.expr)))
        elif isinstance(s, Flush):
            optimized.append(Flush(optimize_expr(s.expr)))
        elif isinstance(s, OdorFlush):
            optimized.append(OdorFlush(optimize_expr(s.expr), s.source))
        elif isinstance(s, DiplomaticFlush):
            optimized.append(DiplomaticFlush(optimize_expr(s.expr), s.from_tribe, s.to_tribe))
        else:
            optimized.append(s)

    optimized_tuple = tuple(optimized)
    optimized_nodes = sum(count_nodes(s.expr) for s in optimized_tuple if hasattr(s, "expr"))
    phil = count_philosophy(optimized_tuple)
    report = OptimizeReport(
        original_nodes=original_nodes,
        optimized_nodes=optimized_nodes,
        folded_nodes=original_nodes - optimized_nodes,
        philosophy_calls=phil,
        axiom_break_prob=min(97.0, phil * 32.3),
    )
    return optimized_tuple, report


# ══════════════════════════════════════════════════════════════
# §3 Kont / CEK
# ══════════════════════════════════════════════════════════════

class Kont:
    pass

@dataclass(frozen=True)
class Halt(Kont):
    pass

@dataclass(frozen=True)
class AssignK(Kont):
    name: str
    rest: tuple[Stmt, ...]
    env: dict[str, Value]
    kont: Kont

@dataclass(frozen=True)
class FlushK(Kont):
    rest: tuple[Stmt, ...]
    env: dict[str, Value]
    kont: Kont

@dataclass(frozen=True)
class OdorFlushK(Kont):
    rest: tuple[Stmt, ...]
    env: dict[str, Value]
    kont: Kont
    source: str

@dataclass(frozen=True)
class DiplomaticFlushK(Kont):
    rest: tuple[Stmt, ...]
    env: dict[str, Value]
    kont: Kont
    from_tribe: str
    to_tribe: str

@dataclass(frozen=True)
class SuccK(Kont):
    kont: Kont

@dataclass(frozen=True)
class AddLeftK(Kont):
    right: Expr
    env: dict[str, Value]
    kont: Kont

@dataclass(frozen=True)
class AddRightK(Kont):
    left_val: Value
    kont: Kont

@dataclass(frozen=True)
class SubLeftK(Kont):
    right: Expr
    env: dict[str, Value]
    kont: Kont

@dataclass(frozen=True)
class SubRightK(Kont):
    left_val: Value
    kont: Kont

@dataclass(frozen=True)
class NeqLeftK(Kont):
    right: Expr
    env: dict[str, Value]
    kont: Kont

@dataclass(frozen=True)
class NeqRightK(Kont):
    left_val: Value
    kont: Kont

@dataclass(frozen=True)
class WhileK(Kont):
    stmt: While
    rest: tuple[Stmt, ...]
    env: dict[str, Value]
    kont: Kont


@dataclass(frozen=True)
class EvalExpr:
    expr: Expr

@dataclass(frozen=True)
class ReturnValue:
    value: Value


@dataclass
class State:
    control: object
    env: dict[str, Value]
    kont: Kont
    sink: "CivilizationEventStore"


def kont_depth(k: Kont) -> int:
    d = 0
    current = k
    while not isinstance(current, Halt):
        d += 1
        current = getattr(current, "kont", Halt())
    return d


@dataclass(frozen=True)
class KontTree:
    kind: str
    meta: str = ""
    child: Optional["KontTree"] = None

    def signature(self) -> tuple:
        return (self.kind, self.meta, self.child.signature() if self.child else None)

    def pretty(self, indent: int = 0) -> str:
        pad = "  " * indent
        line = f"{pad}{self.kind}"
        if self.meta:
            line += f"({self.meta})"
        return line if self.child is None else line + "\n" + self.child.pretty(indent + 1)


def kont_to_tree(k: Kont) -> KontTree:
    if isinstance(k, Halt):
        return KontTree("Halt")
    meta = ""
    if hasattr(k, "name"):
        meta = getattr(k, "name")
    if hasattr(k, "source"):
        meta = getattr(k, "source")
    return KontTree(type(k).__name__, meta, kont_to_tree(getattr(k, "kont", Halt())))


def is_halted(s: State) -> bool:
    return isinstance(s.control, ReturnValue) and isinstance(s.kont, Halt)


def poop_add(a: Value, b: Value) -> Value:
    return int_to_poop(a.to_int() + b.to_int())


def poop_sub(a: Value, b: Value) -> Value:
    return int_to_poop(max(a.to_int() - b.to_int(), 0))


def poop_neq(a: Value, b: Value) -> Value:
    return TRUE if a.to_int() != b.to_int() else FALSE


TREATY = {
    ("UhoTribe", "BananaTribe"): 3,
    ("UhoTribe", "MammothTribe"): 5,
    ("BananaTribe", "MammothTribe"):   4,
    ("PhilosophyGorillaFederation", "*"): 0,
}


def step(state: State) -> State:
    state.sink.tick()
    ctrl = state.control

    if isinstance(ctrl, tuple):
        if not ctrl:
            return State(ReturnValue(PoopZero()), state.env, state.kont, state.sink)

        head, *tail = ctrl
        rest = tuple(tail)
        if isinstance(head, Assign):
            return State(EvalExpr(head.expr), state.env, AssignK(head.name, rest, dict(state.env), state.kont), state.sink)
        if isinstance(head, Flush):
            return State(EvalExpr(head.expr), state.env, FlushK(rest, dict(state.env), state.kont), state.sink)
        if isinstance(head, OdorFlush):
            return State(EvalExpr(head.expr), state.env, OdorFlushK(rest, dict(state.env), state.kont, head.source), state.sink)
        if isinstance(head, DiplomaticFlush):
            return State(EvalExpr(head.expr), state.env, DiplomaticFlushK(rest, dict(state.env), state.kont, head.from_tribe, head.to_tribe), state.sink)
        if isinstance(head, While):
            return State(EvalExpr(OdorNeq(head.left, head.right)), state.env, WhileK(head, rest, dict(state.env), state.kont), state.sink)
        raise ScatError(f"unknown stmt: {head!r}")

    if isinstance(ctrl, EvalExpr):
        e = ctrl.expr
        if isinstance(e, PoopZeroExpr):
            return State(ReturnValue(PoopZero()), state.env, state.kont, state.sink)
        if isinstance(e, PoopSuccExpr):
            return State(EvalExpr(e.expr), state.env, SuccK(state.kont), state.sink)
        if isinstance(e, OdorLevelExpr):
            return State(ReturnValue(int_to_poop(e.level)), state.env, state.kont, state.sink)
        if isinstance(e, Var):
            if e.name not in state.env:
                raise ScatError(f"unbound variable: {e.name}")
            return State(ReturnValue(state.env[e.name]), state.env, state.kont, state.sink)
        if isinstance(e, OdorAdd):
            return State(EvalExpr(e.left), state.env, AddLeftK(e.right, dict(state.env), state.kont), state.sink)
        if isinstance(e, OdorSub):
            return State(EvalExpr(e.left), state.env, SubLeftK(e.right, dict(state.env), state.kont), state.sink)
        if isinstance(e, OdorNeq):
            return State(EvalExpr(e.left), state.env, NeqLeftK(e.right, dict(state.env), state.kont), state.sink)
        if isinstance(e, PhilosophyExpr):
            raise PhilosophyGorillaError(e.question)
        raise ScatError(f"unknown expr: {e!r}")

    if isinstance(ctrl, ReturnValue):
        v = ctrl.value
        k = state.kont
        if isinstance(k, Halt):
            return state
        if isinstance(k, SuccK):
            return State(ReturnValue(PoopSucc(v)), state.env, k.kont, state.sink)
        if isinstance(k, AssignK):
            new_env = dict(k.env)
            new_env[k.name] = v
            return State(k.rest, new_env, k.kont, state.sink)
        if isinstance(k, FlushK):
            state.sink.observe(v, "flush")
            return State(k.rest, dict(k.env), k.kont, state.sink)
        if isinstance(k, OdorFlushK):
            state.sink.observe_odor(v.to_int(), k.source)
            return State(k.rest, dict(k.env), k.kont, state.sink)
        if isinstance(k, DiplomaticFlushK):
            state.sink.observe_diplomatic(v.to_int(), k.from_tribe, k.to_tribe)
            return State(k.rest, dict(k.env), k.kont, state.sink)
        if isinstance(k, AddLeftK):
            return State(EvalExpr(k.right), dict(k.env), AddRightK(v, k.kont), state.sink)
        if isinstance(k, AddRightK):
            return State(ReturnValue(poop_add(k.left_val, v)), state.env, k.kont, state.sink)
        if isinstance(k, SubLeftK):
            return State(EvalExpr(k.right), dict(k.env), SubRightK(v, k.kont), state.sink)
        if isinstance(k, SubRightK):
            return State(ReturnValue(poop_sub(k.left_val, v)), state.env, k.kont, state.sink)
        if isinstance(k, NeqLeftK):
            return State(EvalExpr(k.right), dict(k.env), NeqRightK(v, k.kont), state.sink)
        if isinstance(k, NeqRightK):
            return State(ReturnValue(poop_neq(k.left_val, v)), state.env, k.kont, state.sink)
        if isinstance(k, WhileK):
            if v.to_int() == 0:
                return State(k.rest, dict(k.env), k.kont, state.sink)
            return State(k.stmt.body + (k.stmt,) + k.rest, dict(k.env), k.kont, state.sink)
        raise ScatError(f"unknown kont: {k!r}")

    raise ScatError(f"unknown ctrl: {ctrl!r}")


# ══════════════════════════════════════════════════════════════
# §4 Civilization Events / Event Store
# ══════════════════════════════════════════════════════════════

@dataclass(frozen=True)
class CivEvent:
    step: int
    kind: str
    detail: str
    frontend: str
    era: str = "official"

    def to_dict(self) -> dict:
        return {
            "step": self.step,
            "kind": self.kind,
            "detail": self.detail,
            "frontend": self.frontend,
            "era": self.era,
        }


@dataclass(frozen=True)
class HistoryRewriteEvent(CivEvent):
    erased_event: str = ""
    rewritten_as: str = "deemed to have never existed from the start"

    def to_dict(self) -> dict:
        d = super().to_dict()
        d.update({
            "erased_event": self.erased_event,
            "rewritten_as": self.rewritten_as,
        })
        return d


@dataclass(frozen=True)
class ArchaeologicalArtifact(CivEvent):
    artifact_id: str = ""
    source_event: str = ""
    confidence: float = 0.0

    def to_dict(self) -> dict:
        d = super().to_dict()
        d.update({
            "artifact_id": self.artifact_id,
            "source_event": self.source_event,
            "confidence": self.confidence,
        })
        return d


@dataclass(frozen=True)
class GhostOdorEvent(CivEvent):
    cave_a: str = ""
    cave_b: str = ""
    odor_level: int = 0

    def to_dict(self) -> dict:
        d = super().to_dict()
        d.update({
            "cave_a": self.cave_a,
            "cave_b": self.cave_b,
            "odor_level": self.odor_level,
        })
        return d


@dataclass(frozen=True)
class CausalityLeakEvent(CivEvent):
    leaked_from: str = ""
    leaked_into: str = ""

    def to_dict(self) -> dict:
        d = super().to_dict()
        d.update({"leaked_from": self.leaked_from, "leaked_into": self.leaked_into})
        return d


@dataclass
class CivilizationEventStore:
    frontend: str = "unknown"
    step_count: int = 0
    _full_log: list[CivEvent] = field(default_factory=list)
    _official_history: list[CivEvent] = field(default_factory=list)
    _archaeology: list[ArchaeologicalArtifact] = field(default_factory=list)
    _observations: list[tuple[Value, int, str]] = field(default_factory=list)
    _kont_snapshots: list[tuple[int, KontTree]] = field(default_factory=list)

    def tick(self) -> None:
        self.step_count += 1

    def emit(self, ev: CivEvent, *, official: bool = True) -> None:
        self._full_log.append(ev)
        if official:
            self._official_history.append(ev)

    def observe(self, v: Value, ctx: str = "flush") -> None:
        self._observations.append((v, self.step_count, ctx))
        self.emit(CivEvent(self.step_count, "observe", v.pretty(), self.frontend))

    def observe_odor(self, level: int, source: str) -> None:
        self.emit(CivEvent(self.step_count, "odor", f"💨 Lv{level}({source})", self.frontend))

    def observe_diplomatic(self, level: int, from_tribe: str, to_tribe: str) -> None:
        if "PhilosophyGorilla" in from_tribe:
            treaty = 0
        else:
            treaty = TREATY.get((from_tribe, to_tribe), 4)
        if level > treaty:
            banana = level * 2
            ev = CivEvent(self.step_count, "diplomatic", f"🌍 {from_tribe}→{to_tribe} concentration={level} banana={banana}", self.frontend)
            self.emit(ev)
            raise CrossBorderOdorError(f"{from_tribe}→{to_tribe} concentration={level} banana_reparations={banana}")
        self.emit(CivEvent(self.step_count, "diplomatic", f"🌤️ WeatherGorillaFault({from_tribe}→{to_tribe})", self.frontend))

    def temporal_banana_rewrite(self, banana_count: int, *, target_kind: Optional[str] = None, max_rewrites: Optional[int] = None) -> list[HistoryRewriteEvent]:
        """Removes from official history but leaves traces in full_log and archaeology. UHO."""
        if banana_count < 3:
            ev = CivEvent(self.step_count, "banana_failed", f"🍌×{banana_count} history revision failed", self.frontend)
            self.emit(ev)
            return []

        candidates = [ev for ev in self._official_history if target_kind is None or ev.kind == target_kind]
        if max_rewrites is not None:
            candidates = candidates[:max_rewrites]

        rewrites: list[HistoryRewriteEvent] = []
        for idx, old in enumerate(candidates, 1):
            rewrite = HistoryRewriteEvent(
                step=self.step_count,
                kind="history_rewrite",
                detail=f"🍌×{banana_count} past revision",
                frontend=self.frontend,
                era="meta",
                erased_event=old.detail,
                rewritten_as="deemed to have never existed from the start",
            )
            artifact = ArchaeologicalArtifact(
                step=self.step_count,
                kind="artifact",
                detail=f"🪨 Mural trace: {old.detail}",
                frontend="ArchaeologyIR",
                era="archaeological",
                artifact_id=f"ART-{self.step_count}-{idx}",
                source_event=old.detail,
                confidence=max(0.12, min(0.97, 0.35 + banana_count / 20)),
            )
            rewrites.append(rewrite)
            self._full_log.append(rewrite)
            self._full_log.append(artifact)
            self._archaeology.append(artifact)

        self._official_history = [ev for ev in self._official_history if ev not in candidates]
        self._full_log.append(CivEvent(
            self.step_count,
            "banana_gc",
            f"🍌×{banana_count} → {len(candidates)} official history event(s) revised",
            self.frontend,
            era="meta",
        ))
        return rewrites

    def snapshot_kont(self, step_no: int, k: Kont) -> None:
        self._kont_snapshots.append((step_no, kont_to_tree(k)))

    @property
    def observations(self) -> tuple[tuple[Value, int, str], ...]:
        return tuple(self._observations)

    @property
    def full_log(self) -> list[CivEvent]:
        return list(self._full_log)

    @property
    def official_history(self) -> list[CivEvent]:
        return list(self._official_history)

    @property
    def archaeology(self) -> list[ArchaeologicalArtifact]:
        return list(self._archaeology)

    @property
    def kont_snapshots(self) -> list[tuple[int, KontTree]]:
        return list(self._kont_snapshots)


# ══════════════════════════════════════════════════════════════
# §5 Trace / Run Capture
# ══════════════════════════════════════════════════════════════

@dataclass(frozen=True)
class TraceEvent:
    step: int
    control_kind: str
    expr_kind: str
    kont_kind: str
    kont_depth: int
    env_keys: tuple[str, ...]
    observed_count: int
    frontend: str

    def signature(self, *, include_frontend: bool = False) -> tuple:
        base = (self.control_kind, self.expr_kind, self.kont_kind, self.kont_depth, self.env_keys, self.observed_count)
        return base + ((self.frontend,) if include_frontend else ())

    def to_dict(self) -> dict:
        return {
            "step": self.step,
            "control_kind": self.control_kind,
            "expr_kind": self.expr_kind,
            "kont_kind": self.kont_kind,
            "kont_depth": self.kont_depth,
            "env_keys": list(self.env_keys),
            "observed_count": self.observed_count,
            "frontend": self.frontend,
        }


@dataclass(frozen=True)
class TraceErrorEvent:
    step: int
    control_kind: str
    expr_kind: str
    kont_kind: str
    kont_depth: int
    env_keys: tuple[str, ...]
    observed_count: int
    frontend: str
    error_type: str
    error_message: str

    def signature(self, *, include_frontend: bool = False) -> tuple:
        base = (self.control_kind, self.expr_kind, self.kont_kind, self.kont_depth, self.env_keys, self.observed_count, self.error_type, self.error_message)
        return base + ((self.frontend,) if include_frontend else ())

    def to_dict(self) -> dict:
        return {
            "step": self.step,
            "control_kind": self.control_kind,
            "expr_kind": self.expr_kind,
            "kont_kind": self.kont_kind,
            "kont_depth": self.kont_depth,
            "env_keys": list(self.env_keys),
            "observed_count": self.observed_count,
            "frontend": self.frontend,
            "error_type": self.error_type,
            "error_message": self.error_message,
        }


class TraceCollector:
    def __init__(self) -> None:
        self.events: list[TraceEvent] = []

    def __call__(self, ev: TraceEvent) -> None:
        self.events.append(ev)

    def signature(self, *, include_frontend: bool = False) -> tuple[tuple, ...]:
        return tuple(ev.signature(include_frontend=include_frontend) for ev in self.events)


@dataclass
class RunCapture:
    final_state: Optional[State]
    trace: TraceCollector
    error: Optional[TraceErrorEvent]
    sink: CivilizationEventStore
    optimize_report: Optional[OptimizeReport] = None

    def ok(self) -> bool:
        return self.error is None


def make_trace(step_no: int, state: State) -> TraceEvent:
    ctrl = state.control
    if isinstance(ctrl, tuple):
        ck, ek = "StmtSeq", f"len={len(ctrl)}"
    elif isinstance(ctrl, EvalExpr):
        ck, ek = "EvalExpr", type(ctrl.expr).__name__
    elif isinstance(ctrl, ReturnValue):
        ck, ek = "ReturnValue", ctrl.value.pretty()
    else:
        ck, ek = type(ctrl).__name__, ""

    return TraceEvent(
        step=step_no,
        control_kind=ck,
        expr_kind=ek,
        kont_kind=type(state.kont).__name__,
        kont_depth=kont_depth(state.kont),
        env_keys=tuple(sorted(state.env.keys())),
        observed_count=len(state.sink.observations),
        frontend=state.sink.frontend,
    )


def make_error(step_no: int, state: State, exc: Exception) -> TraceErrorEvent:
    t = make_trace(step_no, state)
    return TraceErrorEvent(
        step=t.step,
        control_kind=t.control_kind,
        expr_kind=t.expr_kind,
        kont_kind=t.kont_kind,
        kont_depth=t.kont_depth,
        env_keys=t.env_keys,
        observed_count=t.observed_count,
        frontend=t.frontend,
        error_type=type(exc).__name__,
        error_message=str(exc),
    )


KONT_SNAPSHOT_STEPS = {0, 1, 3, 5, 8, 13, 21}


def run_capture(
    program: tuple[Stmt, ...],
    *,
    frontend: str,
    env: Optional[dict[str, Value]] = None,
    max_steps: int = 200_000,
    optimize: bool = False,
) -> RunCapture:
    optimize_report = None
    prog = tuple(program)
    if optimize:
        prog, optimize_report = optimize_program(prog)

    collector = TraceCollector()
    sink = CivilizationEventStore(frontend=frontend)
    state = State(tuple(prog), dict(env or {}), Halt(), sink)

    for step_no in range(max_steps):
        if is_halted(state):
            return RunCapture(state, collector, None, sink, optimize_report)

        collector(make_trace(step_no, state))
        if step_no in KONT_SNAPSHOT_STEPS:
            sink.snapshot_kont(step_no, state.kont)

        try:
            state = step(state)
        except ScatError as exc:
            return RunCapture(None, collector, make_error(step_no, state, exc), sink, optimize_report)

    exc = StepLimitError(f"step limit exceeded: {max_steps}")
    return RunCapture(None, collector, make_error(max_steps, state, exc), sink, optimize_report)


# ══════════════════════════════════════════════════════════════
# §6 Federation utilities
# ══════════════════════════════════════════════════════════════

@dataclass(frozen=True)
class FederationReport:
    case: str
    trace_equal: bool
    surface_diff: bool
    left_frontend: str
    right_frontend: str
    left_steps: int
    right_steps: int
    left_error: Optional[dict]
    right_error: Optional[dict]
    kont_equal: bool

    def to_dict(self) -> dict:
        return {
            "case": self.case,
            "trace_equal": self.trace_equal,
            "surface_diff": self.surface_diff,
            "left_frontend": self.left_frontend,
            "right_frontend": self.right_frontend,
            "left_steps": self.left_steps,
            "right_steps": self.right_steps,
            "left_error": self.left_error,
            "right_error": self.right_error,
            "kont_equal": self.kont_equal,
        }


def paired_report(case: str, left: tuple[Stmt, ...], lf: str, right: tuple[Stmt, ...], rf: str, *, max_steps: int = 200_000) -> FederationReport:
    lcap = run_capture(left, frontend=lf, max_steps=max_steps)
    rcap = run_capture(right, frontend=rf, max_steps=max_steps)
    lks = [(step, tree.signature()) for step, tree in lcap.sink.kont_snapshots]
    rks = [(step, tree.signature()) for step, tree in rcap.sink.kont_snapshots]
    return FederationReport(
        case=case,
        trace_equal=lcap.trace.signature() == rcap.trace.signature(),
        surface_diff=lcap.trace.signature(include_frontend=True) != rcap.trace.signature(include_frontend=True),
        left_frontend=lf,
        right_frontend=rf,
        left_steps=len(lcap.trace.events),
        right_steps=len(rcap.trace.events),
        left_error=lcap.error.to_dict() if lcap.error else None,
        right_error=rcap.error.to_dict() if rcap.error else None,
        kont_equal=lks == rks,
    )


# ══════════════════════════════════════════════════════════════
# §7 Ghost Odor / Archaeology
# ══════════════════════════════════════════════════════════════

def mammoth_distributed_transaction() -> tuple[GhostOdorEvent, list[ArchaeologicalArtifact]]:
    cave_a_prog = (OdorFlush(OdorLevelExpr(5), source="Mammoth"),)
    cave_b_prog = (Flush(Var("missing_observer")),)

    cap_a = run_capture(cave_a_prog, frontend="🦣CaveA")
    cap_b = run_capture(cave_b_prog, frontend="🦣CaveB")

    odor_count = sum(1 for ev in cap_a.sink.full_log if ev.kind == "odor")
    step_no = cap_b.error.step if cap_b.error else 0

    ghost = GhostOdorEvent(
        step=step_no,
        kind="ghost_odor",
        detail=f"👻 Ghost Odor Lv{odor_count}: odor exists, no culprit",
        frontend="🦣CaveA+🦣CaveB",
        era="liminal",
        cave_a="🦣CaveA",
        cave_b="🦣CaveB",
        odor_level=odor_count,
    )

    artifacts = [
        ArchaeologicalArtifact(
            step=step_no,
            kind="artifact",
            detail="🪨 CaveA Mural: traces of something Mammoth did",
            frontend="ArchaeologyIR",
            era="archaeological",
            artifact_id="GHOST-A-001",
            source_event=ghost.detail,
            confidence=0.72,
        ),
        ArchaeologicalArtifact(
            step=step_no,
            kind="artifact",
            detail="🪨 CaveB Mural: recorded as observer missing_observer",
            frontend="ArchaeologyIR",
            era="archaeological",
            artifact_id="GHOST-B-001",
            source_event=ghost.detail,
            confidence=0.64,
        ),
    ]
    return ghost, artifacts


# ══════════════════════════════════════════════════════════════
# §8 Gorilla Incompleteness Theorem
# ══════════════════════════════════════════════════════════════

class GorillaTheoremProver:
    @staticmethod
    def prove(statement: str) -> dict:
        table = {
            "fart = wind": ("proof failed", "an observer exists; the moment of observation makes it an incident, not wind"),
            "fart ≠ wind": ("proof failed", "wind direction is an unbound variable; WeatherGorilla refused to testify"),
            "this theorem is provable": ("proof failed", "the theorem observes its own odor"),
        }
        result, reason = table.get(statement, ("proof failed", "PhilosophyGorilla ate the question"))
        return {
            "statement": statement,
            "result": result,
            "reason": reason,
            "theorem": "Gorilla Incompleteness Theorem v2.1",
        }


# ══════════════════════════════════════════════════════════════
# §9 Replay / Diff
# ══════════════════════════════════════════════════════════════

def replay(events: list[CivEvent]) -> None:
    print("\n📜 Replay Civilization v2.1")
    for ev in sorted(events, key=lambda x: (x.step, x.kind, x.detail)):
        if isinstance(ev, HistoryRewriteEvent):
            print(f"  step {ev.step:>3} | 📜🔥 History rewrite: {ev.erased_event} → {ev.rewritten_as}")
        elif isinstance(ev, ArchaeologicalArtifact):
            print(f"  step {ev.step:>3} | 🪨 Artifact {ev.artifact_id}: {ev.detail} confidence={ev.confidence:.2f}")
        elif isinstance(ev, GhostOdorEvent):
            print(f"  step {ev.step:>3} | 👻 {ev.detail}")
        elif isinstance(ev, CausalityLeakEvent):
            print(f"  step {ev.step:>3} | 🌀 Causality Leak: {ev.leaked_from} → {ev.leaked_into}")
        else:
            icon = {
                "odor": "💨",
                "diplomatic": "🌍",
                "observe": "👁️",
                "banana_gc": "🍌",
                "banana_failed": "🍌",
                "theorem": "🦍",
            }.get(ev.kind, "·")
            print(f"  step {ev.step:>3} | {icon} [{ev.frontend}] {ev.detail}")


def civilization_diff(store: CivilizationEventStore) -> dict:
    official = [ev.detail for ev in store.official_history]
    archaeology = [ev.detail for ev in store.archaeology]
    full = [ev.detail for ev in store.full_log]
    missing_from_official = [ev.detail for ev in store.full_log if ev.kind in {"history_rewrite", "artifact"} or ev.detail not in official]
    return {
        "official_history_count": len(official),
        "archaeological_artifact_count": len(archaeology),
        "full_log_count": len(full),
        "missing_from_official": missing_from_official,
    }


# ══════════════════════════════════════════════════════════════
# §10 Demo programs
# ══════════════════════════════════════════════════════════════

def common_success_prog() -> tuple[Stmt, ...]:
    return (
        Assign("x", int_to_expr(3)),
        Assign("y", int_to_expr(2)),
        Flush(OdorAdd(Var("x"), Var("y"))),
    )


def philosophy_prog() -> tuple[Stmt, ...]:
    return (
        Assign("q", PhilosophyExpr("To whom does the flatulence belong?")),
        Flush(Var("q")),
    )


def civilization_crisis_prog() -> tuple[Stmt, ...]:
    return (
        OdorFlush(int_to_expr(7), source="Mammoth"),
        DiplomaticFlush(int_to_expr(7), "UhoTribe", "BananaTribe"),
    )


# ══════════════════════════════════════════════════════════════
# §11 Main
# ══════════════════════════════════════════════════════════════

def banner(title: str) -> None:
    print("\n" + "━" * 72)
    print(f"  {title}")
    print("━" * 72)


def main() -> None:
    banner("DungLang Civilization v2.1 🦍📜🔥")
    print("  Focus: Temporal Banana Rewrite / ArchaeologyIR / Civilization Federation")

    # ① Optimizer
    banner("① OdorOptimizer")
    unopt = (
        Assign("x", OdorAdd(int_to_expr(2), int_to_expr(2))),
        OdorFlush(Var("x"), source="Mammoth"),
    )
    opt_prog, opt_report = optimize_program(unopt)
    print(f"  original_nodes={opt_report.original_nodes}")
    print(f"  optimized_nodes={opt_report.optimized_nodes}")
    print(f"  folded_nodes={opt_report.folded_nodes}")
    print(f"  optimized_prog={opt_prog}")

    # ② Federation comparator
    banner("② Civilization Federation Comparator")
    fed_success = paired_report("common success", common_success_prog(), "💩ScatLang", common_success_prog(), "🌊SeaIR")
    fed_phil = paired_report("philosophy same failure", philosophy_prog(), "🦍DungLang", philosophy_prog(), "🍠YakiimoIR")
    for rep in [fed_success, fed_phil]:
        print(f"  {rep.case}: trace_equal={rep.trace_equal}, kont_equal={rep.kont_equal}, surface_diff={rep.surface_diff}")

    # ③ Run crisis and rewrite official history
    banner("③ Temporal Banana Rewrite")
    crisis = run_capture(civilization_crisis_prog(), frontend="🦍DungLang")
    store = crisis.sink
    if crisis.error:
        print(f"  crisis error: {crisis.error.error_type}({crisis.error.error_message}) @ step {crisis.error.step}")

    print("  official history before:")
    for ev in store.official_history:
        print(f"    step {ev.step}: {ev.detail}")

    store.step_count = 88
    rewrites = store.temporal_banana_rewrite(10, max_rewrites=2)
    print(f"  rewrites={len(rewrites)}")
    print("  official history after:")
    for ev in store.official_history:
        print(f"    step {ev.step}: {ev.detail}")
    if not store.official_history:
        print("    (blank. but the stone tablets remember)")

    # ④ Ghost odor archaeology
    banner("④ Ghost Odor Archaeology")
    ghost, artifacts = mammoth_distributed_transaction()
    print(f"  {ghost.detail}")
    for art in artifacts:
        print(f"  {art.artifact_id}: {art.detail} confidence={art.confidence:.2f}")

    # ⑤ Gorilla theorem
    banner("⑤ Gorilla Incompleteness")
    theorem_results = [
        GorillaTheoremProver.prove("fart = wind"),
        GorillaTheoremProver.prove("fart ≠ wind"),
        GorillaTheoremProver.prove("this theorem is provable"),
    ]
    for r in theorem_results:
        print(f"  Prove: {r['statement']} -> {r['result']} / {r['reason']}")

    # ⑥ Causality leak detection
    banner("⑥ Causality Leak Detector")
    full_events: list[CivEvent] = []
    full_events.extend(store.full_log)
    full_events.append(ghost)
    full_events.extend(artifacts)

    official_details = {ev.detail for ev in store.official_history}
    for ev in store.archaeology:
        if ev.source_event not in official_details:
            leak = CausalityLeakEvent(
                step=ev.step,
                kind="causality_leak",
                detail=f"trace not in official history detected: {ev.source_event}",
                frontend="ArchaeologyIR",
                era="archaeological",
                leaked_from=ev.source_event,
                leaked_into=ev.artifact_id,
            )
            full_events.append(leak)
            print(f"  leak: {leak.detail}")

    # ⑦ Replay
    banner("★ Replay Civilization v2.1")
    replay(full_events)

    report = {
        "version": "DungLang Civilization v2.1",
        "optimizer": opt_report.to_dict(),
        "federation": [fed_success.to_dict(), fed_phil.to_dict()],
        "temporal_rewrites": [ev.to_dict() for ev in rewrites],
        "civilization_diff": civilization_diff(store),
        "ghost_odor": ghost.to_dict(),
        "archaeology": [ev.to_dict() for ev in store.archaeology + artifacts],
        "theorem_results": theorem_results,
        "events": [ev.to_dict() for ev in full_events],
    }

    out = PathLike.default_report_path()
    with open(out, "w", encoding="utf-8") as f:
        json.dump(report, f, ensure_ascii=False, indent=2)

    banner("v2.1 complete")
    print(f"  JSON report: {out}")
    print("  Official history disappears. ArchaeologyIR is laughing. UHO 🪨")


class PathLike:
    @staticmethod
    def default_report_path() -> str:
        # ChatGPT / local both friendly
        return "/mnt/data/dunglang_civilization_v2_1_report.json"


if __name__ == "__main__":
    main()
