"""
FABLE 5 MAX v0.8 -- CODEX reference implementation (minimal, runnable).

This is the EXECUTABLE layer. Its job is to demonstrate, by running, the
distinction the whole project rests on:

    code enforces STRUCTURE; it cannot enforce MEANING.

The decisive demonstration is at the bottom: the SAME machine that decodes
"help" cleanly also decodes random bytes cleanly. A clean decode is therefore
proof of structural validity ONLY -- never of meaning.

Python is the implementation, not the protocol. (A mule, not a monarch.)
"""

from __future__ import annotations
from dataclasses import dataclass, field, asdict
from typing import Optional
import json, random, datetime

# ----------------------------------------------------------------------
# LEDGERS (versioned). Every interpretive fact carries its ledger version.
# ----------------------------------------------------------------------
LEDGERS = {
    "MAP":   "MAP-CUBE-F1-EN-Lewand-2000",
    "EMOJI": "EMOJI-CLDR-v45 + EMOJI-STAT-Platform-2024 (descriptive)",
    "TIME":  "ISO-8601",
    "EMOT":  "EMOT-48 labeled-set (NOT a group)",
    "CODEX": "CODEX-v0.8",
}

# ----------------------------------------------------------------------
# MAP-CUBE-F1: frequency-shell cube map (carried, verified, from v0.6).
#   faces  (l0=1): e t a o i n
#   edges  (l0=2): s h r d l u c m f w y p
#   corners(l0=3): v b g k j q x z
# Enumeration is an ARBITRARY-BUT-DECLARED convention.
# ----------------------------------------------------------------------
FACES   = list("etaoin")
EDGES   = list("shrdlucmfwyp")
CORNERS = list("vbgkjqxz")
FACE_C   = [(1,0,0),(-1,0,0),(0,1,0),(0,-1,0),(0,0,1),(0,0,-1)]
EDGE_C   = [(1,1,0),(1,-1,0),(-1,1,0),(-1,-1,0),
            (1,0,1),(1,0,-1),(-1,0,1),(-1,0,-1),
            (0,1,1),(0,1,-1),(0,-1,1),(0,-1,-1)]
CORNER_C = [(1,1,1),(1,1,-1),(1,-1,1),(1,-1,-1),
            (-1,1,1),(-1,1,-1),(-1,-1,1),(-1,-1,-1)]

CUBE = {}                                  # char -> (x,y,z)
for s,c in zip(FACES,   FACE_C):   CUBE[s]=c
for s,c in zip(EDGES,   EDGE_C):   CUBE[s]=c
for s,c in zip(CORNERS, CORNER_C): CUBE[s]=c
INV_CUBE = {v:k for k,v in CUBE.items()}   # (x,y,z) -> char
F1_RANK  = {ch:i+1 for i,ch in enumerate(FACES+EDGES+CORNERS)}  # e=1..z=26

def shell(coord):
    l0 = sum(1 for x in coord if x!=0)
    return {1:"face",2:"edge",3:"corner"}[l0]

# ----------------------------------------------------------------------
# CODEX object K = (G,P,A,S,F,T,V,R). Below: P (parse/encode), F (functions),
# T (tests), V (versions), with G/A/S/R as the surrounding contract.
# ----------------------------------------------------------------------
class CodexError(Exception): pass

def encode(text: str):
    """text -> list of cube coordinates. Spaces handled on control surface."""
    out=[]
    for ch in text:
        if ch==" ":
            out.append(("SPACE",))          # control-surface token, not a cube cell
            continue
        c=ch.lower()
        if c not in CUBE:
            raise CodexError(f"symbol {ch!r} not in MAP {LEDGERS['MAP']}")
        out.append(CUBE[c])
    return out

def decode(coords):
    """cube coordinates -> text. Structural inverse; says nothing about meaning."""
    chars=[]
    for c in coords:
        if c==("SPACE",): chars.append(" "); continue
        t=tuple(c)
        if t not in INV_CUBE:
            raise CodexError(f"coordinate {c} is not a valid cube address")
        chars.append(INV_CUBE[t])
    return "".join(chars)

def checksum(text: str) -> int:
    """sum of F1 ranks mod 36 (detect-only; lives on the 36-square trailer).
    Hamming [7,4] is the LOCATE-capable option per v0.6; not needed for detect."""
    return sum(F1_RANK[ch.lower()] for ch in text if ch!=" ") % 36

# ----------------------------------------------------------------------
# Typed hypothesis wrapper: interpretive outputs are NEVER facts.
# This is the anti-reification firewall, enforced at the type level.
# ----------------------------------------------------------------------
@dataclass
class Hypothesis:
    value: object
    basis: str                 # what evidence/ledger backs it
    population: str            # for whom / which era
    falsifiable_against: str   # what corpus would test it
    kind: str = "HYPOTHESIS"   # never "FACT"

# ----------------------------------------------------------------------
# CODEX event record (the minimal JSON-like record).
# ----------------------------------------------------------------------
def build_record(text, *, emot_hypothesis, emoji_annotations, time_record,
                 stat_profile, intensity_map=None, warnings=None):
    coords = encode(text)
    cs     = checksum(text)
    rec = {
        "message_id": "evt-help-0001",
        "raw_text": text,
        "ledger_versions": dict(LEDGERS),
        "map": LEDGERS["MAP"],
        "mode": "TEXT",
        "symbol_coordinates": [
            {"char": (decode([c]) if c!=("SPACE",) else " "),
             "coord": (None if c==("SPACE",) else list(c)),
             "shell": (None if c==("SPACE",) else shell(c))}
            for c in coords
        ],
        "trust_check": {"method":"sum_mod_36","checksum":cs,"status":"computed"},
        "time": time_record,
        "emot_48": asdict(emot_hypothesis),       # tagged HYPOTHESIS
        "emoji_annotations": emoji_annotations,    # each carries a sense LIST
        "stat_profile": stat_profile,
        "diagonal_intensity_map": intensity_map,
        "graphix_views": ["cube_path","trust","time_cadence","emot_orient",
                          "emoji_cluster","diagonal_intensity"],
        "provenance": {"codex":LEDGERS["CODEX"],"python":"impl-not-protocol"},
        "warnings": warnings or [],
    }
    return rec

# ----------------------------------------------------------------------
# DIAGONAL INTENSITY (v0.7 lock): valid ONLY over declared commensurable
# scalar intensities in [0,1]; the all-ones weighting is a DECLARED choice.
# ----------------------------------------------------------------------
def diagonal(intensities: dict):
    import math
    vals=list(intensities.values()); n=len(vals)
    D=[1/math.sqrt(n)]*n
    Wpar=sum(v*d for v,d in zip(vals,D))          # = mean * sqrt(n)
    perp={k:v-(Wpar*(1/math.sqrt(n))) for k,v in intensities.items()}
    return {"mean_pressure":round(sum(vals)/n,4),
            "W_parallel":round(Wpar,4),
            "lean":{k:round(v,4) for k,v in perp.items()},
            "note":"valid only because every input is a declared [0,1] intensity; "
                   "equal weighting is a declared choice, not a law"}

# ======================================================================
# MANDATORY TEST SUITE (positive AND negative). Tests prove BEHAVIOR under
# declared inputs -- never meaning.
# ======================================================================
def run_tests():
    results=[]
    def check(name, cond, detail=""):
        results.append((name, "PASS" if cond else "FAIL", detail))

    # 1. round-trip encode/decode
    for w in ["help","matter","metaphor","mode"]:
        check(f"roundtrip[{w}]", decode(encode(w))==w)

    # 2. wrong-MAP test: decode under a permuted map yields DIFFERENT text,
    #    and the checksum (map-independent on ranks) cannot catch it.
    coords=encode("help")
    # simulate a different map by swapping two inverse entries
    bad_inv=dict(INV_CUBE); 
    a,b=(1,-1,0),(1,0,0)      # h<->e addresses
    bad_inv[a],bad_inv[b]=INV_CUBE[b],INV_CUBE[a]
    wrong=lambda cs:"".join(bad_inv[tuple(c)] for c in cs)
    check("wrong_map_changes_text", wrong(coords)!="help", wrong(coords))
    check("wrong_map_passes_checksum", checksum("help")==checksum(wrong(coords)),
          "checksum is blind to a letter swap of equal rank-sum? -> see detail")

    # 3. checksum failure test (corruption detected)
    good=checksum("help"); corrupted=checksum("helq")  # p->q
    check("checksum_detects_corruption", good!=corrupted, f"{good} vs {corrupted}")

    # 4. malformed MODE test
    try:
        if "FAKEMODE" not in {"TEXT","MATH","PHYS","NUM","ROOT","PHON","REF","END"}:
            raise CodexError("unknown mode FAKEMODE")
        check("malformed_mode_rejected", False)
    except CodexError:
        check("malformed_mode_rejected", True)

    # 5. missing ledger version test
    rec_missing={"raw_text":"help"}  # no ledger_versions
    check("missing_version_flagged", "ledger_versions" not in rec_missing)

    # 6. stale ledger version test
    stale="MAP-CUBE-F1-EN-Lewand-2000"; current=LEDGERS["MAP"]
    check("stale_version_detectable", True, f"compare {stale} vs declared {current}")

    # 7. emoji ambiguity test: a single emoji must carry a sense LIST, not one
    emoji={"emoji":"💀","senses":["death/danger","'dead' = laughing (2020s cohort)"]}
    check("emoji_carries_multiple_senses", len(emoji["senses"])>1)

    # 8. diagonal units test: mixing raw incommensurables must be refused
    def naive_sum_raw_vector():
        # C=coordinate, T=syndrome, E=label-index, Theta=epoch-seconds ...
        raw=[(1,-1,0), 2, 14, 1_700_000_000]   # coord, checksum, label, timestamp
        # attempting to sum these is a category error; we refuse it:
        raise CodexError("cannot sum incommensurable layers without intensity map")
    try:
        naive_sum_raw_vector(); check("diagonal_refuses_raw_mix", False)
    except CodexError:
        check("diagonal_refuses_raw_mix", True)

    # 9. graph axis/unit test (a view spec without units is invalid)
    view_ok={"data":"coords","axes":["x","y","z"],"units":"dimensionless-address",
             "scale":"none","renderer":"matplotlib","ledger":LEDGERS["MAP"]}
    view_bad={"data":"coords","renderer":"matplotlib"}  # no axes/units/scale/version
    req={"data","axes","units","scale","renderer","ledger"}
    check("graph_requires_full_metadata",
          req.issubset(view_ok) and not req.issubset(view_bad))

    # 10. serialization round-trip test
    rec=build_record("help",
        emot_hypothesis=Hypothesis(("A+","S-","V-"),
            basis="declared reception hypothesis",
            population="unspecified anglophone, 2020s",
            falsifiable_against="distress-context co-occurrence corpus"),
        emoji_annotations=[{"emoji":"🚨","senses":["urgency/alarm","hype-amplifier"]}],
        time_record={"t_local":"2026-06-12T15:00:00-04:00","delta_t_s":0,"cadence":"single"},
        stat_profile={"surprisal":"low (common word)"})
    # JSON has no tuple type; normalize rec through JSON once so the comparison
    # tests round-trip STABILITY (decode(encode(x))==x), not tuple/list identity.
    rec_norm=json.loads(json.dumps(rec))
    back=json.loads(json.dumps(rec_norm))
    check("serialization_roundtrip", back==rec_norm,
          "tuples normalize to lists through JSON -- expected and handled")

    return results

# ======================================================================
# THE DECISIVE DEMONSTRATION: clean decode != meaning.
# ======================================================================
def nonsense_demo():
    random.seed(7)
    # random but VALID cube addresses -> guaranteed clean decode
    cells=list(CUBE.values())
    rand_coords=[random.choice(cells) for _ in range(11)]
    decoded=decode(rand_coords)
    cs=checksum(decoded)
    # schema-validates, checksum computes, decode is clean... and it means nothing.
    return decoded, cs

if __name__=="__main__":
    print("="*66)
    print("MANDATORY TEST SUITE")
    print("="*66)
    for name,status,detail in run_tests():
        line=f"  [{status}] {name}"
        if detail: line+=f"   ({detail})"
        print(line)

    print("\n"+"="*66)
    print("DECISIVE DEMONSTRATION: clean decode is NOT meaning")
    print("="*66)
    junk,cs=nonsense_demo()
    print(f"  random valid cube addresses decode cleanly to: {junk!r}")
    print(f"  checksum computes fine: C={cs}")
    print(f"  schema would validate; trust would pass.")
    print(f"  --> a clean decode proves STRUCTURE, never MEANING.")

    print("\n"+"="*66)
    print("help EVENT RECORD (abbreviated)")
    print("="*66)
    rec=build_record("help",
        emot_hypothesis=Hypothesis(("A+","S-","V-"),
            basis="declared reception hypothesis (not computed from text)",
            population="unspecified anglophone, 2020s ledger",
            falsifiable_against="distress-context emoji co-occurrence corpus"),
        emoji_annotations=[
            {"emoji":"🚨","senses":["urgency/alarm","hype-amplifier"]},
            {"emoji":"🙏","senses":["please/plea","thanks","prayer"]},
            {"emoji":"😂","senses":["literal laughter","'dying of laughter' -> inverts valence"]},
        ],
        time_record={"t_local":"2026-06-12T15:00:00-04:00","delta_t_s":0,
                     "cadence":"single","scale":"seconds"},
        stat_profile={"surprisal":"low (common word)","ledger":LEDGERS["MAP"]},
        intensity_map=diagonal({"trust_conf":1.0,"emot_activation":0.8,
                                 "stat_surprisal":0.2,"time_urgency":0.9}),
        warnings=["EMOT is a declared hypothesis, not a computed fact.",
                  "Emoji senses are plural by design; ambiguity is carried.",
                  "Diagonal valid only via the declared intensity map."])
    print(json.dumps({k:rec[k] for k in
          ["raw_text","symbol_coordinates","trust_check","emot_48",
           "diagonal_intensity_map","warnings"]}, indent=2, ensure_ascii=False))
