use crate::core::BitCollection;
use bitvec::prelude::*;
use half::f16;
use lru::LruCache;
use memchr::memmem;
use once_cell::sync::Lazy;
use pyo3::exceptions::{PyIndexError, PyOverflowError, PyRuntimeError, PyTypeError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::{PyBool, PyByteArray, PyBytes, PyInt, PyList, PyMemoryView, PyTuple};
use pyo3::{PyErr, ffi};
use rand::rngs::{StdRng, SysRng};
use rand::{Rng, SeedableRng, TryRng};
use sha2::{Digest, Sha256};
use std::borrow::Cow;
use std::num::NonZeroUsize;
use std::sync::Mutex;

pub type BV = BitVec<u8, Msb0>;
pub type BS = BitSlice<u8, Msb0>;

// Define a static LRU cache.
const BITS_CACHE_SIZE: usize = 1024;
pub(crate) const SIGNAL_CHECK_INTERVAL: usize = 65_536;
static BITS_CACHE: Lazy<Mutex<LruCache<String, BV>>> =
    Lazy::new(|| Mutex::new(LruCache::new(NonZeroUsize::new(BITS_CACHE_SIZE).unwrap())));

pub(crate) fn validate_length(length: i64) -> PyResult<usize> {
    if length < 0 {
        Err(PyValueError::new_err(format!(
            "Negative bit length given: {length}."
        )))
    } else {
        Ok(length as usize)
    }
}

pub(crate) fn validate_logical_op_lengths(a: usize, b: usize) -> PyResult<()> {
    if a != b {
        Err(PyValueError::new_err(format!(
            "For logical operations the lengths of both objects must match. Received lengths of {a} and {b} bits."
        )))
    } else {
        Ok(())
    }
}

pub(crate) fn convert_to_bool(bit: &Bound<'_, PyAny>) -> Option<bool> {
    if let Ok(b) = bit.cast::<PyBool>() {
        Some(b.is_true())
    } else if let Ok(val) = bit.extract::<i64>() {
        match val {
            0 => Some(false),
            1 => Some(true),
            _ => None,
        }
    } else {
        None
    }
}

// An implementation of the KMP algorithm for bit slices.
pub(crate) fn compute_lps(py: Python<'_>, pattern: &BS) -> PyResult<Vec<usize>> {
    let len = pattern.len();
    let mut lps = vec![0; len];
    let mut i = 1;
    let mut len_prev = 0;
    let mut check_at = SIGNAL_CHECK_INTERVAL.min(len);

    while i < len {
        while i < check_at {
            match pattern[i] == pattern[len_prev] {
                true => {
                    len_prev += 1;
                    lps[i] = len_prev;
                    i += 1;
                }
                false if len_prev != 0 => len_prev = lps[len_prev - 1],
                false => {
                    lps[i] = 0;
                    i += 1;
                }
            }
        }
        if i < len {
            py.check_signals()?;
            check_at = i.saturating_add(SIGNAL_CHECK_INTERVAL).min(len);
        }
    }
    Ok(lps)
}

pub(crate) fn find_bitvec(
    py: Python<'_>,
    haystack: &BS,
    needle: &BS,
    start: usize,
    end: usize,
    byte_aligned: bool,
) -> PyResult<Option<usize>> {
    debug_assert!(end >= start);
    debug_assert!(end <= haystack.len());
    let lps = compute_lps(py, needle)?;
    let alignment_mod8 = if byte_aligned { Some(0) } else { None };
    find_bitvec_with_lps_aligned(py, haystack, needle, &lps, start, end, alignment_mod8)
}

pub(crate) fn find_bitvec_aligned(
    py: Python<'_>,
    haystack: &BS,
    needle: &BS,
    start: usize,
    end: usize,
    alignment_mod8: Option<usize>,
) -> PyResult<Option<usize>> {
    debug_assert!(end >= start);
    debug_assert!(end <= haystack.len());
    let lps = compute_lps(py, needle)?;
    find_bitvec_with_lps_aligned(py, haystack, needle, &lps, start, end, alignment_mod8)
}

pub(crate) fn find_bitvec_with_lps_aligned(
    py: Python<'_>,
    haystack: &BS,
    needle: &BS,
    lps: &[usize],
    start: usize,
    end: usize,
    alignment_mod8: Option<usize>,
) -> PyResult<Option<usize>> {
    debug_assert!(end >= start);
    debug_assert!(end <= haystack.len());
    if let Some(found) = try_find_byte_search(haystack, needle, start, end, alignment_mod8, false) {
        return Ok(found);
    }
    find_bitvec_impl_with_lps_aligned(py, haystack, needle, lps, start, end, alignment_mod8)
}

pub(crate) fn rfind_bitvec_aligned(
    py: Python<'_>,
    haystack: &BS,
    needle: &BS,
    start: usize,
    end: usize,
    alignment_mod8: Option<usize>,
) -> PyResult<Option<usize>> {
    debug_assert!(end >= start);
    debug_assert!(end <= haystack.len());
    let lps = compute_lps(py, needle)?;
    rfind_bitvec_with_lps_aligned(py, haystack, needle, &lps, start, end, alignment_mod8)
}

pub(crate) fn rfind_bitvec_with_lps_aligned(
    py: Python<'_>,
    haystack: &BS,
    needle: &BS,
    lps: &[usize],
    start: usize,
    end: usize,
    alignment_mod8: Option<usize>,
) -> PyResult<Option<usize>> {
    debug_assert!(end >= start);
    debug_assert!(end <= haystack.len());
    if let Some(found) = try_find_byte_search(haystack, needle, start, end, alignment_mod8, true) {
        return Ok(found);
    }
    rfind_bitvec_impl_with_lps_aligned(py, haystack, needle, lps, start, end, alignment_mod8)
}

pub(crate) fn collect_find_all_positions(
    py: Python<'_>,
    haystack: &BS,
    needle: &BS,
    _haystack_len: usize,
    start: usize,
    end: usize,
    byte_aligned: bool,
) -> PyResult<Vec<u64>> {
    debug_assert!(!needle.is_empty());
    debug_assert!(end >= start);
    debug_assert!(end <= haystack.len());

    let alignment_mod8 = if byte_aligned { Some(0) } else { None };
    if needle.len() == 1 {
        return collect_single_bit_positions(py, haystack, needle[0], start, end, alignment_mod8);
    }

    if let Some((byte_haystack, byte_needle, byte_base)) =
        byte_search_prep(haystack, needle, start, end, alignment_mod8)
    {
        let mut matches = Vec::new();
        let mut byte_current = 0;
        let mut check_at = SIGNAL_CHECK_INTERVAL;

        loop {
            if matches.len() >= check_at {
                py.check_signals()?;
                check_at = matches.len().saturating_add(SIGNAL_CHECK_INTERVAL);
            }
            let found = if byte_current >= byte_haystack.len() {
                None
            } else {
                memmem::find(&byte_haystack[byte_current..], &byte_needle)
                    .map(|pos| pos + byte_current)
            };

            let Some(byte_pos) = found else {
                break;
            };
            matches.push(((byte_base + byte_pos) * 8) as u64);
            byte_current = byte_pos + 1;
        }

        return Ok(matches);
    }

    collect_find_all_positions_kmp(py, haystack, needle, start, end, alignment_mod8)
}

fn collect_single_bit_positions(
    py: Python<'_>,
    haystack: &BS,
    value: bool,
    start: usize,
    end: usize,
    alignment_mod8: Option<usize>,
) -> PyResult<Vec<u64>> {
    let mut matches = Vec::new();
    let mut check_at = SIGNAL_CHECK_INTERVAL;

    if let Some(required) = alignment_mod8 {
        let start_mod = start & 7;
        let adjustment = (required + 8 - start_mod) & 7;
        let mut pos = start.saturating_add(adjustment);
        while pos < end {
            if haystack[pos] == value {
                matches.push(pos as u64);
                if matches.len() >= check_at {
                    py.check_signals()?;
                    check_at = matches.len().saturating_add(SIGNAL_CHECK_INTERVAL);
                }
            }
            pos += 8;
        }
        return Ok(matches);
    }

    let slice = &haystack[start..end];
    if value {
        for pos in slice.iter_ones() {
            matches.push((start + pos) as u64);
            if matches.len() >= check_at {
                py.check_signals()?;
                check_at = matches.len().saturating_add(SIGNAL_CHECK_INTERVAL);
            }
        }
    } else {
        for pos in slice.iter_zeros() {
            matches.push((start + pos) as u64);
            if matches.len() >= check_at {
                py.check_signals()?;
                check_at = matches.len().saturating_add(SIGNAL_CHECK_INTERVAL);
            }
        }
    }

    Ok(matches)
}

fn collect_find_all_positions_kmp(
    py: Python<'_>,
    haystack: &BS,
    needle: &BS,
    start: usize,
    end: usize,
    alignment_mod8: Option<usize>,
) -> PyResult<Vec<u64>> {
    if needle.len() > end - start {
        return Ok(Vec::new());
    }

    let lps = compute_lps(py, needle)?;
    let mut matches = Vec::new();
    let needle_len = needle.len();
    let mut i = start;
    let mut j = 0;
    let mut check_at = start.saturating_add(SIGNAL_CHECK_INTERVAL).min(end);

    while i < end {
        while i < check_at {
            if needle[j] == haystack[i] {
                i += 1;
                j += 1;

                if j == needle_len {
                    let match_pos = i - j;
                    if matches_alignment(match_pos, alignment_mod8) {
                        matches.push(match_pos as u64);
                    }
                    j = lps[j - 1];
                }
            } else if j != 0 {
                j = lps[j - 1];
            } else {
                i += 1;
            }
        }
        if i < end {
            py.check_signals()?;
            check_at = i.saturating_add(SIGNAL_CHECK_INTERVAL).min(end);
        }
    }

    Ok(matches)
}

#[inline]
pub(crate) fn bits_to_bytes(bits: &BS) -> Vec<u8> {
    debug_assert!(bits.len().is_multiple_of(8));
    bits.chunks_exact(8)
        .map(|chunk| chunk.load_be::<u8>())
        .collect()
}

pub(crate) fn byte_search_prep<'h, 'n>(
    haystack: &'h BS,
    needle: &'n BS,
    start: usize,
    end: usize,
    alignment_mod8: Option<usize>,
) -> Option<(Cow<'h, [u8]>, Cow<'n, [u8]>, usize)> {
    if alignment_mod8 != Some(0) || !needle.len().is_multiple_of(8) {
        return None;
    }

    let start_byte = start.div_ceil(8);
    let end_byte = end / 8;
    if start_byte > end_byte {
        return None;
    }
    let haystack_bytes = bits_to_byte_cow(&haystack[start_byte * 8..end_byte * 8]);
    let needle_bytes = bits_to_byte_cow(needle);
    Some((haystack_bytes, needle_bytes, start_byte))
}

fn bits_to_byte_cow(bits: &BS) -> Cow<'_, [u8]> {
    debug_assert!(bits.len().is_multiple_of(8));
    match bits.domain() {
        bitvec::domain::Domain::Region {
            head: None,
            body,
            tail: None,
        } => Cow::Borrowed(body),
        _ => Cow::Owned(bits_to_bytes(bits)),
    }
}

#[inline]
fn matches_alignment(match_pos: usize, alignment_mod8: Option<usize>) -> bool {
    match alignment_mod8 {
        Some(required) => (match_pos & 7) == required,
        None => true,
    }
}

fn try_find_byte_search(
    haystack: &BS,
    needle: &BS,
    start: usize,
    end: usize,
    alignment_mod8: Option<usize>,
    reverse: bool,
) -> Option<Option<usize>> {
    let (search_bytes, needle_bytes, start_byte) =
        byte_search_prep(haystack, needle, start, end, alignment_mod8)?;
    let found = if reverse {
        memmem::rfind(search_bytes.as_ref(), needle_bytes.as_ref())
    } else {
        memmem::find(search_bytes.as_ref(), needle_bytes.as_ref())
    };
    Some(found.map(|index| (start_byte + index) * 8))
}

fn rfind_bitvec_impl_with_lps_aligned(
    py: Python<'_>,
    haystack: &BS,
    needle: &BS,
    _lps: &[usize],
    start: usize,
    end: usize,
    alignment_mod8: Option<usize>,
) -> PyResult<Option<usize>> {
    if needle.is_empty() || needle.len() > end - start {
        return Ok(None);
    }

    let needle_len = needle.len();
    let reversed_needle: BV = needle.iter().by_vals().rev().collect();
    let reversed_lps = compute_lps(py, reversed_needle.as_bitslice())?;
    let search_len = end - start;
    let mut i = 0;
    let mut j = 0;
    let mut check_at = SIGNAL_CHECK_INTERVAL.min(search_len);

    while i < search_len {
        while i < check_at {
            if reversed_needle[j] == haystack[end - 1 - i] {
                i += 1;
                j += 1;

                if j == needle_len {
                    let reversed_match_pos = i - j;
                    let match_pos = end - needle_len - reversed_match_pos;
                    if matches_alignment(match_pos, alignment_mod8) {
                        return Ok(Some(match_pos));
                    }
                    j = reversed_lps[j - 1];
                }
            } else if j != 0 {
                j = reversed_lps[j - 1];
            } else {
                i += 1;
            }
        }
        if i < search_len {
            py.check_signals()?;
            check_at = i.saturating_add(SIGNAL_CHECK_INTERVAL).min(search_len);
        }
    }

    Ok(None)
}

fn find_bitvec_impl_with_lps_aligned(
    py: Python<'_>,
    haystack: &BS,
    needle: &BS,
    lps: &[usize],
    start: usize,
    end: usize,
    alignment_mod8: Option<usize>,
) -> PyResult<Option<usize>> {
    if needle.is_empty() || needle.len() > end - start {
        return Ok(None);
    }
    let needle_len = needle.len();
    let mut i = start;
    let mut j = 0;
    let mut check_at = start.saturating_add(SIGNAL_CHECK_INTERVAL).min(end);

    while i < end {
        while i < check_at {
            if needle[j] == haystack[i] {
                i += 1;
                j += 1;

                if j == needle_len {
                    let match_pos = i - j;
                    if matches_alignment(match_pos, alignment_mod8) {
                        return Ok(Some(match_pos));
                    }
                    // Continue searching for a byte-aligned match
                    j = lps[j - 1];
                }
            } else if j != 0 {
                j = lps[j - 1];
            } else {
                i += 1;
            }
        }
        if i < end {
            py.check_signals()?;
            check_at = i.saturating_add(SIGNAL_CHECK_INTERVAL).min(end);
        }
    }
    Ok(None)
}

/// Count the number of occurrences of needle in haystack.
pub(crate) fn count_bitvec(py: Python<'_>, haystack: &BS, needle: &BS) -> PyResult<usize> {
    if needle.is_empty() || needle.len() > haystack.len() {
        return Ok(0);
    }
    let lps = compute_lps(py, needle)?;
    let needle_len = needle.len();
    let mut i = 0; // The start
    let mut j = 0;
    let end = haystack.len();
    let mut count = 0;
    let mut check_at = SIGNAL_CHECK_INTERVAL.min(end);
    while i < end {
        while i < check_at {
            if needle[j] == haystack[i] {
                i += 1;
                j += 1;

                if j == needle_len {
                    count += 1;
                    // Continue searching
                    j = lps[j - 1];
                }
            } else if j != 0 {
                j = lps[j - 1];
            } else {
                i += 1;
            }
        }
        if i < end {
            py.check_signals()?;
            check_at = i.saturating_add(SIGNAL_CHECK_INTERVAL).min(end);
        }
    }
    Ok(count)
}

/// Validates the index is in range and returns an absolute bit index.
pub(crate) fn validate_index(index: isize, length: usize) -> PyResult<usize> {
    let index_p = if index < 0 {
        length as isize + index
    } else {
        index
    };
    if index_p >= length as isize || index_p < 0 {
        return Err(PyIndexError::new_err(format!(
            "Index of {index} is out of range for length of {length}"
        )));
    }
    Ok(index_p as usize)
}

pub(crate) fn validate_shift(s: &impl BitCollection, n: i64) -> PyResult<usize> {
    if s.is_empty() {
        return Err(PyValueError::new_err(
            "Cannot use a bit shift on an empty container.",
        ));
    }
    if n < 0 {
        return Err(PyValueError::new_err(
            "Cannot bit shift by a negative amount.",
        ));
    }
    Ok(n as usize)
}

#[inline]
pub(crate) fn validate_slice(
    length: usize,
    start: Option<isize>,
    end: Option<isize>,
) -> PyResult<(usize, usize)> {
    let mut start = start.unwrap_or(0);
    let mut end = end.unwrap_or(length as isize);
    if start < 0 {
        start += length as isize;
    }
    if end < 0 {
        end += length as isize;
    }

    if !(0 <= start && start <= end && end <= length as isize) {
        return Err(PyValueError::new_err(format!(
            "Invalid slice positions for length of {length}: start={start}, end={end}."
        )));
    }
    Ok((start as usize, end as usize))
}

pub(crate) fn process_seed(seed: &Option<Vec<u8>>) -> [u8; 32] {
    match seed {
        None => {
            let mut seed_arr = [0u8; 32];
            rand::rng().fill_bytes(&mut seed_arr);
            seed_arr
        }
        Some(seed_bytes) => {
            let mut hasher = Sha256::new();
            hasher.update(seed_bytes);
            let digest = hasher.finalize();
            let mut seed_arr = [0u8; 32];
            seed_arr.copy_from_slice(&digest);
            seed_arr
        }
    }
}

pub(crate) fn bv_from_random(length: i64, secure: bool, seed: &Option<Vec<u8>>) -> PyResult<BV> {
    if length < 0 {
        return Err(PyValueError::new_err(format!(
            "Negative bit length given: {}.",
            length
        )));
    }
    if secure && seed.is_some() {
        return Err(PyValueError::new_err(
            "A seed cannot be used when generating secure random data.",
        ));
    }
    let length = length as usize;
    if length == 0 {
        return Ok(BV::new());
    }
    let num_bytes = length.div_ceil(8);
    let mut data = vec![0u8; num_bytes];
    if secure {
        SysRng
            .try_fill_bytes(&mut data)
            .map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
    } else {
        let seed_arr = process_seed(seed);
        let mut rng = StdRng::from_seed(seed_arr);
        rng.fill_bytes(&mut data);
    }
    let mut bv = BV::from_vec(data);
    if bv.len() > length {
        bv.truncate(length);
    }
    Ok(bv)
}

pub(crate) fn bv_from_zeros(length: usize) -> BV {
    BV::repeat(false, length)
}

pub(crate) fn bv_from_ones(length: usize) -> BV {
    BV::repeat(true, length)
}

#[inline]
pub(crate) fn bv_from_bin(binary_string: &str) -> PyResult<BV> {
    // Ignore any leading '0b' or '0B'
    let s = binary_string
        .strip_prefix("0b")
        .or_else(|| binary_string.strip_prefix("0B"))
        .unwrap_or(binary_string);
    let mut bv: BV = BV::with_capacity(s.len());
    for c in s.chars() {
        match c {
            '0' => bv.push(false),
            '1' => bv.push(true),
            '_' => continue,
            c if c.is_whitespace() => continue,
            _ => {
                return Err(PyValueError::new_err(format!(
                    "Cannot convert from bin '{binary_string}: Invalid character '{c}'."
                )));
            }
        }
    }
    bv.set_uninitialized(false);
    Ok(bv)
}

#[inline]
pub(crate) fn bv_from_oct(octal_string: &str) -> PyResult<BV> {
    // Ignore any leading '0o' or '0O'
    let s = octal_string
        .strip_prefix("0o")
        .or_else(|| octal_string.strip_prefix("0O"))
        .unwrap_or(octal_string);
    let mut bv: BV = BV::with_capacity(s.len() * 3);
    for c in s.chars() {
        match c {
            '0' => bv.extend_from_bitslice(bits![0, 0, 0]),
            '1' => bv.extend_from_bitslice(bits![0, 0, 1]),
            '2' => bv.extend_from_bitslice(bits![0, 1, 0]),
            '3' => bv.extend_from_bitslice(bits![0, 1, 1]),
            '4' => bv.extend_from_bitslice(bits![1, 0, 0]),
            '5' => bv.extend_from_bitslice(bits![1, 0, 1]),
            '6' => bv.extend_from_bitslice(bits![1, 1, 0]),
            '7' => bv.extend_from_bitslice(bits![1, 1, 1]),
            '_' => continue,
            c if c.is_whitespace() => continue,
            _ => {
                return Err(PyValueError::new_err(format!(
                    "Cannot convert from oct '{octal_string}': Invalid character '{c}'."
                )));
            }
        }
    }
    bv.set_uninitialized(false);
    Ok(bv)
}

#[inline]
pub(crate) fn bv_from_hex(hex: &str) -> PyResult<BV> {
    // Ignore any leading '0x' or '0X'
    let mut new_hex = hex
        .strip_prefix("0x")
        .or_else(|| hex.strip_prefix("0X"))
        .unwrap_or(hex)
        .to_string();
    // Remove any underscores or whitespace characters
    new_hex.retain(|c| c != '_' && !c.is_whitespace());
    let new_hex_length = new_hex.len();
    if new_hex_length % 2 != 0 {
        new_hex.push('0');
    }
    let data = match hex::decode(&new_hex) {
        Ok(d) => d,
        Err(e) => {
            return Err(PyValueError::new_err(format!(
                "Cannot convert from hex '{hex}': {}",
                e
            )));
        }
    };
    let bv = bv_from_bytes_slice(data, None, Some(new_hex_length * 4))?;
    Ok(bv)
}

pub(crate) fn bv_from_bytes_slice(
    data: Vec<u8>,
    offset: Option<usize>,
    length: Option<usize>,
) -> PyResult<BV> {
    if length.is_none() && offset.is_none() {
        return Ok(BV::from_vec(data));
    }
    let offset = offset.unwrap_or(0);
    let data_length = data.len() * 8;
    if offset > data_length {
        return Err(PyValueError::new_err(format!(
            "Offset of {offset} is greater than the data length ({data_length} bits)."
        )));
    }
    let length = length.unwrap_or(data_length - offset);
    if offset + length > data_length {
        return Err(PyValueError::new_err(format!(
            "Length of {length} with offset of {offset} is greater than the data length ({data_length} bits)."
        )));
    }
    let bs = BS::from_slice(&data);
    Ok(bs[offset..offset + length].to_bitvec())
}

#[inline]
pub(crate) fn bv_from_u128(value: u128, length: usize, is_little_endian: bool) -> PyResult<BV> {
    if length == 0 || length > 128 {
        return Err(PyValueError::new_err(format!(
            "Bit length for unsigned int must be between 1 and 128. Received {length}."
        )));
    }
    // Special case for 128 to avoid overflow in more general case
    if length == 128 {
        let mut bv = BV::repeat(false, 128);
        if is_little_endian {
            bv.store_le(value);
        } else {
            bv.store_be(value);
        }
        return Ok(bv);
    }
    if value >= (1u128 << length) {
        return Err(PyOverflowError::new_err(format!(
            "Value {value} does not fit in {length} bits."
        )));
    }
    let mut bv = BV::repeat(false, length as usize);
    if is_little_endian {
        bv.store_le(value);
    } else {
        bv.store_be(value);
    }
    Ok(bv)
}

#[inline]
pub(crate) fn bv_from_i128(value: i128, length: usize, is_little_endian: bool) -> PyResult<BV> {
    if length == 0 || length > 128 {
        return Err(PyValueError::new_err(format!(
            "Bit length for signed int must be between 1 and 128. Received {length}."
        )));
    }
    // Special case for 128 to avoid overflow in more general case
    if length == 128 {
        let mut bv = BV::repeat(value < 0, 128);
        if is_little_endian {
            bv.store_le(value);
        } else {
            bv.store_be(value);
        }
        return Ok(bv);
    }
    let min_val = -(1i128 << (length - 1));
    let max_val = (1i128 << (length - 1)) - 1;
    if value < min_val || value > max_val {
        return Err(PyOverflowError::new_err(format!(
            "Value {value} does not fit in {length} signed bits."
        )));
    }
    let repeat_bit = value < 0;
    let mut bv = BV::repeat(repeat_bit, length as usize);
    if is_little_endian {
        bv.store_le(value);
    } else {
        bv.store_be(value);
    }
    Ok(bv)
}

pub(crate) fn bv_from_f64(value: f64, length: usize, is_little_endian: bool) -> PyResult<BV> {
    let bv = match length {
        64 => {
            let mut bv = BV::repeat(false, 64);
            if is_little_endian {
                bv.store_le(value.to_bits());
            } else {
                bv.store_be(value.to_bits());
            }
            bv
        }
        32 => {
            let value_f32 = value as f32;
            let mut bv = BV::repeat(false, 32);
            if is_little_endian {
                bv.store_le(value_f32.to_bits());
            } else {
                bv.store_be(value_f32.to_bits());
            }
            bv
        }
        16 => {
            let value_f16 = f16::from_f64(value);
            let mut bv = BV::repeat(false, 16);
            if is_little_endian {
                bv.store_le(value_f16.to_bits());
            } else {
                bv.store_be(value_f16.to_bits());
            }
            bv
        }
        _ => {
            return Err(PyValueError::new_err(format!(
                "Unsupported float bit length '{length}'. Only 16, 32 and 64 are supported."
            )));
        }
    };
    Ok(bv)
}

pub(crate) fn bv_from_bools(iterable: &Bound<'_, PyAny>) -> PyResult<BV> {
    // Lists and tuples are the common bulk path. Reading their items through
    // the C API avoids creating a Bound<PyAny> wrapper for every bit.
    if let Ok(list) = iterable.cast::<PyList>() {
        return unsafe {
            bv_from_py_sequence_items(iterable.py(), list.len(), |index| {
                ffi::PyList_GetItem(list.as_ptr(), index as ffi::Py_ssize_t)
            })
        };
    }
    if let Ok(tuple) = iterable.cast::<PyTuple>() {
        return unsafe {
            bv_from_py_sequence_items(iterable.py(), tuple.len(), |index| {
                ffi::PyTuple_GetItem(tuple.as_ptr(), index as ffi::Py_ssize_t)
            })
        };
    }

    // For sequences, we can pre-allocate the capacity.
    let capacity = iterable.len().ok().unwrap_or(64);
    let mut bv = BV::with_capacity(capacity);

    for value in iterable.try_iter()? {
        bv.push(value?.is_truthy()?);
    }
    Ok(bv)
}

unsafe fn bv_from_py_sequence_items(
    py: Python<'_>,
    len: usize,
    mut get_item: impl FnMut(usize) -> *mut ffi::PyObject,
) -> PyResult<BV> {
    // The callers pass list/tuple indices in bounds, so borrowed item pointers
    // are valid while the GIL is held. Pack directly into Msb0 bytes to avoid
    // BitVec::push overhead for large Python bool sequences.
    let mut bytes = vec![0u8; len.div_ceil(8)];
    let py_true = unsafe { ffi::Py_True() };
    let py_false = unsafe { ffi::Py_False() };

    for index in 0..len {
        let item = get_item(index);
        // Python bools are singletons, so pointer comparison handles the
        // benchmark path cheaply. Other objects keep normal truthiness.
        let bit = if item == py_true {
            true
        } else if item == py_false {
            false
        } else {
            match unsafe { ffi::PyObject_IsTrue(item) } {
                0 => false,
                1 => true,
                -1 => return Err(PyErr::fetch(py)),
                _ => unreachable!("PyObject_IsTrue only returns -1, 0, or 1"),
            }
        };
        if bit {
            bytes[index / 8] |= 0x80 >> (index & 7);
        }
    }
    let mut bv = BV::from_vec(bytes);
    bv.truncate(len);
    Ok(bv)
}

fn string_literal_to_bv(s: &str) -> PyResult<BV> {
    match s.get(0..2).map(|p| p.to_ascii_lowercase()).as_deref() {
        Some("0b") => {
            let bv = bv_from_bin(s)?;
            Ok(bv)
        }
        Some("0x") => {
            let bv = bv_from_hex(s)?;
            Ok(bv)
        }
        Some("0o") => {
            let bv = bv_from_oct(s)?;
            Ok(bv)
        }
        _ => Err(PyValueError::new_err(format!(
            "Can't parse token '{s}'. Did you mean to prefix with '0x', '0b' or '0o'?"
        ))),
    }
}

pub(crate) fn str_to_bv(s: String) -> PyResult<BV> {
    // First remove whitespace
    let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();
    // Check if it's already in the cache
    {
        let mut cache = BITS_CACHE
            .lock()
            .map_err(|_| PyRuntimeError::new_err("Internal bits cache mutex poisoned?"))?;
        if let Some(cached_data) = cache.get(&s) {
            return Ok(cached_data.clone());
        }
    }
    let tokens = s.split(',');
    let mut bv_array = Vec::<BV>::new();
    let mut total_bit_length = 0;
    for token in tokens {
        if token.is_empty() {
            continue;
        }
        let x = string_literal_to_bv(token)?;
        total_bit_length += x.len();
        bv_array.push(x);
    }
    if bv_array.is_empty() {
        return Ok(BV::new());
    }
    // Combine all bits
    let result = if bv_array.len() == 1 {
        bv_array.pop().unwrap()
    } else {
        let mut result = BV::with_capacity(total_bit_length);
        for bv in bv_array {
            result.extend_from_bitslice(&bv);
        }
        result
    };
    // Update cache with new result
    {
        let mut cache = BITS_CACHE
            .lock()
            .map_err(|_| PyRuntimeError::new_err("Internal bits cache mutex poisoned?"))?;
        cache.put(s, result.clone());
    }
    Ok(result)
}

pub(crate) fn promote_to_bv(any: &Bound<'_, PyAny>) -> PyResult<BV> {
    // Is it a string?
    if let Ok(any_string) = any.extract::<String>() {
        let bv = str_to_bv(any_string)?;
        return Ok(bv);
    }

    // Is it a bytes, bytearray or memoryview?
    if (any.is_instance_of::<PyBytes>()
        || any.is_instance_of::<PyByteArray>()
        || any.is_instance_of::<PyMemoryView>())
        && let Ok(any_bytes) = any.extract::<Vec<u8>>()
    {
        return Ok(BV::from_vec(any_bytes));
    }

    // Is it an iterable that we can convert each element to a bool?
    if any.try_iter().is_ok() {
        return bv_from_bools(any);
    }
    let type_name = match any.get_type().name() {
        Ok(name) => name.to_string(),
        Err(_) => "<unknown>".to_string(),
    };
    let mut err = format!("Cannot promote object of type <{type_name}> to a Tibs/Mutibs object. ");
    if any.is_instance_of::<PyInt>() {
        err.push_str("Perhaps you want to use the class methods 'from_zeros()', 'from_ones()' or 'from_random()'?");
    };
    Err(PyTypeError::new_err(err))
}
