"""
finsler_metric.py -- Randers Metric F = α + β computation

Computes the Finsler-Randers metric on the Hachimoji 8-state simplex.
The metric is the UNIQUE geometry on Δ⁷ (proven by ChentsovFinite.lean):

    F(a→b) = α(a,b) + β(a,b)

    α: Fisher information metric (symmetric, from Chentsov uniqueness theorem)
    β: Drift 1-form (asymmetric, encodes torsion / wind field)

The Fisher metric is canonical: Chentsov's theorem proves it is the ONLY
Riemannian metric on the probability simplex that is invariant under all
Markov embeddings (stochastic refinements).

Reference: CoreFormalism/ChentsovFinite.lean -- chentsov_hachimoji theorem
"""

from __future__ import annotations

import math
from dataclasses import dataclass, field
from typing import Any, Optional

import numpy as np


# =========================================================================
# Q16_16 Fixed-Point Constants (from CoreFormalism/FixedPoint.lean)
# =========================================================================
Q16_SCALE: int = 65536  # 2^16 -- number of subdivisions per unit


def to_q16(value: float) -> int:
    """Convert float to Q16_16 raw integer with canonical rounding."""
    scaled = value * Q16_SCALE
    rounded = round(scaled)
    return max(-2147483648, min(2147483647, rounded))


def from_q16(raw: int) -> float:
    """Convert Q16_16 raw integer to float."""
    return raw / Q16_SCALE


# =========================================================================
# Hachimoji 8-State System
# =========================================================================

# Greek states with phases (45° steps, from HachimojiBase.lean §5)
GREEK_STATES: list[str] = ["\u03a6", "\u039b", "\u03a1", "\u039a", "\u03a9", "\u03a3", "\u03a0", "\u0396"]

# Phase angles in degrees (0, 45, 90, 135, 180, 225, 270, 315)
GREEK_PHASE: dict[str, float] = {
    "\u03a6": 0.0, "\u039b": 45.0, "\u03a1": 90.0, "\u039a": 135.0,
    "\u03a9": 180.0, "\u03a3": 225.0, "\u03a0": 270.0, "\u0396": 315.0,
}

# Latin ↔ Greek bijection (from HachimojiBase.lean §2)
LATIN_TO_GREEK: dict[str, str] = {
    "A": "\u03a6", "T": "\u039b", "G": "\u03a1", "C": "\u039a",
    "B": "\u03a9", "S": "\u03a3", "P": "\u03a0", "Z": "\u0396",
}

GREEK_TO_LATIN: dict[str, str] = {v: k for k, v in LATIN_TO_GREEK.items()}


# =========================================================================
# State Descriptors (4D HachimojiState)
# =========================================================================

@dataclass
class HachimojiState4D:
    """4D descriptor for a Hachimoji state on the probability simplex.

    Components:
        - symbol: Greek letter (Φ Λ Ρ Κ Ω Σ Π Ζ)
        - phase: angle on S¹ in degrees [0, 360)
        - probability: point on Δ⁷ (8 probabilities, sum to 1)
        - fisher_curvature: local Fisher information scalar at this state
        - drift: β component (wind field) at this state
    """
    symbol: str
    phase: float
    probability: np.ndarray  # 8-element, sums to 1
    fisher_curvature: float = 0.0
    drift: np.ndarray = field(default_factory=lambda: np.zeros(8))

    def __post_init__(self):
        self.probability = np.asarray(self.probability, dtype=float)
        self.drift = np.asarray(self.drift, dtype=float)
        # Normalize probability
        s = self.probability.sum()
        if s > 0:
            self.probability /= s

    def to_dict(self) -> dict:
        return {
            "symbol": self.symbol,
            "phase": self.phase,
            "probability": self.probability.tolist(),
            "fisher_curvature": self.fisher_curvature,
            "drift": self.drift.tolist(),
        }

    @classmethod
    def from_dict(cls, d: dict) -> "HachimojiState4D":
        return cls(
            symbol=d["symbol"],
            phase=d["phase"],
            probability=np.array(d["probability"]),
            fisher_curvature=d.get("fisher_curvature", 0.0),
            drift=np.array(d.get("drift", [0.0] * 8)),
        )


def make_uniform_hachimoji_states() -> list[HachimojiState4D]:
    """Create the 8 canonical Hachimoji states at the uniform distribution.

    Each state corresponds to one vertex of the Greek alphabet on Δ⁷.
    At the uniform distribution p_i = 1/8, the Fisher metric is:
        g_Fisher(X, X) = 8 * Σ_i X_i²  (since 1/p_i = 8)
    """
    states = []
    for sym in GREEK_STATES:
        phase = GREEK_PHASE[sym]
        # Uniform distribution on Δ⁷
        prob = np.ones(8) / 8.0
        # Fisher curvature at uniform: g_ii = 1/p_i = 8
        fisher_curvature = 8.0
        # Drift (β) points in the direction of increasing phase
        # This encodes the circular topology of the Hachimoji states
        drift = np.zeros(8)
        idx = GREEK_STATES.index(sym)
        # Wind field: stronger drift toward adjacent states on the circle
        drift[(idx + 1) % 8] = 0.3   # forward neighbor
        drift[(idx - 1) % 8] = -0.1  # backward neighbor
        states.append(HachimojiState4D(
            symbol=sym,
            phase=phase,
            probability=prob,
            fisher_curvature=fisher_curvature,
            drift=drift,
        ))
    return states


# =========================================================================
# Fisher Information Metric (α component -- symmetric, canonical)
# =========================================================================

def fisher_information_metric(
    p: np.ndarray,
    X: np.ndarray,
    Y: np.ndarray,
) -> float:
    """Compute the Fisher information metric g_Fisher(X, Y) at point p.

    g_Fisher(X, Y) = Σ_i X_i · Y_i / p_i

    This is the UNIQUE Riemannian metric on the probability simplex
    that is invariant under all Markov embeddings (Chentsov's theorem).

    Args:
        p: probability distribution (positive, sums to 1)
        X, Y: tangent vectors (components sum to 0)

    Returns:
        Fisher inner product
    """
    p = np.asarray(p, dtype=float)
    X = np.asarray(X, dtype=float)
    Y = np.asarray(Y, dtype=float)
    eps = 1e-12
    result = 0.0
    for i in range(len(p)):
        if p[i] > eps:
            result += X[i] * Y[i] / p[i]
    return float(result)


def fisher_metric_matrix(p: np.ndarray) -> np.ndarray:
    """Compute the Fisher metric matrix G_ij = δ_ij / p_i at point p.

    Returns:
        8×8 diagonal matrix with G_ii = 1/p_i
    """
    p = np.asarray(p, dtype=float)
    eps = 1e-12
    G = np.zeros((len(p), len(p)))
    for i in range(len(p)):
        if p[i] > eps:
            G[i, i] = 1.0 / p[i]
    return G


# =========================================================================
# Randers Finsler Metric: F = α + β
# =========================================================================

def compute_alpha_component(
    state_a: HachimojiState4D | dict,
    state_b: HachimojiState4D | dict,
) -> float:
    """Compute the α (Fisher) component: symmetric Riemannian distance.

    α(a,b) = arccosh(1 + ½ · g_Fisher(v, v))
    where v = b - a (tangent vector at the midpoint)

    For the uniform distribution, this simplifies to the
    Fisher-Rao distance on Δ⁷.
    """
    if isinstance(state_a, dict):
        state_a = HachimojiState4D.from_dict(state_a)
    if isinstance(state_b, dict):
        state_b = HachimojiState4D.from_dict(state_b)

    # Tangent vector: difference of probability distributions
    v = state_b.probability - state_a.probability

    # Use midpoint for metric evaluation
    p_mid = (state_a.probability + state_b.probability) / 2.0
    p_mid = np.maximum(p_mid, 1e-12)
    p_mid /= p_mid.sum()

    # Fisher norm: g_Fisher(v, v) = Σ_i v_i² / p_i
    fisher_norm_sq = fisher_information_metric(p_mid, v, v)

    # α = sqrt(g_Fisher(v, v)) -- the Riemannian length
    alpha = math.sqrt(max(0.0, fisher_norm_sq))
    return alpha


def compute_beta_component(
    state_a: HachimojiState4D | dict,
    state_b: HachimojiState4D | dict,
) -> float:
    """Compute the β (drift) component: antisymmetric 1-form.

    β(a,b) = ½ · (drift_a + drift_b) · (b - a)

    β is a 1-form: β(-v) = -β(v), so β(b,a) = -β(a,b).
    This encodes the torsion / wind field on the Hachimoji manifold.
    """
    if isinstance(state_a, dict):
        state_a = HachimojiState4D.from_dict(state_a)
    if isinstance(state_b, dict):
        state_b = HachimojiState4D.from_dict(state_b)

    # Average drift field
    avg_drift = (state_a.drift + state_b.drift) / 2.0

    # Displacement vector
    v = state_b.probability - state_a.probability

    # β = drift · v
    beta = float(np.dot(avg_drift, v))
    return beta


def compute_finsler_metric(
    state_a: HachimojiState4D | dict,
    state_b: HachimojiState4D | dict,
) -> float:
    """Compute Randers metric F(a→b) = α(a,b) + β(a,b).

    α: Fisher information metric (symmetric, from Chentsov uniqueness)
    β: Drift 1-form (asymmetric, encodes torsion)

    The metric is the UNIQUE geometry on the Hachimoji simplex
    (proven by ChentsovFinite.lean: chentsov_hachimoji theorem).

    Args:
        state_a: HachimojiState4D for source (or dict)
        state_b: HachimojiState4D for target (or dict)

    Returns:
        F(a→b): positive float, direction-dependent distance
    """
    alpha = compute_alpha_component(state_a, state_b)
    beta = compute_beta_component(state_a, state_b)

    # F = α + β, but ensure positivity
    # For a valid Finsler metric, we need α > |β|
    F = alpha + beta
    return max(F, 1e-10)  # clamp to positive


def compute_finsler_distance_matrix(
    states: list[HachimojiState4D | dict],
) -> np.ndarray:
    """Compute the full 8×8 Finsler distance matrix.

    D[i,j] = F(states[i] → states[j])

    Note: D is NOT symmetric because β is antisymmetric:
        D[i,j] ≠ D[j,i] when drift ≠ 0
    """
    n = len(states)
    D = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            if i != j:
                D[i, j] = compute_finsler_metric(states[i], states[j])
    return D


# =========================================================================
# Phase Distance on S¹ (circular topology)
# =========================================================================

def phase_distance_s1(phase_a: float, phase_b: float) -> float:
    """Compute the shortest distance between two phases on S¹.

    d_phase(a,b) = min(|a-b|, 360 - |a-b|) · π/180

    The phases live on a circle (0° = 360°), so the distance
    is the arc length along the shorter arc.
    """
    diff = abs(phase_a - phase_b)
    diff = min(diff, 360.0 - diff)
    # Convert to radians for geometric distance
    return diff * (math.pi / 180.0)


def circular_phase_matrix(states: list[HachimojiState4D | dict]) -> np.ndarray:
    """Compute the 8×8 phase distance matrix on S¹.

    P[i,j] = d_phase(phase_i, phase_j)² -- squared circular distance
    """
    n = len(states)
    P = np.zeros((n, n))
    for i in range(n):
        pa = states[i].phase if hasattr(states[i], "phase") else states[i]["phase"]
        for j in range(n):
            pb = states[j].phase if hasattr(states[j], "phase") else states[j]["phase"]
            d = phase_distance_s1(pa, pb)
            P[i, j] = d * d
    return P


# =========================================================================
# Fisher Metric (canonical) factory
# =========================================================================

def build_fisher_metric(states: list[HachimojiState4D]) -> dict:
    """Build the canonical Fisher metric dict for the state simplex.

    Returns dict compatible with compute_finsler_metric's
    fisher_metric parameter:
        {
            "type": "Fisher",
            "metric_matrix": 8×8 array,
            "chentsov_constant": c,  # positive constant from theorem
            "at_uniform": True,       # at p_i = 1/8
        }
    """
    # At the uniform distribution, the Fisher metric is 8·I
    G = fisher_metric_matrix(np.ones(8) / 8.0)
    return {
        "type": "Fisher",
        "metric_matrix": G.tolist(),
        "chentsov_constant": 1.0,  # c = 1 at uniform
        "at_uniform": True,
    }


# =========================================================================
# Convenience: full pipeline from states to distance matrix
# =========================================================================

def build_full_finsler_pipeline(
    states: Optional[list[HachimojiState4D]] = None,
) -> dict:
    """Run the full Finsler metric computation pipeline.

    Returns:
        {
            "states": [state dicts],
            "finsler_matrix": 8×8 distance matrix,
            "alpha_matrix": 8×8 symmetric α component,
            "beta_matrix": 8×8 antisymmetric β component,
            "phase_matrix": 8×8 phase distance on S¹,
            "fisher_metric": Fisher metric dict,
            "is_anisotropic": bool,  # True if β ≠ 0
        }
    """
    if states is None:
        states = make_uniform_hachimoji_states()

    n = len(states)
    F_mat = np.zeros((n, n))
    A_mat = np.zeros((n, n))
    B_mat = np.zeros((n, n))

    for i in range(n):
        for j in range(n):
            if i != j:
                A_mat[i, j] = compute_alpha_component(states[i], states[j])
                B_mat[i, j] = compute_beta_component(states[i], states[j])
                F_mat[i, j] = A_mat[i, j] + B_mat[i, j]

    P_mat = circular_phase_matrix(states)
    fisher = build_fisher_metric(states)

    # Check anisotropy: max |B_ij + B_ji| should be ~0 (antisymmetric)
    # but the Finsler matrix has asymmetric off-diagonals
    anisotropic = np.any(np.abs(B_mat) > 1e-9)

    return {
        "states": [s.to_dict() for s in states],
        "finsler_matrix": F_mat.tolist(),
        "alpha_matrix": A_mat.tolist(),
        "beta_matrix": B_mat.tolist(),
        "phase_matrix": P_mat.tolist(),
        "fisher_metric": fisher,
        "is_anisotropic": bool(anisotropic),
    }


if __name__ == "__main__":
    result = build_full_finsler_pipeline()
    print(f"Finsler metric computed for 8 Hachimoji states")
    print(f"Anisotropic: {result['is_anisotropic']}")
    print(f"Finsler matrix (first row): {result['finsler_matrix'][0]}")
    print(f"Alpha matrix (first row): {result['alpha_matrix'][0]}")
    print(f"Beta matrix (first row): {result['beta_matrix'][0]}")
