//! The sim capability contract + the reference registrant.
//!
//! The extension deliberately standardizes **no physics** (extension
//! §6.4): the moment it specifies a solver it stops being a container
//! standard and starts being an engine. What it standardizes is the
//! contract any sim must honor — deterministic under the declared
//! numeric profile, pure per tick, convergent under canonical order —
//! and this trait is that contract in code.
//!
//! [`Kinematic0`] (`wai.world.sim.kinematic0`) is the reference
//! registrant: point-mass kinematics + one scalar field per node, all
//! Q32.32. Small on purpose — rich enough to exercise every normative
//! mechanism (create/delete, LWW and SUM conflicts, integration over
//! ticks), simple enough that an independent implementation is an
//! afternoon, which is what a conformance corpus needs.

use super::fixed::Fx;
use super::graph::WorldState;
use super::op::{mint_node_id, CausalOp};
use super::rng::Xoshiro256pp;
use super::{SimContract, WorldError};

/// Which registered resolution function a mutation kind maps to
/// (extension §6.5). `Lww` rides the canonical total order — applying
/// ops in canonical order makes "last writer" deterministic for free.
/// `Sum` kinds MUST be commutative-monoid mutations (order within a
/// tick provably cannot matter).
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Resolve {
    Lww,
    Sum,
}

/// A registered sim capability.
///
/// Determinism obligations on every implementation:
/// - `apply` and `step` use only the declared numeric profile and the
///   supplied [`Xoshiro256pp`] — no platform floats, no ambient state.
/// - Ops targeting absent nodes are deterministic no-ops (an op set
///   must mean the same thing on every sink, including sinks that saw
///   a delete win a race).
/// - `serialize_node` defines the node's canonical field order — it IS
///   the registration's normative state layout.
pub trait Sim {
    type Node: Clone;

    /// The registered capability string, e.g. `wai.world.sim.kinematic0`.
    fn capability(&self) -> &'static str;

    /// Static mutation-kind → resolution mapping. `None` = unregistered
    /// kind (the op is invalid, not ignorable: extension §5 ops are
    /// signed claims, and an unknown claim must fail loudly).
    fn resolve_kind(&self, mutation_kind: u16) -> Option<Resolve>;

    /// Apply one op's mutation at its effect tick.
    fn apply(&self, world: &mut WorldState<Self::Node>, op: &CausalOp) -> Result<(), WorldError>;

    /// Integrate one tick: `state(t) = step(state(t-1) + ops_at(t))`.
    fn step(
        &self,
        world: &mut WorldState<Self::Node>,
        contract: &SimContract,
        tick: u64,
        rng: &mut Xoshiro256pp,
    );

    /// Canonical node-state serialization (normative field order).
    fn serialize_node(&self, node: &Self::Node, out: &mut Vec<u8>);

    /// Inverse of `serialize_node`, for graph-section load.
    fn parse_node(&self, bytes: &[u8]) -> Result<Self::Node, WorldError>;
}

// ---------------------------------------------------------------------------
// Reference registrant: wai.world.sim.kinematic0
// ---------------------------------------------------------------------------

pub const CAP_SIM_KINEMATIC0: &str = "wai.world.sim.kinematic0";

/// Mutation kinds registered by kinematic0. Payloads are packed
/// little-endian `i64` Fx bit patterns.
pub mod kin_mutation {
    /// `pos[3] | vel[3] | scalar` (7 × i64). Target MUST equal
    /// `mint_node_id(actor, lamport)`. Resolution: LWW.
    pub const CREATE: u16 = 0x0001;
    /// Empty payload. Resolution: LWW.
    pub const DELETE: u16 = 0x0002;
    /// `scalar` (1 × i64). Resolution: LWW.
    pub const SET_SCALAR: u16 = 0x0003;
    /// `dv[3]` (3 × i64) added to velocity. Resolution: SUM — two
    /// actors shoving the same node both act; neither is discarded.
    pub const IMPULSE: u16 = 0x0004;
    /// `pos[3]` (3 × i64). Resolution: LWW.
    pub const SET_POS: u16 = 0x0005;
}

/// Node state: position, velocity, one scalar field. Canonical field
/// order (and therefore hash order) is exactly this struct order.
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct KinNode {
    pub pos: [Fx; 3],
    pub vel: [Fx; 3],
    pub scalar: Fx,
}

pub struct Kinematic0;

impl Sim for Kinematic0 {
    type Node = KinNode;

    fn capability(&self) -> &'static str {
        CAP_SIM_KINEMATIC0
    }

    fn resolve_kind(&self, mutation_kind: u16) -> Option<Resolve> {
        match mutation_kind {
            kin_mutation::CREATE | kin_mutation::DELETE | kin_mutation::SET_SCALAR
            | kin_mutation::SET_POS => Some(Resolve::Lww),
            kin_mutation::IMPULSE => Some(Resolve::Sum),
            _ => None,
        }
    }

    fn apply(&self, world: &mut WorldState<KinNode>, op: &CausalOp) -> Result<(), WorldError> {
        match op.mutation_kind {
            kin_mutation::CREATE => {
                if op.target != mint_node_id(&op.actor, op.lamport) {
                    return Err(WorldError::BadNodeMint);
                }
                let v = take_fx::<7>(&op.mutation)?;
                world.nodes.insert(
                    op.target,
                    KinNode { pos: [v[0], v[1], v[2]], vel: [v[3], v[4], v[5]], scalar: v[6] },
                );
            }
            kin_mutation::DELETE => {
                if !op.mutation.is_empty() {
                    return Err(WorldError::BadMutation);
                }
                world.nodes.remove(&op.target);
            }
            kin_mutation::SET_SCALAR => {
                let v = take_fx::<1>(&op.mutation)?;
                if let Some(n) = world.nodes.get_mut(&op.target) {
                    n.scalar = v[0];
                }
            }
            kin_mutation::IMPULSE => {
                let v = take_fx::<3>(&op.mutation)?;
                if let Some(n) = world.nodes.get_mut(&op.target) {
                    for (vel, dv) in n.vel.iter_mut().zip(v) {
                        *vel = *vel + dv;
                    }
                }
            }
            kin_mutation::SET_POS => {
                let v = take_fx::<3>(&op.mutation)?;
                if let Some(n) = world.nodes.get_mut(&op.target) {
                    n.pos = [v[0], v[1], v[2]];
                }
            }
            k => return Err(WorldError::UnknownMutationKind(k)),
        }
        Ok(())
    }

    fn step(
        &self,
        world: &mut WorldState<KinNode>,
        contract: &SimContract,
        _tick: u64,
        _rng: &mut Xoshiro256pp,
    ) {
        // dt = 1 / tick_rate in Q32.32 — itself a deterministic Fx op,
        // so every sink integrates with the identical dt bit pattern.
        let dt = Fx::ONE / Fx::from_int(contract.tick_rate as i32);
        for node in world.nodes.values_mut() {
            for i in 0..3 {
                node.pos[i] = node.pos[i] + node.vel[i] * dt;
            }
        }
    }

    fn serialize_node(&self, node: &KinNode, out: &mut Vec<u8>) {
        for v in node.pos.iter().chain(node.vel.iter()).chain([&node.scalar]) {
            out.extend_from_slice(&v.to_bits().to_le_bytes());
        }
    }

    fn parse_node(&self, bytes: &[u8]) -> Result<KinNode, WorldError> {
        let v = take_fx::<7>(bytes)?;
        Ok(KinNode { pos: [v[0], v[1], v[2]], vel: [v[3], v[4], v[5]], scalar: v[6] })
    }
}

// ---------------------------------------------------------------------------
// Second registrant: wai.world.sim.ballistic0
// ---------------------------------------------------------------------------

pub const CAP_SIM_BALLISTIC0: &str = "wai.world.sim.ballistic0";

/// Mutation kinds registered by ballistic0. Kind numbers are per-sim
/// (the sim capability owns `mutation_kind` semantics); ballistic0
/// mirrors kinematic0's numbering where the semantics coincide.
pub mod bal_mutation {
    /// `pos[3] | vel[3] | radius | restitution` (8 × i64). Target MUST
    /// equal `mint_node_id(actor, lamport)`. Resolution: LWW.
    pub const CREATE: u16 = 0x0001;
    /// Empty payload. Resolution: LWW.
    pub const DELETE: u16 = 0x0002;
    /// `dv[3]` (3 × i64) added to velocity. Resolution: SUM.
    pub const IMPULSE: u16 = 0x0004;
    /// `pos[3]` (3 × i64). Resolution: LWW.
    pub const SET_POS: u16 = 0x0005;
}

/// Ballistic node: position, velocity, collision radius, restitution.
/// Canonical field order is exactly this struct order.
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct BalNode {
    pub pos: [Fx; 3],
    pub vel: [Fx; 3],
    pub radius: Fx,
    pub restitution: Fx,
}

/// Gravity + ground-plane bounce: per tick, `vel.y += G·dt`,
/// `pos += vel·dt`, and a node that has fallen through the ground
/// plane (`pos.y < radius` while moving down) is clamped to the
/// surface with `vel.y ← −vel.y · restitution`. `G = −10` in world
/// units/s² is a constant of this registration. Everything is
/// `wai.det.fixed64`, so the bounce sequence — including the decaying
/// tail where saturating arithmetic settles the node — is bit-exact
/// on every machine. The registrant exists to prove the registration
/// story: a second sim entered the registry without touching the
/// extension.
pub struct Ballistic0;

impl Ballistic0 {
    const GRAVITY: Fx = Fx::from_int(-10);
}

impl Sim for Ballistic0 {
    type Node = BalNode;

    fn capability(&self) -> &'static str {
        CAP_SIM_BALLISTIC0
    }

    fn resolve_kind(&self, mutation_kind: u16) -> Option<Resolve> {
        match mutation_kind {
            bal_mutation::CREATE | bal_mutation::DELETE | bal_mutation::SET_POS => Some(Resolve::Lww),
            bal_mutation::IMPULSE => Some(Resolve::Sum),
            _ => None,
        }
    }

    fn apply(&self, world: &mut WorldState<BalNode>, op: &CausalOp) -> Result<(), WorldError> {
        match op.mutation_kind {
            bal_mutation::CREATE => {
                if op.target != mint_node_id(&op.actor, op.lamport) {
                    return Err(WorldError::BadNodeMint);
                }
                let v = take_fx::<8>(&op.mutation)?;
                world.nodes.insert(
                    op.target,
                    BalNode {
                        pos: [v[0], v[1], v[2]],
                        vel: [v[3], v[4], v[5]],
                        radius: v[6],
                        restitution: v[7],
                    },
                );
            }
            bal_mutation::DELETE => {
                if !op.mutation.is_empty() {
                    return Err(WorldError::BadMutation);
                }
                world.nodes.remove(&op.target);
            }
            bal_mutation::IMPULSE => {
                let v = take_fx::<3>(&op.mutation)?;
                if let Some(n) = world.nodes.get_mut(&op.target) {
                    for (vel, dv) in n.vel.iter_mut().zip(v) {
                        *vel = *vel + dv;
                    }
                }
            }
            bal_mutation::SET_POS => {
                let v = take_fx::<3>(&op.mutation)?;
                if let Some(n) = world.nodes.get_mut(&op.target) {
                    n.pos = [v[0], v[1], v[2]];
                }
            }
            k => return Err(WorldError::UnknownMutationKind(k)),
        }
        Ok(())
    }

    fn step(
        &self,
        world: &mut WorldState<BalNode>,
        contract: &SimContract,
        _tick: u64,
        _rng: &mut Xoshiro256pp,
    ) {
        let dt = Fx::ONE / Fx::from_int(contract.tick_rate as i32);
        for node in world.nodes.values_mut() {
            node.vel[1] = node.vel[1] + Self::GRAVITY * dt;
            for i in 0..3 {
                node.pos[i] = node.pos[i] + node.vel[i] * dt;
            }
            if node.pos[1] < node.radius && node.vel[1] < Fx::ZERO {
                node.pos[1] = node.radius;
                node.vel[1] = -(node.vel[1] * node.restitution);
            }
        }
    }

    fn serialize_node(&self, node: &BalNode, out: &mut Vec<u8>) {
        for v in node
            .pos
            .iter()
            .chain(node.vel.iter())
            .chain([&node.radius, &node.restitution])
        {
            out.extend_from_slice(&v.to_bits().to_le_bytes());
        }
    }

    fn parse_node(&self, bytes: &[u8]) -> Result<BalNode, WorldError> {
        let v = take_fx::<8>(bytes)?;
        Ok(BalNode {
            pos: [v[0], v[1], v[2]],
            vel: [v[3], v[4], v[5]],
            radius: v[6],
            restitution: v[7],
        })
    }
}

/// Mutation payload helpers: encode/decode packed `i64` Fx vectors.
pub fn pack_fx(values: &[Fx]) -> Vec<u8> {
    let mut out = Vec::with_capacity(values.len() * 8);
    for v in values {
        out.extend_from_slice(&v.to_bits().to_le_bytes());
    }
    out
}

fn take_fx<const N: usize>(bytes: &[u8]) -> Result<[Fx; N], WorldError> {
    if bytes.len() != N * 8 {
        return Err(WorldError::BadMutation);
    }
    let mut out = [Fx::ZERO; N];
    for (i, chunk) in bytes.chunks_exact(8).enumerate() {
        out[i] = Fx::from_bits(i64::from_le_bytes(chunk.try_into().unwrap()));
    }
    Ok(out)
}
