use std::collections::BTreeMap;
use std::ops::ControlFlow;

use crate::error::{Error, Result};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct H5Map {
    key_type: String,
    value_type: String,
    create_plist: BTreeMap<String, Vec<u8>>,
    access_plist: BTreeMap<String, Vec<u8>>,
    entries: BTreeMap<Vec<u8>, Vec<u8>>,
    open: bool,
}

impl H5Map {
    pub fn new(key_type: impl Into<String>, value_type: impl Into<String>) -> Self {
        Self {
            key_type: key_type.into(),
            value_type: value_type.into(),
            create_plist: BTreeMap::new(),
            access_plist: BTreeMap::new(),
            entries: BTreeMap::new(),
            open: true,
        }
    }

    fn ensure_open(&self) -> Result<()> {
        if self.open {
            Ok(())
        } else {
            Err(Error::InvalidFormat("map handle is closed".into()))
        }
    }
}

fn unsupported_map(name: &str) -> Error {
    Error::Unsupported(format!(
        "{name} requires libhdf5 map behavior not implemented in pure-Rust mode"
    ))
}

pub trait H5MIterCallbackResult {
    fn should_stop(self) -> bool;
}

impl H5MIterCallbackResult for () {
    fn should_stop(self) -> bool {
        false
    }
}

impl H5MIterCallbackResult for ControlFlow<()> {
    fn should_stop(self) -> bool {
        matches!(self, ControlFlow::Break(()))
    }
}

#[allow(non_snake_case)]
pub fn H5M_init() -> bool {
    true
}

#[allow(non_snake_case)]
pub fn H5M__init_package() -> bool {
    H5M_init()
}

#[allow(non_snake_case)]
pub fn H5M_top_term_package() {}

#[allow(non_snake_case)]
pub fn H5M_term_package() {}

#[allow(non_snake_case)]
pub fn H5M__close_cb(map: &mut H5Map) {
    map.open = false;
}

#[allow(non_snake_case)]
pub fn H5M__create_api_common(key_type: impl Into<String>, value_type: impl Into<String>) -> H5Map {
    H5Map::new(key_type, value_type)
}

#[allow(non_snake_case)]
pub fn H5Mcreate(
    _loc: impl Into<String>,
    _name: impl Into<String>,
    key_type: impl Into<String>,
    value_type: impl Into<String>,
) -> H5Map {
    H5M__create_api_common(key_type, value_type)
}

#[allow(non_snake_case)]
pub fn H5Mcreate_async(
    loc: impl Into<String>,
    name: impl Into<String>,
    key_type: impl Into<String>,
    value_type: impl Into<String>,
) -> H5Map {
    H5Mcreate(loc, name, key_type, value_type)
}

#[allow(non_snake_case)]
pub fn H5Mcreate_anon(key_type: impl Into<String>, value_type: impl Into<String>) -> H5Map {
    H5M__create_api_common(key_type, value_type)
}

#[allow(non_snake_case)]
pub fn H5Mcreate_anon_async(key_type: impl Into<String>, value_type: impl Into<String>) -> H5Map {
    H5Mcreate_anon(key_type, value_type)
}

#[allow(non_snake_case)]
pub fn H5M__open_api_common_ref(map: &H5Map) -> Result<&H5Map> {
    map.ensure_open()?;
    Ok(map)
}

#[allow(non_snake_case)]
pub fn H5Mopen_ref(
    map: &H5Map,
    _loc: impl Into<String>,
    _name: impl Into<String>,
) -> Result<&H5Map> {
    H5M__open_api_common_ref(map)
}

#[allow(non_snake_case)]
pub fn H5Mopen_async_ref(
    map: &H5Map,
    loc: impl Into<String>,
    name: impl Into<String>,
) -> Result<&H5Map> {
    H5Mopen_ref(map, loc, name)
}

#[deprecated(note = "use H5M__open_api_common_ref to borrow the map without cloning")]
#[allow(non_snake_case)]
pub fn H5M__open_api_common(map: &H5Map) -> Result<H5Map> {
    Ok(H5M__open_api_common_ref(map)?.clone())
}

#[deprecated(note = "use H5Mopen_ref to borrow the map without cloning")]
#[allow(non_snake_case)]
#[allow(deprecated)]
pub fn H5Mopen(map: &H5Map, _loc: impl Into<String>, _name: impl Into<String>) -> Result<H5Map> {
    H5M__open_api_common(map)
}

#[deprecated(note = "use H5Mopen_async_ref to borrow the map without cloning")]
#[allow(non_snake_case)]
#[allow(deprecated)]
pub fn H5Mopen_async(
    map: &H5Map,
    loc: impl Into<String>,
    name: impl Into<String>,
) -> Result<H5Map> {
    H5Mopen(map, loc, name)
}

#[allow(non_snake_case)]
pub fn H5Mclose(map: &mut H5Map) {
    H5M__close_cb(map);
}

#[allow(non_snake_case)]
pub fn H5Mclose_async(map: &mut H5Map) {
    H5Mclose(map);
}

#[allow(non_snake_case)]
pub fn H5Mget_key_type(map: &H5Map) -> Result<&str> {
    map.ensure_open()?;
    Ok(&map.key_type)
}

#[allow(non_snake_case)]
pub fn H5Mget_key_type_into(map: &H5Map, out: &mut String) -> Result<()> {
    let key_type = H5Mget_key_type(map)?;
    out.try_reserve(key_type.len()).map_err(|err| {
        Error::InvalidFormat(format!("map key type output allocation failed: {err}"))
    })?;
    out.clear();
    out.push_str(key_type);
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5Mget_val_type(map: &H5Map) -> Result<&str> {
    map.ensure_open()?;
    Ok(&map.value_type)
}

#[allow(non_snake_case)]
pub fn H5Mget_val_type_into(map: &H5Map, out: &mut String) -> Result<()> {
    let value_type = H5Mget_val_type(map)?;
    out.try_reserve(value_type.len()).map_err(|err| {
        Error::InvalidFormat(format!("map value type output allocation failed: {err}"))
    })?;
    out.clear();
    out.push_str(value_type);
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5Mget_create_plist_ref(map: &H5Map) -> Result<&BTreeMap<String, Vec<u8>>> {
    map.ensure_open()?;
    Ok(&map.create_plist)
}

#[allow(non_snake_case)]
pub fn H5Mget_access_plist_ref(map: &H5Map) -> Result<&BTreeMap<String, Vec<u8>>> {
    map.ensure_open()?;
    Ok(&map.access_plist)
}

#[deprecated(note = "use H5Mget_create_plist_ref to borrow the property list without cloning")]
#[allow(non_snake_case)]
pub fn H5Mget_create_plist(map: &H5Map) -> Result<BTreeMap<String, Vec<u8>>> {
    Ok(H5Mget_create_plist_ref(map)?.clone())
}

#[deprecated(note = "use H5Mget_access_plist_ref to borrow the property list without cloning")]
#[allow(non_snake_case)]
pub fn H5Mget_access_plist(map: &H5Map) -> Result<BTreeMap<String, Vec<u8>>> {
    Ok(H5Mget_access_plist_ref(map)?.clone())
}

#[allow(non_snake_case)]
pub fn H5Mget_count(map: &H5Map) -> Result<usize> {
    let mut count = 0;
    H5Mget_count_into(map, &mut count)?;
    Ok(count)
}

#[allow(non_snake_case)]
pub fn H5Mget_count_into(map: &H5Map, count: &mut usize) -> Result<()> {
    map.ensure_open()?;
    *count = map.entries.len();
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5Mget_count_async(map: &H5Map) -> Result<usize> {
    H5Mget_count(map)
}

#[allow(non_snake_case)]
pub fn H5M__put_api_common(map: &mut H5Map, key: Vec<u8>, value: Vec<u8>) -> Result<()> {
    map.ensure_open()?;
    map.entries.insert(key, value);
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5Mput(map: &mut H5Map, key: Vec<u8>, value: Vec<u8>) -> Result<()> {
    H5M__put_api_common(map, key, value)
}

#[allow(non_snake_case)]
pub fn H5Mput_async(map: &mut H5Map, key: Vec<u8>, value: Vec<u8>) -> Result<()> {
    H5Mput(map, key, value)
}

#[allow(non_snake_case)]
pub fn H5M__get_api_common_ref<'a>(map: &'a H5Map, key: &[u8]) -> Result<Option<&'a [u8]>> {
    map.ensure_open()?;
    Ok(map.entries.get(key).map(Vec::as_slice))
}

#[allow(non_snake_case)]
pub fn H5Mget_ref<'a>(map: &'a H5Map, key: &[u8]) -> Result<Option<&'a [u8]>> {
    H5M__get_api_common_ref(map, key)
}

#[allow(non_snake_case)]
pub fn H5Mget_async_ref<'a>(map: &'a H5Map, key: &[u8]) -> Result<Option<&'a [u8]>> {
    H5Mget_ref(map, key)
}

#[allow(non_snake_case)]
pub fn H5Mget_into(map: &H5Map, key: &[u8], value: &mut Vec<u8>) -> Result<bool> {
    map.ensure_open()?;
    if let Some(stored_value) = map.entries.get(key) {
        value.clear();
        value.extend_from_slice(stored_value);
        Ok(true)
    } else {
        Ok(false)
    }
}

#[allow(non_snake_case)]
pub fn H5Mget_async_into(map: &H5Map, key: &[u8], value: &mut Vec<u8>) -> Result<bool> {
    H5Mget_into(map, key, value)
}

#[deprecated(note = "use H5M__get_api_common_ref to borrow the value without cloning")]
#[allow(non_snake_case)]
pub fn H5M__get_api_common(map: &H5Map, key: &[u8]) -> Result<Option<Vec<u8>>> {
    Ok(H5M__get_api_common_ref(map, key)?.map(<[u8]>::to_vec))
}

#[deprecated(note = "use H5Mget_ref to borrow the value without cloning")]
#[allow(non_snake_case)]
pub fn H5Mget(map: &H5Map, key: &[u8]) -> Result<Option<Vec<u8>>> {
    Ok(H5Mget_ref(map, key)?.map(<[u8]>::to_vec))
}

#[deprecated(note = "use H5Mget_async_ref to borrow the value without cloning")]
#[allow(non_snake_case)]
pub fn H5Mget_async(map: &H5Map, key: &[u8]) -> Result<Option<Vec<u8>>> {
    Ok(H5Mget_async_ref(map, key)?.map(<[u8]>::to_vec))
}

#[allow(non_snake_case)]
pub fn H5Mexists(map: &H5Map, key: &[u8]) -> Result<bool> {
    let mut exists = false;
    H5Mexists_into(map, key, &mut exists)?;
    Ok(exists)
}

#[allow(non_snake_case)]
pub fn H5Mexists_into(map: &H5Map, key: &[u8], exists: &mut bool) -> Result<()> {
    map.ensure_open()?;
    *exists = map.entries.contains_key(key);
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5Mexists_async(map: &H5Map, key: &[u8]) -> Result<bool> {
    H5Mexists(map, key)
}

#[allow(non_snake_case)]
pub fn H5M_iter_entries(map: &H5Map) -> Result<impl Iterator<Item = (&[u8], &[u8])> + '_> {
    map.ensure_open()?;
    Ok(map
        .entries
        .iter()
        .map(|(key, value)| (key.as_slice(), value.as_slice())))
}

#[allow(non_snake_case)]
pub fn H5M_iterate_with<F, R>(map: &H5Map, mut callback: F) -> Result<()>
where
    F: FnMut(&[u8], &[u8]) -> Result<R>,
    R: H5MIterCallbackResult,
{
    map.ensure_open()?;
    for (key, value) in &map.entries {
        if callback(key, value)?.should_stop() {
            break;
        }
    }
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5Miterate_with<F, R>(map: &H5Map, callback: F) -> Result<()>
where
    F: FnMut(&[u8], &[u8]) -> Result<R>,
    R: H5MIterCallbackResult,
{
    H5M_iterate_with(map, callback)
}

#[allow(non_snake_case)]
pub fn H5Miterate_from_with<F, R>(map: &H5Map, idx: &mut usize, mut callback: F) -> Result<()>
where
    F: FnMut(&[u8], &[u8]) -> Result<R>,
    R: H5MIterCallbackResult,
{
    map.ensure_open()?;
    if *idx > map.entries.len() {
        return Err(Error::InvalidFormat(
            "map iteration index is past the entry count".into(),
        ));
    }
    for (offset, (key, value)) in map.entries.iter().enumerate().skip(*idx) {
        let next_idx = offset + 1;
        if callback(key, value)?.should_stop() {
            *idx = next_idx;
            break;
        }
        *idx = next_idx;
    }
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5M_iterate_into(map: &H5Map, entries: &mut Vec<(Vec<u8>, Vec<u8>)>) -> Result<()> {
    map.ensure_open()?;
    let mut next = Vec::new();
    next.try_reserve_exact(map.entries.len()).map_err(|err| {
        Error::InvalidFormat(format!("map iteration output allocation failed: {err}"))
    })?;
    H5M_iterate_with(map, |key, value| {
        let mut owned_key = Vec::new();
        owned_key.try_reserve_exact(key.len()).map_err(|err| {
            Error::InvalidFormat(format!("map iteration key allocation failed: {err}"))
        })?;
        owned_key.extend_from_slice(key);

        let mut owned_value = Vec::new();
        owned_value.try_reserve_exact(value.len()).map_err(|err| {
            Error::InvalidFormat(format!("map iteration value allocation failed: {err}"))
        })?;
        owned_value.extend_from_slice(value);

        next.push((owned_key, owned_value));
        Ok(())
    })?;
    entries.try_reserve_exact(next.len()).map_err(|err| {
        Error::InvalidFormat(format!("map iteration output allocation failed: {err}"))
    })?;
    entries.clear();
    entries.extend(next);
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5Miterate_into(map: &H5Map, entries: &mut Vec<(Vec<u8>, Vec<u8>)>) -> Result<()> {
    H5M_iterate_into(map, entries)
}

#[deprecated(
    note = "use H5M_iter_entries, H5M_iterate_with, or H5M_iterate_into to avoid returning an allocated Vec"
)]
#[allow(non_snake_case)]
pub fn H5M_iterate(map: &H5Map) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
    let mut entries = Vec::new();
    H5M_iterate_into(map, &mut entries)?;
    Ok(entries)
}

#[allow(non_snake_case)]
pub fn H5Miterate<F, R>(map: &H5Map, idx: &mut usize, mut callback: F) -> Result<()>
where
    F: FnMut(&[u8]) -> Result<R>,
    R: H5MIterCallbackResult,
{
    map.ensure_open()?;
    if *idx > map.entries.len() {
        return Err(Error::InvalidFormat(
            "map iteration index is past the entry count".into(),
        ));
    }
    for (offset, (key, _value)) in map.entries.iter().enumerate().skip(*idx) {
        let next_idx = offset + 1;
        if callback(key)?.should_stop() {
            *idx = next_idx;
            break;
        }
        *idx = next_idx;
    }
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5Miterate_by_name(
    _loc: impl Into<String>,
    _map_name: impl Into<String>,
    _idx: &mut usize,
) -> Result<()> {
    Err(unsupported_map("H5Miterate_by_name"))
}

#[allow(non_snake_case)]
pub fn H5Mdelete(map: &mut H5Map, key: &[u8]) -> Result<Option<Vec<u8>>> {
    map.ensure_open()?;
    Ok(map.entries.remove(key))
}

#[allow(non_snake_case)]
pub fn H5Mdelete_into(map: &mut H5Map, key: &[u8], value: &mut Vec<u8>) -> Result<bool> {
    map.ensure_open()?;
    if let Some(removed) = map.entries.remove(key) {
        value.clear();
        value.extend_from_slice(&removed);
        Ok(true)
    } else {
        Ok(false)
    }
}

#[allow(non_snake_case)]
pub fn H5Mdelete_async(map: &mut H5Map, key: &[u8]) -> Result<Option<Vec<u8>>> {
    H5Mdelete(map, key)
}

#[allow(non_snake_case)]
pub fn H5Mdelete_async_into(map: &mut H5Map, key: &[u8], value: &mut Vec<u8>) -> Result<bool> {
    H5Mdelete_into(map, key, value)
}

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

    #[test]
    fn map_api_put_get_iterate_delete() {
        let mut map = H5Mcreate("file", "map", "u8", "bytes");
        assert_eq!(H5Mget_key_type(&map).unwrap(), "u8");
        assert_eq!(H5Mget_val_type(&map).unwrap(), "bytes");
        H5Mput(&mut map, b"k".to_vec(), b"v".to_vec()).unwrap();
        assert_eq!(H5Mget_count(&map).unwrap(), 1);
        assert!(H5Mexists(&map, b"k").unwrap());
        assert!(std::ptr::eq(H5M__open_api_common_ref(&map).unwrap(), &map));
        assert!(std::ptr::eq(
            H5Mopen_ref(&map, "file", "map").unwrap(),
            &map
        ));
        assert!(std::ptr::eq(
            H5Mopen_async_ref(&map, "file", "map").unwrap(),
            &map
        ));
        assert_eq!(H5Mget_ref(&map, b"k").unwrap(), Some(b"v".as_slice()));
        let entries: Vec<_> = H5M_iter_entries(&map).unwrap().collect();
        assert_eq!(entries, vec![(b"k".as_slice(), b"v".as_slice())]);

        let mut visited = Vec::new();
        H5Miterate_with(&map, |key, value| {
            visited.push((key.to_vec(), value.to_vec()));
            Ok(())
        })
        .unwrap();
        assert_eq!(visited, vec![(b"k".to_vec(), b"v".to_vec())]);

        let mut copied = Vec::new();
        H5M_iterate_into(&map, &mut copied).unwrap();
        assert_eq!(copied, vec![(b"k".to_vec(), b"v".to_vec())]);

        let mut idx = 0;
        let mut keys = Vec::new();
        H5Miterate(&map, &mut idx, |key| {
            keys.push(key.to_vec());
            Ok(())
        })
        .unwrap();
        assert_eq!(keys, vec![b"k".to_vec()]);
        assert_eq!(idx, 1);

        H5Mput(&mut map, b"k2".to_vec(), b"v2".to_vec()).unwrap();
        let mut stopped = Vec::new();
        H5Miterate_with(&map, |key, value| {
            stopped.push((key.to_vec(), value.to_vec()));
            Ok(ControlFlow::Break(()))
        })
        .unwrap();
        assert_eq!(stopped, vec![(b"k".to_vec(), b"v".to_vec())]);
        assert_eq!(H5Mdelete(&mut map, b"k2").unwrap(), Some(b"v2".to_vec()));

        H5Mput(&mut map, b"a".to_vec(), b"1".to_vec()).unwrap();
        H5Mput(&mut map, b"b".to_vec(), b"2".to_vec()).unwrap();
        let mut idx = 1;
        let mut resumed = Vec::new();
        H5Miterate_from_with(&map, &mut idx, |key, value| {
            resumed.push((key.to_vec(), value.to_vec()));
            Ok(ControlFlow::Break(()))
        })
        .unwrap();
        assert_eq!(resumed, vec![(b"b".to_vec(), b"2".to_vec())]);
        assert_eq!(idx, 2);

        H5Miterate_from_with(&map, &mut idx, |key, value| {
            resumed.push((key.to_vec(), value.to_vec()));
            Ok(())
        })
        .unwrap();
        assert_eq!(
            resumed,
            vec![
                (b"b".to_vec(), b"2".to_vec()),
                (b"k".to_vec(), b"v".to_vec())
            ]
        );
        assert_eq!(idx, 3);
        idx = 4;
        assert!(H5Miterate_from_with(&map, &mut idx, |_key, _value| Ok(())).is_err());

        idx = 1;
        let mut resumed_keys = Vec::new();
        H5Miterate(&map, &mut idx, |key| {
            resumed_keys.push(key.to_vec());
            Ok(ControlFlow::Break(()))
        })
        .unwrap();
        assert_eq!(resumed_keys, vec![b"b".to_vec()]);
        assert_eq!(idx, 2);
        idx = 4;
        assert!(H5Miterate(&map, &mut idx, |_key| Ok(())).is_err());

        assert_eq!(H5Mdelete(&mut map, b"a").unwrap(), Some(b"1".to_vec()));
        assert_eq!(H5Mdelete(&mut map, b"b").unwrap(), Some(b"2".to_vec()));
        assert_eq!(H5Mdelete(&mut map, b"k").unwrap(), Some(b"v".to_vec()));
        H5Mput_async(&mut map, b"k2".to_vec(), b"v2".to_vec()).unwrap();
        assert_eq!(
            H5Mdelete_async(&mut map, b"k2").unwrap(),
            Some(b"v2".to_vec())
        );
        H5Mclose_async(&mut map);
        assert!(H5Mget_count(&map).is_err());

        let anon = H5Mcreate_anon_async("u8", "bytes");
        assert_eq!(H5Mget_count(&anon).unwrap(), 0);
    }

    #[test]
    fn map_api_out_parameter_queries_match_existing_state() {
        let mut map = H5Mcreate("file", "map", "u8", "bytes");
        H5Mput(&mut map, b"a".to_vec(), b"1".to_vec()).unwrap();
        H5Mput(&mut map, b"b".to_vec(), b"2".to_vec()).unwrap();

        assert_eq!(H5Mget_count_async(&map).unwrap(), 2);
        assert!(H5Mexists_async(&map, b"a").unwrap());
        assert!(!H5Mexists_async(&map, b"missing").unwrap());

        let mut count = usize::MAX;
        H5Mget_count_into(&map, &mut count).unwrap();
        assert_eq!(count, 2);

        let mut exists = false;
        H5Mexists_into(&map, b"a", &mut exists).unwrap();
        assert!(exists);

        H5Mexists_into(&map, b"missing", &mut exists).unwrap();
        assert!(!exists);

        let mut key_type = String::from("stale");
        H5Mget_key_type_into(&map, &mut key_type).unwrap();
        assert_eq!(key_type, "u8");

        let mut value_type = String::from("stale");
        H5Mget_val_type_into(&map, &mut value_type).unwrap();
        assert_eq!(value_type, "bytes");
    }

    #[test]
    fn map_api_get_into_copies_existing_value_to_output_buffer() {
        let mut map = H5Mcreate("file", "map", "u8", "bytes");
        H5Mput(&mut map, b"a".to_vec(), b"1".to_vec()).unwrap();
        H5Mput(&mut map, b"b".to_vec(), b"22".to_vec()).unwrap();

        let mut value = b"stale".to_vec();
        assert!(H5Mget_into(&map, b"a", &mut value).unwrap());
        assert_eq!(value, b"1");

        assert!(H5Mget_async_into(&map, b"b", &mut value).unwrap());
        assert_eq!(value, b"22");

        assert!(!H5Mget_into(&map, b"missing", &mut value).unwrap());
        assert_eq!(value, b"22");
    }

    #[test]
    fn map_api_delete_into_reuses_caller_buffer() {
        let mut map = H5Mcreate("file", "map", "u8", "bytes");
        H5Mput(&mut map, b"a".to_vec(), b"1".to_vec()).unwrap();
        H5Mput(&mut map, b"b".to_vec(), b"22".to_vec()).unwrap();

        let mut value = b"stale".to_vec();
        assert!(H5Mdelete_into(&mut map, b"a", &mut value).unwrap());
        assert_eq!(value, b"1");
        assert!(!H5Mexists(&map, b"a").unwrap());

        assert!(H5Mdelete_async_into(&mut map, b"b", &mut value).unwrap());
        assert_eq!(value, b"22");

        assert!(!H5Mdelete_into(&mut map, b"missing", &mut value).unwrap());
        assert_eq!(value, b"22");
    }

    #[test]
    fn map_api_iterate_into_replaces_stale_output() {
        let mut map = H5Mcreate("file", "map", "u8", "bytes");
        H5Mput(&mut map, b"a".to_vec(), b"1".to_vec()).unwrap();
        H5Mput(&mut map, b"b".to_vec(), b"2".to_vec()).unwrap();

        let mut entries = vec![(b"stale".to_vec(), b"value".to_vec())];
        H5M_iterate_into(&map, &mut entries).unwrap();
        assert_eq!(
            entries,
            vec![
                (b"a".to_vec(), b"1".to_vec()),
                (b"b".to_vec(), b"2".to_vec())
            ]
        );

        H5Mdelete(&mut map, b"a").unwrap();
        H5Mdelete(&mut map, b"b").unwrap();
        H5Miterate_into(&map, &mut entries).unwrap();
        assert!(entries.is_empty());
    }

    #[test]
    fn map_api_out_parameter_queries_require_open_map() {
        let mut map = H5Mcreate("file", "map", "u8", "bytes");
        H5Mput(&mut map, b"k".to_vec(), b"v".to_vec()).unwrap();
        H5Mclose(&mut map);

        let mut count = usize::MAX;
        assert!(H5Mget_count_into(&map, &mut count).is_err());
        assert_eq!(count, usize::MAX);

        let mut key_type = String::from("stale-key");
        assert!(H5Mget_key_type_into(&map, &mut key_type).is_err());
        assert_eq!(key_type, "stale-key");

        let mut value_type = String::from("stale-value");
        assert!(H5Mget_val_type_into(&map, &mut value_type).is_err());
        assert_eq!(value_type, "stale-value");

        let mut exists = true;
        assert!(H5Mexists_into(&map, b"k", &mut exists).is_err());
        assert!(exists);
        assert!(H5Mget_count_async(&map).is_err());
        assert!(H5Mexists_async(&map, b"k").is_err());

        let mut value = b"stale".to_vec();
        assert!(H5Mget_into(&map, b"k", &mut value).is_err());
        assert_eq!(value, b"stale");
        assert!(H5Mdelete_into(&mut map, b"k", &mut value).is_err());
        assert_eq!(value, b"stale");
    }

    #[test]
    fn named_map_iteration_is_explicit_unsupported_boundary() {
        let mut idx = 7;
        let err = H5Miterate_by_name("file", "map", &mut idx)
            .expect_err("named H5M iteration requires libhdf5 map behavior");
        assert_eq!(
            err.to_string(),
            "Unsupported: H5Miterate_by_name requires libhdf5 map behavior not implemented in pure-Rust mode"
        );
        assert_eq!(idx, 7);
    }

    #[test]
    fn map_iteration_preserves_failed_callback_index() {
        let mut map = H5Mcreate("file", "map", "u8", "bytes");
        H5Mput(&mut map, b"a".to_vec(), b"1".to_vec()).unwrap();
        H5Mput(&mut map, b"b".to_vec(), b"2".to_vec()).unwrap();
        H5Mput(&mut map, b"c".to_vec(), b"3".to_vec()).unwrap();

        let mut idx = 1;
        let err = H5Miterate_from_with(&map, &mut idx, |key, value| {
            assert_eq!(key, b"b");
            assert_eq!(value, b"2");
            Err::<(), _>(Error::InvalidFormat("callback failure".into()))
        })
        .expect_err("callback error should propagate");
        assert!(matches!(err, Error::InvalidFormat(_)));
        assert_eq!(idx, 1);

        let mut idx = 1;
        let err = H5Miterate(&map, &mut idx, |key| {
            assert_eq!(key, b"b");
            Err::<(), _>(Error::InvalidFormat("callback failure".into()))
        })
        .expect_err("callback error should propagate");
        assert!(matches!(err, Error::InvalidFormat(_)));
        assert_eq!(idx, 1);
    }
}
