//! EVA Algebra — Composition operators for BRIK-64 monomers.
//!
//! Three composition laws:
//!
//! | Operator | Name        | Law                                      |
//! |----------|-------------|------------------------------------------|
//! | `⊗`      | Sequential  | `(A ⊗ B)(x) = B(A(x))`                  |
//! | `∥`      | Parallel    | `(A ∥ B)((x, y)) = (A(x), B(y))`        |
//! | `⊕`      | Conditional | `(A ⊕ B)(cond, x) = if cond { A(x) } else { B(x) }` |
//!
//! ## Composition Notes
//!
//! These helpers model the public EVA composition vocabulary for examples and
//! local integration. Formal closure or certification claims require separate
//! BRIK64 evidence artifacts and are not established by importing this crate.
//!
//! ## Usage
//!
//! ```rust
//! use brik64_core::{mc, eva};
//!
//! // Sequential: add8 then check even
//! let pipeline = eva::seq(
//!     |x: (u8, u8)| mc::add8(x.0, x.1),
//!     |sum| sum % 2 == 0,
//! );
//! assert_eq!(pipeline((3u8, 5u8)), true); // 8 is even
//!
//! // Parallel: add8 on first pair, xor8 on second
//! let dual = eva::par(
//!     |x: (u8, u8)| mc::add8(x.0, x.1),
//!     |x: (u8, u8)| mc::xor8(x.0, x.1),
//! );
//! let (a, b) = dual((10u8, 5u8), (0b1100u8, 0b1010u8));
//! assert_eq!(a, 15);
//! assert_eq!(b, 0b0110);
//!
//! // Conditional: even numbers get doubled, odd get halved
//! let transform = eva::cond(
//!     |x: &i64| *x % 2 == 0,
//!     |x| x * 2,
//!     |x| x / 2,
//! );
//! assert_eq!(transform(4i64), 8);
//! assert_eq!(transform(5i64), 2);
//! ```

/// `⊗` Sequential composition.
///
/// Apply `f` first, then `g` to its result. This is function composition in
/// left-to-right order (data pipeline order).
///
/// ```
/// use brik64_core::eva::seq;
/// use brik64_core::mc::{add8, mod8};
///
/// let pipeline = seq(|x: (u8, u8)| add8(x.0, x.1), |s| mod8(s, 10));
/// assert_eq!(pipeline((7u8, 8u8)), 5); // add8(7,8)=15, mod8(15,10)=5
/// ```
pub fn seq<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> C
where
    F: Fn(A) -> B,
    G: Fn(B) -> C,
{
    move |a| g(f(a))
}

/// `∥` Parallel composition.
///
/// Apply `f` to the first argument and `g` to the second, independently.
/// Commutative: `par(f, g)((a, b)) = par(g, f)((b, a))`.
///
/// ```
/// use brik64_core::eva::par;
/// use brik64_core::mc::{add8, xor8};
///
/// let dual = par(|x: (u8, u8)| add8(x.0, x.1), |x: (u8, u8)| xor8(x.0, x.1));
/// assert_eq!(dual((10u8, 5u8), (0b1100u8, 0b1010u8)), (15, 0b0110));
/// ```
pub fn par<A1, A2, B1, B2, F, G>(f: F, g: G) -> impl Fn(A1, A2) -> (B1, B2)
where
    F: Fn(A1) -> B1,
    G: Fn(A2) -> B2,
{
    move |a, b| (f(a), g(b))
}

/// `⊕` Conditional composition.
///
/// Evaluate `pred(x)` first; if true apply `f(x)`, else apply `g(x)`.
///
/// ```
/// use brik64_core::eva::cond;
///
/// let transform = cond(|x: &i64| *x % 2 == 0, |x| x * 2, |x| x / 2);
/// assert_eq!(transform(4i64), 8);
/// assert_eq!(transform(5i64), 2);
/// ```
pub fn cond<T, B, P, F, G>(pred: P, f: F, g: G) -> impl Fn(T) -> B
where
    T: Copy,
    P: Fn(&T) -> bool,
    F: Fn(T) -> B,
    G: Fn(T) -> B,
{
    move |x| if pred(&x) { f(x) } else { g(x) }
}

/// Apply a pipeline of sequential steps from a Vec of boxed functions.
///
/// Useful for dynamic composition where the number of steps is not known at
/// compile time.
///
/// ```
/// use brik64_core::eva::pipeline;
///
/// let steps: Vec<Box<dyn Fn(i64) -> i64>> = vec![
///     Box::new(|x| x + 1),
///     Box::new(|x| x * 2),
///     Box::new(|x| x - 3),
/// ];
/// assert_eq!(pipeline(5i64, &steps), (5 + 1) * 2 - 3); // 9
/// ```
pub fn pipeline<T, F>(init: T, steps: &[F]) -> T
where
    F: Fn(T) -> T,
{
    steps.iter().fold(init, |acc, f| f(acc))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::mc::{add8, mod8};

    #[test]
    fn seq_composition() {
        let p = seq(|x: (u8, u8)| add8(x.0, x.1), |s| mod8(s, 10));
        assert_eq!(p((7u8, 8u8)), 5);
    }

    #[test]
    fn par_independence() {
        let d = par(|x: (u8, u8)| add8(x.0, x.1), |b: u8| !b);
        let (sum, inv) = d((3u8, 4u8), 0b10101010u8);
        assert_eq!(sum, 7);
        assert_eq!(inv, 0b01010101);
    }

    #[test]
    fn cond_branching() {
        let f = cond(|x: &i64| *x > 0, |x| x * 10, |x| -x);
        assert_eq!(f(3i64), 30);
        assert_eq!(f(-5i64), 5);
    }

    #[test]
    fn pipeline_chain() {
        let steps: Vec<Box<dyn Fn(i64) -> i64>> =
            vec![Box::new(|x| x + 1), Box::new(|x| x * 2)];
        assert_eq!(pipeline(5i64, &steps), 12);
    }
}
