//! Totalizer encoding for cardinality constraints.
//!
//! The totalizer encoding is a recursive circuit for counting literals.
//! It provides an efficient encoding for at-least-k constraints with
//! incremental bound unfolding (lazy encoding).
//!
//! Reference: Z3's `opt/totalizer.cpp`, based on:
//! - Bailleux & Boufkhad (2003): "Efficient CNF Encoding of Boolean Cardinality Constraints"

use oxiz_sat::{Lit, Var};
use smallvec::SmallVec;

/// Clause generated by the totalizer encoding
#[derive(Debug, Clone)]
pub struct TotalizerClause {
    pub lits: SmallVec<[Lit; 4]>,
}

impl TotalizerClause {
    pub fn new(lits: impl IntoIterator<Item = Lit>) -> Self {
        Self {
            lits: lits.into_iter().collect(),
        }
    }
}

/// A node in the totalizer tree.
#[derive(Debug, Clone)]
struct TotalizerNode {
    /// Output literals for this node (index i represents "at least i+1 inputs are true")
    outputs: Vec<Option<Lit>>,
    /// Left child index (None for leaf nodes)
    left: Option<usize>,
    /// Right child index (None for leaf nodes)
    right: Option<usize>,
    /// Size (number of inputs in this subtree)
    size: usize,
}

impl TotalizerNode {
    /// Create a leaf node from a single literal
    fn leaf(lit: Lit) -> Self {
        Self {
            outputs: vec![Some(lit)],
            left: None,
            right: None,
            size: 1,
        }
    }

    /// Create an internal node from two children
    fn internal(left_idx: usize, right_idx: usize, left_size: usize, right_size: usize) -> Self {
        let size = left_size + right_size;
        Self {
            outputs: vec![None; size],
            left: Some(left_idx),
            right: Some(right_idx),
            size,
        }
    }
}

/// Totalizer encoding for cardinality constraints.
///
/// The totalizer builds a binary tree where:
/// - Leaves are input literals
/// - Internal nodes compute the sum of their children
/// - Output literals represent "at least k inputs are true"
#[derive(Debug)]
pub struct Totalizer {
    /// Nodes in the tree (stored flat for easier mutation)
    nodes: Vec<TotalizerNode>,
    /// Index of the root node
    root_idx: usize,
    /// Generated clauses (for the encoding)
    clauses: Vec<TotalizerClause>,
    /// Variable definitions (output var, definition)
    definitions: Vec<(Lit, Vec<Lit>)>,
    /// Next variable ID for fresh variables
    next_var: u32,
    /// Current bound that has been encoded
    encoded_bound: usize,
}

impl Totalizer {
    /// Create a new totalizer from input literals.
    ///
    /// This builds the tree structure but doesn't generate any clauses yet.
    /// Call `ensure_bound` to lazily encode up to a specific bound.
    pub fn new(inputs: &[Lit], next_var: u32) -> Self {
        if inputs.is_empty() {
            return Self {
                nodes: vec![TotalizerNode {
                    outputs: Vec::new(),
                    left: None,
                    right: None,
                    size: 0,
                }],
                root_idx: 0,
                clauses: Vec::new(),
                definitions: Vec::new(),
                next_var,
                encoded_bound: 0,
            };
        }

        let mut nodes = Vec::new();

        // Build leaf nodes
        let mut level_indices: Vec<usize> = inputs
            .iter()
            .map(|&lit| {
                let idx = nodes.len();
                nodes.push(TotalizerNode::leaf(lit));
                idx
            })
            .collect();

        // Build tree bottom-up
        while level_indices.len() > 1 {
            let mut new_level = Vec::new();
            let mut i = 0;
            while i + 1 < level_indices.len() {
                let left_idx = level_indices[i];
                let right_idx = level_indices[i + 1];
                let left_size = nodes[left_idx].size;
                let right_size = nodes[right_idx].size;

                let new_idx = nodes.len();
                nodes.push(TotalizerNode::internal(
                    left_idx, right_idx, left_size, right_size,
                ));
                new_level.push(new_idx);
                i += 2;
            }
            // Handle odd node (just move it up)
            if i < level_indices.len() {
                new_level.push(level_indices[i]);
            }
            level_indices = new_level;
        }

        let root_idx = level_indices[0];

        Self {
            nodes,
            root_idx,
            clauses: Vec::new(),
            definitions: Vec::new(),
            next_var,
            encoded_bound: 0,
        }
    }

    /// Get the size (number of input literals)
    pub fn size(&self) -> usize {
        self.nodes[self.root_idx].size
    }

    /// Get the next available variable ID
    pub fn next_var(&self) -> u32 {
        self.next_var
    }

    /// Ensure the totalizer is encoded up to bound k.
    ///
    /// This generates clauses for "at least k" constraints lazily.
    /// Returns the output literal for "at least k inputs are true",
    /// or None if k > size (always false).
    pub fn ensure_bound(&mut self, k: usize) -> Option<Lit> {
        if k == 0 {
            return None; // "at least 0" is always true
        }
        if k > self.size() {
            return None; // "at least k" is always false when k > size
        }

        // Encode bounds from encoded_bound+1 to k
        if k > self.encoded_bound {
            self.encode_node(self.root_idx, k);
            self.encoded_bound = k;
        }

        self.nodes[self.root_idx]
            .outputs
            .get(k - 1)
            .and_then(|opt| *opt)
    }

    /// Encode a node up to bound k
    fn encode_node(&mut self, node_idx: usize, k: usize) {
        let node = &self.nodes[node_idx];

        // Leaf nodes are already encoded (output = input)
        if node.left.is_none() {
            return;
        }

        let left_idx = node.left.expect("left child exists after is_none check");
        let right_idx = node.right.expect("right child exists when left exists");

        // Recursively encode children
        self.encode_node(left_idx, k);
        self.encode_node(right_idx, k);

        let node_size = self.nodes[node_idx].size;
        let left_size = self.nodes[left_idx].size;
        let right_size = self.nodes[right_idx].size;

        // Generate clauses for this node
        // For each sum i from 1 to min(k, left.size + right.size):
        //   output[i] <=> (left[j] & right[i-j]) for all valid j
        for i in 1..=k.min(node_size) {
            if self.nodes[node_idx]
                .outputs
                .get(i - 1)
                .is_some_and(|o| o.is_some())
            {
                continue; // Already encoded
            }

            // Create output variable
            let out_var = Var(self.next_var);
            self.next_var += 1;
            let out_lit = Lit::pos(out_var);
            self.nodes[node_idx].outputs[i - 1] = Some(out_lit);

            // Generate clauses:
            // For "at least i": left[j] & right[i-j] => output[i]
            // Which is: ~left[j] | ~right[i-j] | output[i]
            let mut definition = Vec::new();

            for j1 in 0..=i {
                let j2 = i - j1;

                if j1 > left_size || j2 > right_size {
                    continue;
                }

                let left_lit = if j1 == 0 {
                    None
                } else {
                    self.nodes[left_idx].outputs.get(j1 - 1).and_then(|o| *o)
                };

                let right_lit = if j2 == 0 {
                    None
                } else {
                    self.nodes[right_idx].outputs.get(j2 - 1).and_then(|o| *o)
                };

                // Generate clause: ~left[j1] | ~right[j2] | output[i]
                let mut clause_lits: SmallVec<[Lit; 4]> = SmallVec::new();
                if let Some(l) = left_lit {
                    clause_lits.push(l.negate());
                }
                if let Some(r) = right_lit {
                    clause_lits.push(r.negate());
                }

                // Skip if clause is empty (would mean output[i] is true)
                if clause_lits.is_empty() {
                    continue;
                }

                // Add definition tracking
                if let Some(l) = left_lit {
                    definition.push(l);
                }
                if let Some(r) = right_lit {
                    definition.push(r);
                }

                clause_lits.push(out_lit);
                self.clauses.push(TotalizerClause::new(clause_lits));
            }

            if !definition.is_empty() {
                self.definitions.push((out_lit, definition));
            }
        }
    }

    /// Get the literal for "at least k" constraint.
    /// Returns None if not yet encoded or k is out of range.
    pub fn at_least(&self, k: usize) -> Option<Lit> {
        if k == 0 {
            return None; // "at least 0" is always true
        }
        if k > self.size() {
            return None;
        }
        self.nodes[self.root_idx]
            .outputs
            .get(k - 1)
            .and_then(|opt| *opt)
    }

    /// Get the literal for "at most k" constraint.
    /// Returns the negation of "at least k+1".
    pub fn at_most(&self, k: usize) -> Option<Lit> {
        if k >= self.size() {
            return None; // "at most size" is always true
        }
        self.at_least(k + 1).map(|lit| lit.negate())
    }

    /// Take the generated clauses
    pub fn take_clauses(&mut self) -> Vec<TotalizerClause> {
        std::mem::take(&mut self.clauses)
    }

    /// Take the variable definitions
    pub fn take_definitions(&mut self) -> Vec<(Lit, Vec<Lit>)> {
        std::mem::take(&mut self.definitions)
    }

    /// Get the number of clauses generated
    pub fn num_clauses(&self) -> usize {
        self.clauses.len()
    }
}

/// Incremental totalizer that supports dynamic bound adjustment.
///
/// This is useful for RC2-style algorithms that need to weaken
/// cardinality bounds during solving.
#[derive(Debug)]
pub struct IncrementalTotalizer {
    /// The underlying totalizer
    totalizer: Totalizer,
    /// Current upper bound (at most k)
    current_bound: usize,
    /// Assumption literal for the current bound
    bound_assumption: Option<Lit>,
}

impl IncrementalTotalizer {
    /// Create a new incremental totalizer
    pub fn new(inputs: &[Lit], next_var: u32) -> Self {
        let size = inputs.len();
        Self {
            totalizer: Totalizer::new(inputs, next_var),
            current_bound: size,
            bound_assumption: None,
        }
    }

    /// Get the size (number of input literals)
    pub fn size(&self) -> usize {
        self.totalizer.size()
    }

    /// Get the next available variable ID
    pub fn next_var(&self) -> u32 {
        self.totalizer.next_var()
    }

    /// Set the current bound to "at most k"
    ///
    /// Returns the assumption literal to use, and any new clauses generated.
    pub fn set_bound(&mut self, k: usize) -> (Option<Lit>, Vec<TotalizerClause>) {
        self.current_bound = k;

        // Ensure encoding exists for bound k+1 (for at_most(k) = ~at_least(k+1))
        if k < self.totalizer.size() {
            self.totalizer.ensure_bound(k + 1);
            self.bound_assumption = self.totalizer.at_most(k);
        } else {
            self.bound_assumption = None;
        }

        let clauses = self.totalizer.take_clauses();
        (self.bound_assumption, clauses)
    }

    /// Weaken the bound by 1 (increase k by 1)
    pub fn weaken(&mut self) -> (Option<Lit>, Vec<TotalizerClause>) {
        self.set_bound(self.current_bound + 1)
    }

    /// Get the current bound
    pub fn current_bound(&self) -> usize {
        self.current_bound
    }

    /// Get the current bound assumption literal
    pub fn bound_assumption(&self) -> Option<Lit> {
        self.bound_assumption
    }

    /// Take variable definitions
    pub fn take_definitions(&mut self) -> Vec<(Lit, Vec<Lit>)> {
        self.totalizer.take_definitions()
    }
}

/// Build a simple pairwise at-most-one encoding.
///
/// For small numbers of literals, this is more efficient than the totalizer.
pub fn encode_at_most_one_pairwise(lits: &[Lit]) -> Vec<TotalizerClause> {
    let mut clauses = Vec::new();
    for i in 0..lits.len() {
        for j in (i + 1)..lits.len() {
            // ~x_i | ~x_j
            clauses.push(TotalizerClause::new([lits[i].negate(), lits[j].negate()]));
        }
    }
    clauses
}

/// Build an at-most-k encoding using the specified method.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CardinalityEncoding {
    /// Pairwise encoding (good for k=1, small n)
    Pairwise,
    /// Sequential counter encoding (good for small k)
    Sequential,
    /// Totalizer encoding (good for incremental/larger k)
    Totalizer,
    /// Sorted encoding using merge networks (good for balanced performance)
    Sorted,
    /// Cardinality networks using odd-even merge (good for hardware-inspired encodings)
    CardinalityNetwork,
}

/// Encode "at most k of inputs are true"
pub fn encode_at_most_k(
    inputs: &[Lit],
    k: usize,
    encoding: CardinalityEncoding,
    next_var: u32,
) -> (Vec<TotalizerClause>, Option<Lit>, u32) {
    match encoding {
        CardinalityEncoding::Pairwise if k == 1 => {
            (encode_at_most_one_pairwise(inputs), None, next_var)
        }
        CardinalityEncoding::Totalizer | CardinalityEncoding::Pairwise => {
            let mut tot = Totalizer::new(inputs, next_var);
            tot.ensure_bound(k + 1);
            let clauses = tot.take_clauses();
            let assumption = tot.at_most(k);
            (clauses, assumption, tot.next_var())
        }
        CardinalityEncoding::Sequential => {
            // Sequential counter: s[i][j] = "at least j of first i inputs are true"
            // This is more complex but good for small k
            encode_sequential_counter(inputs, k, next_var)
        }
        CardinalityEncoding::Sorted => encode_sorted(inputs, k, next_var),
        CardinalityEncoding::CardinalityNetwork => encode_sorted(inputs, k, next_var),
    }
}

/// Sequential counter encoding for at-most-k
fn encode_sequential_counter(
    inputs: &[Lit],
    k: usize,
    mut next_var: u32,
) -> (Vec<TotalizerClause>, Option<Lit>, u32) {
    if inputs.is_empty() || k >= inputs.len() {
        return (Vec::new(), None, next_var);
    }

    let n = inputs.len();
    let mut clauses = Vec::new();

    // s[i][j] = "at least j of inputs[0..=i] are true"
    // We only need s[i][j] for j in 1..=min(i+1, k+1)
    let mut s: Vec<Vec<Lit>> = Vec::new();

    for i in 0..n {
        let bound = (i + 1).min(k + 1);
        let mut row = Vec::new();

        for _ in 1..=bound {
            let var = Var(next_var);
            next_var += 1;
            row.push(Lit::pos(var));
        }

        s.push(row);
    }

    // Initial constraints for s[0]
    if !s.is_empty() && !s[0].is_empty() {
        // s[0][1] <=> inputs[0]
        // s[0][1] => inputs[0]: ~s[0][1] | inputs[0]
        // inputs[0] => s[0][1]: ~inputs[0] | s[0][1]
        clauses.push(TotalizerClause::new([s[0][0].negate(), inputs[0]]));
        clauses.push(TotalizerClause::new([inputs[0].negate(), s[0][0]]));
    }

    // Recursive constraints
    for i in 1..n {
        let prev_bound = i.min(k + 1);
        let curr_bound = (i + 1).min(k + 1);

        for j in 1..=curr_bound {
            // s[i][j] <=> s[i-1][j] | (inputs[i] & s[i-1][j-1])
            // where s[i-1][0] = true

            let curr = s[i][j - 1];
            let prev_j = if j <= prev_bound {
                Some(s[i - 1][j - 1])
            } else {
                None
            };
            let prev_j_minus_1 = if j > 1 && j - 1 <= prev_bound {
                Some(s[i - 1][j - 2])
            } else {
                None // s[i-1][0] is always true, or out of bounds
            };

            // s[i-1][j] => s[i][j]
            if let Some(pj) = prev_j {
                clauses.push(TotalizerClause::new([pj.negate(), curr]));
            }

            // inputs[i] & s[i-1][j-1] => s[i][j]
            if j == 1 {
                // inputs[i] => s[i][1]
                clauses.push(TotalizerClause::new([inputs[i].negate(), curr]));
            } else if let Some(pj1) = prev_j_minus_1 {
                // ~inputs[i] | ~s[i-1][j-1] | s[i][j]
                clauses.push(TotalizerClause::new([
                    inputs[i].negate(),
                    pj1.negate(),
                    curr,
                ]));
            }

            // s[i][j] => s[i-1][j] | (inputs[i] & s[i-1][j-1])
            // Contrapositive: ~s[i-1][j] & (~inputs[i] | ~s[i-1][j-1]) => ~s[i][j]
            // This is harder to encode efficiently, so we skip the reverse implication
            // for now (the encoding is still sound, just potentially weaker)
        }
    }

    // The at-most-k constraint is: ~s[n-1][k+1]
    let assumption = if k < n.min(k + 1) {
        Some(s[n - 1][k].negate())
    } else {
        None
    };

    (clauses, assumption, next_var)
}

/// Cardinality network encoding using merge-based sorting networks.
///
/// This encoding is based on odd-even merge sort. It creates a sorting
/// network that outputs sorted values, where the k-th output represents
/// "at least k inputs are true".
///
/// Reference: Batcher's odd-even merge sort
#[derive(Debug)]
pub struct CardinalityNetwork {
    /// Input literals
    #[allow(dead_code)]
    inputs: Vec<Lit>,
    /// Output literals (outputs\[i\] = "at least i+1 inputs are true")
    outputs: Vec<Lit>,
    /// Generated clauses
    clauses: Vec<TotalizerClause>,
    /// Next variable ID
    next_var: u32,
}

impl CardinalityNetwork {
    /// Create a new cardinality network
    pub fn new(inputs: Vec<Lit>, next_var: u32) -> Self {
        let n = inputs.len();
        let mut network = Self {
            inputs: inputs.clone(),
            outputs: Vec::new(),
            clauses: Vec::new(),
            next_var,
        };

        if n == 0 {
            return network;
        }

        // Build the sorting network
        let sorted = network.build_sorting_network(&inputs);
        network.outputs = sorted;

        network
    }

    /// Build a sorting network for the given inputs
    fn build_sorting_network(&mut self, inputs: &[Lit]) -> Vec<Lit> {
        let n = inputs.len();

        if n == 0 {
            return Vec::new();
        }

        if n == 1 {
            return vec![inputs[0]];
        }

        // Split inputs and recursively sort
        let mid = n / 2;
        let left = self.build_sorting_network(&inputs[..mid]);
        let right = self.build_sorting_network(&inputs[mid..]);

        // Merge the sorted halves
        self.odd_even_merge(&left, &right)
    }

    /// Odd-even merge of two sorted sequences
    fn odd_even_merge(&mut self, left: &[Lit], right: &[Lit]) -> Vec<Lit> {
        let n1 = left.len();
        let n2 = right.len();
        let n = n1 + n2;

        if n == 0 {
            return Vec::new();
        }

        if n == 1 {
            return vec![if n1 > 0 { left[0] } else { right[0] }];
        }

        if n == 2 {
            // Base case: merge two single elements using a comparator
            if n1 == 1 && n2 == 1 {
                return self.comparator(left[0], right[0]);
            } else if n1 == 2 {
                return vec![left[0], left[1]];
            } else {
                return vec![right[0], right[1]];
            }
        }

        // Split into odd and even indexed elements
        let left_odd: Vec<Lit> = left.iter().step_by(2).copied().collect();
        let left_even: Vec<Lit> = left.iter().skip(1).step_by(2).copied().collect();
        let right_odd: Vec<Lit> = right.iter().step_by(2).copied().collect();
        let right_even: Vec<Lit> = right.iter().skip(1).step_by(2).copied().collect();

        // Recursively merge odd and even subsequences
        let merged_odd = self.odd_even_merge(&left_odd, &right_odd);
        let merged_even = self.odd_even_merge(&left_even, &right_even);

        // Interleave and apply comparators
        let mut result = Vec::new();

        // First element is always from merged_odd
        if !merged_odd.is_empty() {
            result.push(merged_odd[0]);
        }

        // Apply comparators between adjacent elements
        let max_pairs = merged_odd.len().max(merged_even.len());
        for i in 0..max_pairs {
            if i < merged_even.len() && i + 1 < merged_odd.len() {
                let compared = self.comparator(merged_even[i], merged_odd[i + 1]);
                result.push(compared[0]);
                if i + 1 < max_pairs {
                    result.push(compared[1]);
                }
            } else if i < merged_even.len() {
                result.push(merged_even[i]);
            } else if i + 1 < merged_odd.len() {
                result.push(merged_odd[i + 1]);
            }
        }

        result.truncate(n);
        result
    }

    /// Create a comparator that takes two inputs and outputs them sorted
    /// Returns [max, min] where max = a OR b, min = a AND b
    fn comparator(&mut self, a: Lit, b: Lit) -> Vec<Lit> {
        // Output: max = a OR b (at least 1 of a,b is true)
        // Output: min = a AND b (at least 2 of a,b are true)

        let max_var = Var(self.next_var);
        self.next_var += 1;
        let max_lit = Lit::pos(max_var);

        let min_var = Var(self.next_var);
        self.next_var += 1;
        let min_lit = Lit::pos(min_var);

        // max <=> a OR b
        // max => a OR b: ~max | a | b
        self.clauses
            .push(TotalizerClause::new([max_lit.negate(), a, b]));
        // a => max: ~a | max
        self.clauses
            .push(TotalizerClause::new([a.negate(), max_lit]));
        // b => max: ~b | max
        self.clauses
            .push(TotalizerClause::new([b.negate(), max_lit]));

        // min <=> a AND b
        // a AND b => min: ~a | ~b | min
        self.clauses.push(TotalizerClause::new([
            a.negate(),
            b.negate(),
            min_lit.negate(),
        ]));
        // min => a: ~min | a
        self.clauses
            .push(TotalizerClause::new([min_lit.negate(), a]));
        // min => b: ~min | b
        self.clauses
            .push(TotalizerClause::new([min_lit.negate(), b]));

        vec![max_lit, min_lit]
    }

    /// Get the output literal for "at least k inputs are true"
    pub fn at_least(&self, k: usize) -> Option<Lit> {
        if k == 0 || k > self.outputs.len() {
            None
        } else {
            Some(self.outputs[k - 1])
        }
    }

    /// Get the output literal for "at most k inputs are true"
    pub fn at_most(&self, k: usize) -> Option<Lit> {
        if k >= self.outputs.len() {
            None
        } else {
            Some(self.outputs[k].negate())
        }
    }

    /// Get all generated clauses
    pub fn clauses(&self) -> &[TotalizerClause] {
        &self.clauses
    }

    /// Take the generated clauses
    pub fn take_clauses(self) -> Vec<TotalizerClause> {
        self.clauses
    }

    /// Get the next variable ID
    pub fn next_var(&self) -> u32 {
        self.next_var
    }
}

/// Sorted encoding using merge-based approach.
///
/// This is a simplified sorting network that uses a merge operation
/// to combine sorted sequences, similar to merge sort.
pub fn encode_sorted(
    inputs: &[Lit],
    k: usize,
    mut next_var: u32,
) -> (Vec<TotalizerClause>, Option<Lit>, u32) {
    if inputs.is_empty() || k >= inputs.len() {
        return (Vec::new(), None, next_var);
    }

    // Build sorting network
    let network = CardinalityNetwork::new(inputs.to_vec(), next_var);
    next_var = network.next_var();

    let assumption = network.at_most(k);
    let clauses = network.take_clauses();

    (clauses, assumption, next_var)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn var(v: u32) -> Var {
        Var(v)
    }

    fn pos(v: u32) -> Lit {
        Lit::pos(var(v))
    }

    #[test]
    fn test_totalizer_empty() {
        let tot = Totalizer::new(&[], 10);
        assert_eq!(tot.size(), 0);
    }

    #[test]
    fn test_totalizer_single() {
        let inputs = vec![pos(0)];
        let mut tot = Totalizer::new(&inputs, 10);
        assert_eq!(tot.size(), 1);

        let at_least_1 = tot.ensure_bound(1);
        assert_eq!(at_least_1, Some(pos(0)));

        let at_least_2 = tot.ensure_bound(2);
        assert!(at_least_2.is_none());
    }

    #[test]
    fn test_totalizer_two() {
        let inputs = vec![pos(0), pos(1)];
        let mut tot = Totalizer::new(&inputs, 10);
        assert_eq!(tot.size(), 2);

        let at_least_1 = tot.ensure_bound(1);
        assert!(at_least_1.is_some());

        let at_least_2 = tot.ensure_bound(2);
        assert!(at_least_2.is_some());

        // Should have generated some clauses
        assert!(tot.num_clauses() > 0);
    }

    #[test]
    fn test_totalizer_four() {
        let inputs = vec![pos(0), pos(1), pos(2), pos(3)];
        let mut tot = Totalizer::new(&inputs, 10);
        assert_eq!(tot.size(), 4);

        // Encode all bounds
        for k in 1..=4 {
            let lit = tot.ensure_bound(k);
            assert!(lit.is_some(), "at_least({k}) should have a literal");
        }

        // at_least(5) should be None
        let at_least_5 = tot.ensure_bound(5);
        assert!(at_least_5.is_none());

        // Take clauses
        let clauses = tot.take_clauses();
        assert!(!clauses.is_empty());
    }

    #[test]
    fn test_at_most_one_pairwise() {
        let lits = vec![pos(0), pos(1), pos(2)];
        let clauses = encode_at_most_one_pairwise(&lits);

        // Should have C(3,2) = 3 clauses
        assert_eq!(clauses.len(), 3);

        // Each clause should be binary
        for clause in &clauses {
            assert_eq!(clause.lits.len(), 2);
        }
    }

    #[test]
    fn test_incremental_totalizer() {
        let inputs = vec![pos(0), pos(1), pos(2), pos(3)];
        let mut inc_tot = IncrementalTotalizer::new(&inputs, 10);

        // Initial bound is size (at most 4)
        assert_eq!(inc_tot.current_bound(), 4);

        // Set bound to 2
        let (assumption, clauses) = inc_tot.set_bound(2);
        assert!(assumption.is_some());
        assert!(!clauses.is_empty());
        assert_eq!(inc_tot.current_bound(), 2);

        // Weaken to 3
        let (assumption2, _) = inc_tot.weaken();
        assert!(assumption2.is_some());
        assert_eq!(inc_tot.current_bound(), 3);
    }

    #[test]
    fn test_encode_at_most_k_pairwise() {
        let inputs = vec![pos(0), pos(1), pos(2)];
        let (clauses, _, _) = encode_at_most_k(&inputs, 1, CardinalityEncoding::Pairwise, 10);
        assert_eq!(clauses.len(), 3); // pairwise encoding
    }

    #[test]
    fn test_encode_at_most_k_totalizer() {
        let inputs = vec![pos(0), pos(1), pos(2), pos(3)];
        let (clauses, assumption, _) =
            encode_at_most_k(&inputs, 2, CardinalityEncoding::Totalizer, 10);
        assert!(!clauses.is_empty());
        assert!(assumption.is_some());
    }

    #[test]
    fn test_sequential_counter() {
        let inputs = vec![pos(0), pos(1), pos(2)];
        let (clauses, assumption, _) = encode_sequential_counter(&inputs, 1, 10);
        assert!(!clauses.is_empty());
        assert!(assumption.is_some());
    }

    #[test]
    fn test_cardinality_network_empty() {
        let network = CardinalityNetwork::new(vec![], 10);
        assert_eq!(network.outputs.len(), 0);
    }

    #[test]
    fn test_cardinality_network_single() {
        let inputs = vec![pos(0)];
        let network = CardinalityNetwork::new(inputs, 10);
        assert_eq!(network.outputs.len(), 1);
        assert_eq!(network.at_least(1), Some(pos(0)));
    }

    #[test]
    fn test_cardinality_network_two() {
        let inputs = vec![pos(0), pos(1)];
        let network = CardinalityNetwork::new(inputs, 10);
        assert_eq!(network.outputs.len(), 2);
        assert!(network.at_least(1).is_some());
        assert!(network.at_least(2).is_some());
        assert!(!network.clauses().is_empty());
    }

    #[test]
    fn test_cardinality_network_four() {
        let inputs = vec![pos(0), pos(1), pos(2), pos(3)];
        let network = CardinalityNetwork::new(inputs, 10);
        assert_eq!(network.outputs.len(), 4);

        // All bounds should be available
        for k in 1..=4 {
            assert!(network.at_least(k).is_some(), "at_least({k}) should exist");
        }

        // at_most tests
        for k in 0..3 {
            assert!(network.at_most(k).is_some(), "at_most({k}) should exist");
        }
    }

    #[test]
    fn test_encode_sorted() {
        let inputs = vec![pos(0), pos(1), pos(2), pos(3)];
        let (clauses, assumption, next) = encode_sorted(&inputs, 2, 10);
        assert!(!clauses.is_empty());
        assert!(assumption.is_some());
        assert!(next > 10);
    }

    #[test]
    fn test_encode_at_most_k_sorted() {
        let inputs = vec![pos(0), pos(1), pos(2)];
        let (clauses, assumption, _) =
            encode_at_most_k(&inputs, 1, CardinalityEncoding::Sorted, 10);
        assert!(!clauses.is_empty());
        assert!(assumption.is_some());
    }

    #[test]
    fn test_encode_at_most_k_cardinality_network() {
        let inputs = vec![pos(0), pos(1), pos(2), pos(3)];
        let (clauses, assumption, _) =
            encode_at_most_k(&inputs, 2, CardinalityEncoding::CardinalityNetwork, 10);
        assert!(!clauses.is_empty());
        assert!(assumption.is_some());
    }
}
