use hdf5_pure_rust::engine::writer::{CompoundFieldSpec, DtypeSpec};
use hdf5_pure_rust::format::messages::data_layout::ChunkIndexType;
use hdf5_pure_rust::hl::types::{FieldDescriptor, H5Type, TypeClass};
use hdf5_pure_rust::{Attribute, Dataset, H5Value, Location, Result, WritableFile};

fn file_has_member(file: &hdf5_pure_rust::File, expected: &str) -> hdf5_pure_rust::Result<bool> {
    let mut found = false;
    file.visit_member_names(|name| {
        if name == expected {
            found = true;
        }
        Ok(())
    })?;
    Ok(found)
}

fn group_member_summary_into(
    group: &hdf5_pure_rust::Group,
    expected: &[&str],
    found: &mut [bool],
) -> hdf5_pure_rust::Result<usize> {
    assert_eq!(expected.len(), found.len());
    found.fill(false);
    let mut count = 0;
    group.visit_member_names(|name| {
        count += 1;
        for (idx, expected_name) in expected.iter().enumerate() {
            if name == *expected_name {
                found[idx] = true;
            }
        }
        Ok(())
    })?;
    Ok(count)
}

fn location_attr_summary_into<L: Location>(
    location: &L,
    expected: &[&str],
    found: &mut [bool],
) -> Result<usize> {
    assert_eq!(expected.len(), found.len());
    found.fill(false);
    let mut count = 0;
    location.visit_attr_names(|name| {
        count += 1;
        for (idx, expected_name) in expected.iter().enumerate() {
            if name == *expected_name {
                found[idx] = true;
            }
        }
        Ok(())
    })?;
    Ok(count)
}

fn assert_fractal_heap_uses_filters(bytes: &[u8]) {
    let mut found_filtered_heap = false;
    for (pos, _) in bytes
        .windows(4)
        .enumerate()
        .filter(|(_, window)| *window == b"FRHP")
    {
        let io_filter_pos = pos + 7;
        if io_filter_pos + 2 <= bytes.len() {
            let io_filter_len =
                u16::from_le_bytes([bytes[io_filter_pos], bytes[io_filter_pos + 1]]);
            found_filtered_heap |= io_filter_len > 0;
        }
    }
    assert!(
        found_filtered_heap,
        "expected a filtered fractal heap header"
    );
}

fn assert_dataset_values<T>(ds: &Dataset, expected: &[T]) -> Result<()>
where
    T: H5Type + Default + Clone + PartialEq + std::fmt::Debug,
{
    let mut values = vec![T::default(); ds.size()? as usize];
    ds.read_into(&mut values)?;
    assert_eq!(values, expected);
    Ok(())
}

fn assert_dataset_raw(ds: &Dataset, expected: &[u8]) -> Result<()> {
    let mut raw = vec![0; ds.size()? as usize * ds.element_size()?];
    ds.read_raw_into(&mut raw)?;
    assert_eq!(raw, expected);
    Ok(())
}

fn assert_h5dump_dataset_read(path: &std::path::Path, dataset: &str, context: &str) {
    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-d")
        .arg(dataset)
        .arg(path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -d failed or timed out on {context}: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
    }
}

fn assert_h5py_script(path: &std::path::Path, script: &str, context: &str) {
    let code = format!(
        "import sys, h5py\n\
         f = h5py.File(sys.argv[1], 'r')\n\
         {script}\n\
         f.close()\n\
         print('OK')"
    );
    let out = std::process::Command::new("python3")
        .arg("-c")
        .arg(code)
        .arg(path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success() && String::from_utf8_lossy(&out.stdout).contains("OK"),
            "h5py verification failed on {context}: {}",
            String::from_utf8_lossy(&out.stderr)
        );
    }
}

fn dataset_scalar<T>(ds: &Dataset) -> Result<T>
where
    T: H5Type + Default,
{
    let mut value = T::default();
    ds.read_scalar_into(&mut value)?;
    Ok(value)
}

fn assert_dataset_strings(ds: &Dataset, expected: &[&str]) -> Result<()> {
    let mut strings = Vec::new();
    ds.read_strings_into(&mut strings)?;
    assert_eq!(strings.len(), expected.len());
    for (actual, expected) in strings.iter().zip(expected) {
        assert_eq!(actual, expected);
    }
    Ok(())
}

fn assert_dataset_field<T>(ds: &Dataset, field_name: &str, expected: &[T]) -> Result<()>
where
    T: H5Type + Default + Clone + PartialEq + std::fmt::Debug,
{
    let mut values = vec![T::default(); ds.size()? as usize];
    ds.read_field_into(field_name, &mut values)?;
    assert_eq!(values, expected);
    Ok(())
}

fn assert_dataset_field_values(ds: &Dataset, field_name: &str, expected: &[H5Value]) -> Result<()> {
    let mut values = Vec::new();
    ds.read_field_values_into(field_name, &mut values)?;
    assert_eq!(values, expected);
    Ok(())
}

fn dataset_field_value_count(ds: &Dataset, field_name: &str) -> Result<usize> {
    let mut count = 0;
    ds.visit_field_values(field_name, |_| {
        count += 1;
        Ok(())
    })?;
    Ok(count)
}

fn dataset_has_filter_id(ds: &Dataset, id: u16) -> Result<bool> {
    let mut found = false;
    ds.visit_filters(|filter| {
        if filter.id == id {
            found = true;
        }
        Ok(())
    })?;
    Ok(found)
}

fn assert_attribute_values<T>(attr: &Attribute, expected: &[T]) -> Result<()>
where
    T: H5Type + Default + Clone + PartialEq + std::fmt::Debug,
{
    let len = attr.shape().iter().try_fold(1usize, |acc, &dim| {
        acc.checked_mul(dim as usize)
            .ok_or_else(|| hdf5_pure_rust::Error::InvalidFormat("attribute shape overflows".into()))
    })?;
    let mut values = vec![T::default(); len];
    attr.read_into(&mut values)?;
    assert_eq!(values, expected);
    Ok(())
}

fn attribute_string(attr: &Attribute) -> Result<String> {
    let mut value = String::new();
    attr.read_string_into(&mut value)?;
    Ok(value)
}

fn assert_attribute_strings(attr: &Attribute, expected: &[&str]) -> Result<()> {
    let mut index = 0;
    attr.visit_strings(|value| {
        assert_eq!(Some(value), expected.get(index).copied());
        index += 1;
        Ok(())
    })?;
    assert_eq!(index, expected.len());
    Ok(())
}

#[repr(C)]
#[derive(Clone, Copy)]
struct Point {
    x: f64,
    label: i32,
}

#[repr(C)]
#[derive(Clone, Copy)]
struct WidePair {
    signed: i128,
    unsigned: u128,
}

unsafe impl H5Type for Point {
    fn type_size() -> usize {
        std::mem::size_of::<Point>()
    }

    fn compound_fields_into(out: &mut Vec<FieldDescriptor>) -> Option<()> {
        out.clear();
        out.extend([
            FieldDescriptor {
                name: "x".to_string(),
                offset: std::mem::offset_of!(Point, x),
                size: std::mem::size_of::<f64>(),
                type_class: TypeClass::Float,
            },
            FieldDescriptor {
                name: "label".to_string(),
                offset: std::mem::offset_of!(Point, label),
                size: std::mem::size_of::<i32>(),
                type_class: TypeClass::Integer { signed: true },
            },
        ]);
        Some(())
    }
}

unsafe impl H5Type for WidePair {
    fn type_size() -> usize {
        std::mem::size_of::<WidePair>()
    }

    fn compound_fields_into(out: &mut Vec<FieldDescriptor>) -> Option<()> {
        out.clear();
        out.extend([
            FieldDescriptor {
                name: "signed".to_string(),
                offset: std::mem::offset_of!(WidePair, signed),
                size: std::mem::size_of::<i128>(),
                type_class: TypeClass::Integer { signed: true },
            },
            FieldDescriptor {
                name: "unsigned".to_string(),
                offset: std::mem::offset_of!(WidePair, unsigned),
                size: std::mem::size_of::<u128>(),
                type_class: TypeClass::Integer { signed: false },
            },
        ]);
        Some(())
    }
}

#[test]
fn test_writable_file_simple() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_simple.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("temperatures")
            .shape(&[5])
            .write::<f64>(&[20.0, 21.5, 22.0, 19.8, 23.1])
            .unwrap();
        let f = wf.close().unwrap();
        let ds = f.dataset("temperatures").unwrap();
        assert_dataset_values::<f64>(&ds, &[20.0, 21.5, 22.0, 19.8, 23.1]).unwrap();
    }
}

#[test]
fn test_writable_file_i128_u128_roundtrip() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_i128_u128.h5");
    let signed = [i128::MIN, -42, 0, i128::MAX];
    let unsigned = [0u128, 42, 1u128 << 96, u128::MAX];

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("signed")
            .shape(&[signed.len() as u64])
            .write::<i128>(&signed)
            .unwrap();
        wf.new_dataset_builder("unsigned")
            .shape(&[unsigned.len() as u64])
            .write::<u128>(&unsigned)
            .unwrap();
        wf.add_attr("wide_attr", i128::MIN + 7).unwrap();
        wf.flush().unwrap();
    }

    let f = hdf5_pure_rust::File::open(&path).unwrap();
    assert_dataset_values::<i128>(&f.dataset("signed").unwrap(), &signed).unwrap();
    assert_dataset_values::<u128>(&f.dataset("unsigned").unwrap(), &unsigned).unwrap();
    assert_eq!(
        f.attr("wide_attr").unwrap().read_scalar::<i128>().unwrap(),
        i128::MIN + 7
    );
}

#[test]
fn test_writable_file_compound_i128_u128_fields_roundtrip() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_compound_i128_u128.h5");
    let data = [
        WidePair {
            signed: -123456789012345678901234567890i128,
            unsigned: 123456789012345678901234567890u128,
        },
        WidePair {
            signed: i128::MAX,
            unsigned: u128::MAX,
        },
    ];

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("wide_pairs")
            .shape(&[data.len() as u64])
            .write::<WidePair>(&data)
            .unwrap();
        wf.flush().unwrap();
    }

    let f = hdf5_pure_rust::File::open(&path).unwrap();
    let ds = f.dataset("wide_pairs").unwrap();
    let expected_signed = data.iter().map(|value| value.signed).collect::<Vec<_>>();
    let expected_unsigned = data.iter().map(|value| value.unsigned).collect::<Vec<_>>();
    let expected_signed_values = data
        .iter()
        .map(|value| H5Value::Int(value.signed))
        .collect::<Vec<_>>();
    let expected_unsigned_values = data
        .iter()
        .map(|value| H5Value::UInt(value.unsigned))
        .collect::<Vec<_>>();
    assert_dataset_field::<i128>(&ds, "signed", &expected_signed).unwrap();
    assert_dataset_field::<u128>(&ds, "unsigned", &expected_unsigned).unwrap();
    assert_dataset_field_values(&ds, "signed", &expected_signed_values).unwrap();
    assert_dataset_field_values(&ds, "unsigned", &expected_unsigned_values).unwrap();
}

#[test]
fn test_dataset_builder_rejects_shape_data_length_mismatch() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_shape_mismatch.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let err = wf
            .new_dataset_builder("bad")
            .shape(&[2, 3])
            .write::<i32>(&[1, 2, 3, 4, 5])
            .expect_err("shape/data element count mismatch should be rejected");
        assert!(err
            .to_string()
            .contains("does not match shape element count"));
    }
}

#[test]
fn test_dataset_builder_rejects_excessive_dataspace_rank() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_rank_too_large.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let err = wf
            .new_dataset_builder("bad_rank")
            .shape(&vec![1; 33])
            .write::<u8>(&[0])
            .expect_err("dataspace rank above supported maximum should be rejected");
        assert!(err.to_string().contains("dataspace rank"));
    }
}

#[test]
fn test_writable_rejects_duplicate_child_names_and_invalid_link_names() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_duplicate_child_names.h5");

    let mut wf = WritableFile::create(&path).unwrap();
    wf.new_dataset_builder("data")
        .write::<i32>(&[1, 2, 3])
        .unwrap();

    let err = wf
        .new_dataset_builder("data")
        .write::<i32>(&[4, 5, 6])
        .expect_err("duplicate dataset name should be rejected");
    assert!(err.to_string().contains("already exists"));

    let err = match wf.create_group("bad/name") {
        Ok(_) => panic!("slash-containing group name should be rejected"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("must not contain '/'"));

    let err = wf
        .link_soft("bad/link", "/data")
        .expect_err("slash-containing soft-link name should be rejected");
    assert!(err.to_string().contains("must not contain '/'"));

    let err = wf
        .link_soft("data", "/data")
        .expect_err("soft link must not shadow an existing dataset");
    assert!(err.to_string().contains("already exists"));

    wf.link_soft("alias", "/data").unwrap();
    let err = match wf.create_group("alias") {
        Ok(_) => panic!("group must not shadow a pending soft link"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("already exists"));
}

#[test]
fn test_writable_file_with_groups() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_groups.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut g = wf.create_group("sensors").unwrap();
        g.new_dataset_builder("pressure")
            .write::<f32>(&[1013.25, 1012.0, 1011.5])
            .unwrap();
        let f = wf.close().unwrap();

        assert!(file_has_member(&f, "sensors").unwrap());

        let g = f.group("sensors").unwrap();
        let ds = g.open_dataset("pressure").unwrap();
        assert_dataset_values::<f32>(&ds, &[1013.25, 1012.0, 1011.5]).unwrap();
    }
}

#[test]
fn test_writable_group_attr() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_group_attr.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut g = wf.create_group("sensors").unwrap();
        g.add_attr("site_id", 17i64).unwrap();
        g.new_dataset_builder("pressure")
            .write::<f32>(&[1013.25, 1012.0])
            .unwrap();
        let f = wf.close().unwrap();

        let g = f.group("sensors").unwrap();
        let mut found = [false];
        location_attr_summary_into(&g, &["site_id"], &mut found).unwrap();
        assert!(found[0]);
        let value: i64 = g.attr("site_id").unwrap().read_scalar::<i64>().unwrap();
        assert_eq!(value, 17);
    }

    assert_h5py_script(
        &path,
        "g = f['sensors']\n\
         assert g.attrs['site_id'] == 17\n\
         d = g['pressure']\n\
         assert d.shape == (2,)\n\
         assert d[:].tolist() == [1013.25, 1012.0]",
        "group attribute writer fixture",
    );
}

#[test]
fn test_writable_rejects_duplicate_attribute_names() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_duplicate_attrs.h5");

    let mut wf = WritableFile::create(&path).unwrap();
    wf.add_attr("version", 1i32).unwrap();
    let err = wf
        .add_attr("version", 2i32)
        .expect_err("duplicate root attribute should be rejected");
    assert!(err.to_string().contains("already exists"));

    let mut group = wf.create_group("metadata").unwrap();
    group.add_attr("version", 1i32).unwrap();
    let err = group
        .add_attr_array("version", &[2i32, 3])
        .expect_err("duplicate group attribute should be rejected");
    assert!(err.to_string().contains("already exists"));

    let err = match wf
        .new_dataset_builder("values")
        .attr("units", 1i32)
        .unwrap()
        .fixed_ascii_attr("units", "ms", 8)
    {
        Ok(_) => panic!("duplicate dataset builder attribute should be rejected"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("already exists"));
}

#[test]
fn test_writable_group_dense_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_group_dense_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut g = wf.create_group("annotated").unwrap();
        for idx in 0..16 {
            g.add_attr(&format!("attr_{idx:02}"), idx as i64).unwrap();
        }
        let f = wf.close().unwrap();

        let g = f.group("annotated").unwrap();
        let mut found = [false, false];
        let count = location_attr_summary_into(&g, &["attr_00", "attr_15"], &mut found).unwrap();
        assert_eq!(count, 16);
        assert!(found[0]);
        assert!(found[1]);
        assert_eq!(g.attr("attr_12").unwrap().read_scalar_i64(), Some(12));
    }

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-A")
        .arg("-g")
        .arg("annotated")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -A failed or timed out on dense group attribute fixture: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("attr_00"));
        assert!(stdout.contains("attr_15"));
    }

    assert_h5py_script(
        &path,
        "attrs = f['annotated'].attrs\n\
         assert len(attrs) == 16\n\
         assert attrs['attr_00'] == 0\n\
         assert attrs['attr_12'] == 12\n\
         assert attrs['attr_15'] == 15",
        "dense group attribute writer fixture",
    );
}

#[test]
fn test_writable_dense_attrs_use_indirect_fractal_heap_blocks() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_dense_attrs_indirect_heap.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut g = wf.create_group("annotated").unwrap();
        for idx in 0..40 {
            g.add_fixed_ascii_attr(&format!("attr_{idx:02}"), "value", 256)
                .unwrap();
        }
        let f = wf.close().unwrap();

        let g = f.group("annotated").unwrap();
        let mut found = [false, false];
        let count = location_attr_summary_into(&g, &["attr_00", "attr_39"], &mut found).unwrap();
        assert_eq!(count, 40);
        assert!(found[0]);
        assert!(found[1]);
        assert_eq!(
            attribute_string(&g.attr("attr_17").unwrap()).unwrap(),
            "value"
        );
    }

    let bytes = std::fs::read(&path).unwrap();
    assert!(bytes.windows(4).any(|window| window == b"FHIB"));
    assert_fractal_heap_uses_filters(&bytes);
}

#[test]
fn test_writable_root_and_group_array_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_array_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.add_attr_array("calibration", &[1.0f64, 2.5, 4.0])
            .unwrap();
        let mut g = wf.create_group("sensors").unwrap();
        g.add_attr_array("ids", &[10i32, 20, 30, 40]).unwrap();
        let f = wf.close().unwrap();

        let root_attr = f.attr("calibration").unwrap();
        assert_eq!(root_attr.shape(), &[3]);
        assert_attribute_values::<f64>(&root_attr, &[1.0, 2.5, 4.0]).unwrap();

        let group = f.group("sensors").unwrap();
        let group_attr = group.attr("ids").unwrap();
        assert_eq!(group_attr.shape(), &[4]);
        assert_attribute_values::<i32>(&group_attr, &[10, 20, 30, 40]).unwrap();
    }

    assert_h5py_script(
        &path,
        "assert f.attrs['calibration'].tolist() == [1.0, 2.5, 4.0]\n\
         assert f['sensors'].attrs['ids'].tolist() == [10, 20, 30, 40]",
        "root and group array attribute writer fixture",
    );
}

#[test]
fn test_writable_fixed_string_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_string_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.add_fixed_ascii_attr("project", "hdf", 8).unwrap();
        let mut g = wf.create_group("metadata").unwrap();
        g.add_fixed_utf8_attr("species", "猫", 8).unwrap();
        let f = wf.close().unwrap();

        assert_eq!(
            attribute_string(&f.attr("project").unwrap()).unwrap(),
            "hdf"
        );
        let group = f.group("metadata").unwrap();
        assert_eq!(
            attribute_string(&group.attr("species").unwrap()).unwrap(),
            "猫"
        );
    }

    assert_h5py_script(
        &path,
        "project = f.attrs['project']\n\
         species = f['metadata'].attrs['species']\n\
         project = project.decode('utf-8') if isinstance(project, bytes) else str(project)\n\
         species = species.decode('utf-8') if isinstance(species, bytes) else str(species)\n\
         assert project.rstrip('\\x00') == 'hdf'\n\
         assert species.rstrip('\\x00') == '猫'",
        "fixed string attribute writer fixture",
    );
}

#[test]
#[cfg(target_pointer_width = "64")]
fn test_writable_rejects_unrepresentable_fixed_string_lengths() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_string_len_overflow.h5");
    let too_long = u32::MAX as usize + 1;

    let mut wf = WritableFile::create(&path).unwrap();
    let err = wf
        .add_fixed_ascii_attr("root", "x", too_long)
        .expect_err("root fixed string length should fit encoded u32");
    assert!(err.to_string().contains("fixed string length"));

    let err = wf
        .create_group("metadata")
        .unwrap()
        .add_fixed_utf8_attr("group", "x", too_long)
        .expect_err("group fixed string length should fit encoded u32");
    assert!(err.to_string().contains("fixed string length"));

    let err = match wf
        .new_dataset_builder("values")
        .fixed_ascii_attr("units", "x", too_long)
    {
        Ok(_) => panic!("dataset attribute fixed string length should fit encoded u32"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("fixed string length"));

    let err = wf
        .new_dataset_builder("names")
        .write_fixed_ascii_strings(&["x"], too_long)
        .expect_err("dataset fixed string length should fit encoded u32");
    assert!(err.to_string().contains("fixed string length"));
}

#[test]
fn test_writable_routes_oversized_attrs_to_dense_storage() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_oversized_dense_attrs.h5");
    let large_len = u16::MAX as usize + 1;

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut group = wf.create_group("metadata").unwrap();
        group
            .add_fixed_ascii_attr("large_attr", "x", large_len)
            .unwrap();
        wf.new_dataset_builder("data")
            .fixed_ascii_attr("large_ds_attr", "y", large_len)
            .unwrap()
            .write::<i32>(&[1, 2, 3])
            .unwrap();
        let f = wf.close().unwrap();

        let group = f.group("metadata").unwrap();
        assert_eq!(
            attribute_string(&group.attr("large_attr").unwrap()).unwrap(),
            "x"
        );
        let ds = f.dataset("data").unwrap();
        assert_dataset_values::<i32>(&ds, &[1, 2, 3]).unwrap();
        assert_eq!(
            attribute_string(&ds.attr("large_ds_attr").unwrap()).unwrap(),
            "y"
        );
    }
}

#[test]
fn test_writable_dense_attrs_support_wider_heap_ids() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_dense_attr_wide_heap_id.h5");
    let large_len = (1usize << 24) + 1;

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut group = wf.create_group("metadata").unwrap();
        group
            .add_fixed_ascii_attr("very_large_attr", "z", large_len)
            .unwrap();
        for idx in 0..8 {
            group.add_attr(&format!("attr_{idx}"), idx).unwrap();
        }
        let f = wf.close().unwrap();

        let group = f.group("metadata").unwrap();
        assert_eq!(
            attribute_string(&group.attr("very_large_attr").unwrap()).unwrap(),
            "z"
        );
        assert_attribute_values::<i32>(&group.attr("attr_7").unwrap(), &[7]).unwrap();
    }

    assert!(std::fs::read(&path)
        .unwrap()
        .windows(4)
        .any(|window| window == b"BTHD"));

    let bytes = std::fs::read(&path).unwrap();
    let heap_pos = bytes
        .windows(4)
        .position(|window| window == b"FRHP")
        .expect("dense attribute storage should emit a fractal heap header");
    let heap_id_len = u16::from_le_bytes([bytes[heap_pos + 5], bytes[heap_pos + 6]]);
    let max_heap_size = u16::from_le_bytes([bytes[heap_pos + 128], bytes[heap_pos + 129]]);
    assert_eq!(heap_id_len, 9);
    assert_eq!(max_heap_size, 32);
}

#[test]
fn test_writable_dense_attrs_cross_leaf_btree_boundary() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_dense_attr_internal_btree.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut group = wf.create_group("metadata").unwrap();
        for idx in 0..64 {
            group.add_attr(&format!("attr_{idx:02}"), idx).unwrap();
        }
        let f = wf.close().unwrap();

        let group = f.group("metadata").unwrap();
        let mut found = [false, false];
        let count =
            location_attr_summary_into(&group, &["attr_00", "attr_63"], &mut found).unwrap();
        assert_eq!(count, 64);
        assert!(found[0]);
        assert!(found[1]);
        assert_attribute_values::<i32>(&group.attr("attr_42").unwrap(), &[42]).unwrap();
    }

    let bytes = std::fs::read(&path).unwrap();
    assert!(bytes.windows(16).any(|window| {
        window.starts_with(b"BTHD")
            && window.get(5) == Some(&8)
            && u16::from_le_bytes([window[12], window[13]]) > 0
    }));

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-A")
        .arg("-g")
        .arg("metadata")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -A failed or timed out on dense attribute internal-B-tree fixture: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("attr_00"));
        assert!(stdout.contains("attr_63"));
    }

    assert_h5py_script(
        &path,
        "attrs = f['metadata'].attrs\n\
         assert len(attrs) == 64\n\
         assert attrs['attr_00'] == 0\n\
         assert attrs['attr_42'] == 42\n\
         assert attrs['attr_63'] == 63",
        "dense attribute internal-B-tree writer fixture",
    );
}

#[test]
fn test_writable_rejects_oversized_attribute_name_field() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_oversized_attr_name.h5");

    let mut wf = WritableFile::create(&path).unwrap();
    let name = "a".repeat(u16::MAX as usize);
    wf.add_attr(&name, 1i32).unwrap();
    let err = wf
        .flush()
        .expect_err("attribute name length should fit u16 field");
    assert!(err.to_string().contains("attribute name"));
}

#[test]
fn test_writable_fixed_string_array_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_string_array_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.add_fixed_ascii_attr_array("stages", &["raw", "qc", "done"], 8)
            .unwrap();
        let mut g = wf.create_group("metadata").unwrap();
        g.add_fixed_utf8_attr_array("labels", &["猫", "å"], 8)
            .unwrap();
        let f = wf.close().unwrap();

        let stages = f.attr("stages").unwrap();
        assert_eq!(stages.shape(), &[3]);
        assert_attribute_strings(&stages, &["raw", "qc", "done"]).unwrap();

        let labels = f.group("metadata").unwrap().attr("labels").unwrap();
        assert_eq!(labels.shape(), &[2]);
        assert_attribute_strings(&labels, &["猫", "å"]).unwrap();
    }

    assert_h5py_script(
        &path,
        "text = lambda value: value.decode('utf-8') if isinstance(value, bytes) else str(value)\n\
         stages = [text(value).rstrip('\\x00') for value in f.attrs['stages'].tolist()]\n\
         labels = [text(value).rstrip('\\x00') for value in f['metadata'].attrs['labels'].tolist()]\n\
         assert stages == ['raw', 'qc', 'done']\n\
         assert labels == ['猫', 'å']",
        "fixed string array attribute writer fixture",
    );
}

#[test]
fn test_dataset_builder_scalar_and_array_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_dataset_builder_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("values")
            .attr("version", 2i64)
            .unwrap()
            .attr_array("scale", &[1.0f64, 10.0, 100.0])
            .unwrap()
            .write::<i32>(&[4, 5, 6])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("values").unwrap();
        assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(2));
        let scale = ds.attr("scale").unwrap();
        assert_eq!(scale.shape(), &[3]);
        assert_attribute_values::<f64>(&scale, &[1.0, 10.0, 100.0]).unwrap();
    }

    assert_h5py_script(
        &path,
        "d = f['values']\n\
         assert d[:].tolist() == [4, 5, 6]\n\
         assert d.attrs['version'] == 2\n\
         assert d.attrs['scale'].tolist() == [1.0, 10.0, 100.0]",
        "dataset builder numeric attribute writer fixture",
    );
}

#[test]
fn test_dataset_builder_fixed_string_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_dataset_string_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("values")
            .fixed_ascii_attr("units", "ms", 8)
            .unwrap()
            .fixed_utf8_attr("label", "猫", 8)
            .unwrap()
            .write::<i32>(&[1, 2, 3])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("values").unwrap();
        assert_eq!(attribute_string(&ds.attr("units").unwrap()).unwrap(), "ms");
        assert_eq!(attribute_string(&ds.attr("label").unwrap()).unwrap(), "猫");
    }

    assert_h5py_script(
        &path,
        "d = f['values']\n\
         text = lambda value: value.decode('utf-8') if isinstance(value, bytes) else str(value)\n\
         assert d[:].tolist() == [1, 2, 3]\n\
         assert text(d.attrs['units']).rstrip('\\x00') == 'ms'\n\
         assert text(d.attrs['label']).rstrip('\\x00') == '猫'",
        "dataset builder fixed-string attribute writer fixture",
    );
}

#[test]
fn test_dataset_builder_fixed_string_array_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_dataset_string_array_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("values")
            .fixed_ascii_attr_array("units", &["ms", "s"], 8)
            .unwrap()
            .fixed_utf8_attr_array("labels", &["猫", "å"], 8)
            .unwrap()
            .write::<i32>(&[1, 2, 3])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("values").unwrap();
        assert_attribute_strings(&ds.attr("units").unwrap(), &["ms", "s"]).unwrap();
        assert_attribute_strings(&ds.attr("labels").unwrap(), &["猫", "å"]).unwrap();
    }

    assert_h5py_script(
        &path,
        "d = f['values']\n\
         text = lambda value: value.decode('utf-8') if isinstance(value, bytes) else str(value)\n\
         assert d[:].tolist() == [1, 2, 3]\n\
         assert [text(value).rstrip('\\x00') for value in d.attrs['units'].tolist()] == ['ms', 's']\n\
         assert [text(value).rstrip('\\x00') for value in d.attrs['labels'].tolist()] == ['猫', 'å']",
        "dataset builder fixed-string array attribute writer fixture",
    );
}

#[test]
fn test_dataset_builder_compact_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_compact_dataset_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("values")
            .compact()
            .attr("version", 3i64)
            .unwrap()
            .write::<i16>(&[7, 8, 9])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("values").unwrap();
        assert_dataset_values::<i16>(&ds, &[7, 8, 9]).unwrap();
        assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(3));
    }

    assert_h5py_script(
        &path,
        "d = f['values']\n\
         assert d[:].tolist() == [7, 8, 9]\n\
         assert d.attrs['version'] == 3",
        "compact dataset attribute writer fixture",
    );
}

#[test]
fn test_dataset_builder_attrs_with_explicit_fill_values() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_dataset_attrs_with_fill_values.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("contiguous")
            .fill_value::<i32>(-7)
            .attr("version", 4i64)
            .unwrap()
            .write::<i32>(&[1, 2, 3])
            .unwrap();
        wf.new_dataset_builder("compact")
            .compact()
            .fill_value::<i16>(-2)
            .attr("version", 5i64)
            .unwrap()
            .write::<i16>(&[7, 8])
            .unwrap();
        wf.new_dataset_builder("scalar")
            .fill_value::<u64>(99)
            .attr("version", 6i64)
            .unwrap()
            .write_scalar::<u64>(42)
            .unwrap();
        let f = wf.close().unwrap();

        let contiguous = f.dataset("contiguous").unwrap();
        let plist = contiguous.create_plist().unwrap();
        assert_eq!(plist.fill_value, Some((-7i32).to_le_bytes().to_vec()));
        assert_eq!(
            contiguous.attr("version").unwrap().read_scalar_i64(),
            Some(4)
        );
        assert_dataset_values::<i32>(&contiguous, &[1, 2, 3]).unwrap();

        let compact = f.dataset("compact").unwrap();
        let plist = compact.create_plist().unwrap();
        assert_eq!(plist.fill_value, Some((-2i16).to_le_bytes().to_vec()));
        assert_eq!(compact.attr("version").unwrap().read_scalar_i64(), Some(5));
        assert_dataset_values::<i16>(&compact, &[7, 8]).unwrap();

        let scalar = f.dataset("scalar").unwrap();
        let plist = scalar.create_plist().unwrap();
        assert_eq!(plist.fill_value, Some(99u64.to_le_bytes().to_vec()));
        assert_eq!(scalar.attr("version").unwrap().read_scalar_i64(), Some(6));
        assert_eq!(dataset_scalar::<u64>(&scalar).unwrap(), 42);
    }

    assert_h5py_script(
        &path,
        "assert f['contiguous'][:].tolist() == [1, 2, 3]\n\
         assert f['contiguous'].fillvalue == -7\n\
         assert f['contiguous'].attrs['version'] == 4\n\
         assert f['compact'][:].tolist() == [7, 8]\n\
         assert f['compact'].fillvalue == -2\n\
         assert f['compact'].attrs['version'] == 5\n\
         assert int(f['scalar'][()]) == 42\n\
         assert f['scalar'].fillvalue == 99\n\
         assert f['scalar'].attrs['version'] == 6",
        "dataset attribute and explicit fill-value writer fixture",
    );
}

#[test]
fn test_writable_file_group_with_many_compact_links() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_many_links.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut g = wf.create_group("many").unwrap();
        for idx in 0..40 {
            let name = format!("value_{idx:02}");
            g.new_dataset_builder(&name).write::<i32>(&[idx]).unwrap();
        }
        let f = wf.close().unwrap();

        let group = f.group("many").unwrap();
        let mut found = [false, false];
        let count =
            group_member_summary_into(&group, &["value_00", "value_39"], &mut found).unwrap();
        assert_eq!(count, 40);
        assert!(found[0]);
        assert!(found[1]);
        let value_37 = group.open_dataset("value_37").unwrap();
        assert_dataset_values::<i32>(&value_37, &[37]).unwrap();
    }

    let out = std::process::Command::new("h5dump")
        .arg("-H")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump failed on many-link writer fixture: {}",
            String::from_utf8_lossy(&out.stderr)
        );
    }
}

#[test]
fn test_writable_dense_links_support_wider_heap_ids() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_dense_link_wide_heap_id.h5");
    let long_name = format!("value_{}", "x".repeat(u16::MAX as usize + 64));

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut g = wf.create_group("many").unwrap();
        for idx in 0..8 {
            let name = format!("value_{idx:02}");
            g.new_dataset_builder(&name).write::<i32>(&[idx]).unwrap();
        }
        g.new_dataset_builder(&long_name)
            .write::<i32>(&[99])
            .unwrap();
        let f = wf.close().unwrap();

        let group = f.group("many").unwrap();
        let expected = ["value_00", long_name.as_str()];
        let mut found = [false, false];
        let count = group_member_summary_into(&group, &expected, &mut found).unwrap();
        assert_eq!(count, 9);
        assert!(found[0]);
        assert!(found[1]);
    }

    assert!(std::fs::read(&path)
        .unwrap()
        .windows(4)
        .any(|window| window == b"BTHD"));
}

#[test]
fn test_writable_dense_links_cross_leaf_btree_boundary() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_dense_link_internal_btree.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut group = wf.create_group("many").unwrap();
        for idx in 0..64 {
            group
                .new_dataset_builder(&format!("value_{idx:02}"))
                .write::<i32>(&[idx])
                .unwrap();
        }
        let f = wf.close().unwrap();

        let group = f.group("many").unwrap();
        let mut found = [false, false];
        let count =
            group_member_summary_into(&group, &["value_00", "value_63"], &mut found).unwrap();
        assert_eq!(count, 64);
        assert!(found[0]);
        assert!(found[1]);
        assert_dataset_values::<i32>(&group.open_dataset("value_42").unwrap(), &[42]).unwrap();
    }

    let bytes = std::fs::read(&path).unwrap();
    assert!(bytes.windows(16).any(|window| {
        window.starts_with(b"BTHD")
            && window.get(5) == Some(&5)
            && u16::from_le_bytes([window[12], window[13]]) > 0
    }));

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-H")
        .arg("-g")
        .arg("many")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -H failed or timed out on dense link internal-B-tree fixture: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("value_00"));
        assert!(stdout.contains("value_63"));
    }

    assert_h5py_script(
        &path,
        "g = f['many']\n\
         assert len(g) == 64\n\
         assert sorted(g.keys())[0] == 'value_00'\n\
         assert sorted(g.keys())[-1] == 'value_63'\n\
         assert int(g['value_42'][0]) == 42",
        "dense link internal-B-tree writer fixture",
    );
}

#[test]
fn test_writable_dense_links_use_indirect_fractal_heap_blocks() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_dense_links_indirect_heap.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut group = wf.create_group("many").unwrap();
        for idx in 0..40 {
            let name = format!("value_{idx:02}_{}", "x".repeat(192));
            group
                .new_dataset_builder(&name)
                .write::<i32>(&[idx])
                .unwrap();
        }
        let f = wf.close().unwrap();

        let group = f.group("many").unwrap();
        let first = format!("value_{:02}_{}", 0, "x".repeat(192));
        let last = format!("value_{:02}_{}", 39, "x".repeat(192));
        let mut found = [false, false];
        let count = group_member_summary_into(&group, &[&first, &last], &mut found).unwrap();
        assert_eq!(count, 40);
        assert!(found[0]);
        assert!(found[1]);
        assert_dataset_values::<i32>(&group.open_dataset(&last).unwrap(), &[39]).unwrap();
    }

    let bytes = std::fs::read(&path).unwrap();
    assert!(bytes.windows(4).any(|window| window == b"FHIB"));
    assert_fractal_heap_uses_filters(&bytes);
}

#[test]
fn test_writable_file_chunked_compressed() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_chunked.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..100).collect();
        wf.new_dataset_builder("data")
            .shape(&[100])
            .chunk(&[25])
            .deflate(4)
            .shuffle()
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        let mut vals = vec![0i32; ds.size().unwrap() as usize];
        ds.read_into(&mut vals).unwrap();
        assert_eq!(vals.len(), 100);
        for (i, v) in vals.iter().enumerate() {
            assert_eq!(*v, i as i32);
        }
    }
}

#[test]
fn test_writable_file_chunked_row_slab_streams_multidimensional_run() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_chunked_row_slab.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..24).collect();
        wf.new_dataset_builder("fixed")
            .shape(&[6, 4])
            .chunk(&[2, 4])
            .write::<i32>(&data)
            .unwrap();
        wf.new_dataset_builder("resizable")
            .shape(&[6, 4])
            .max_shape(&[12, 4])
            .chunk(&[2, 4])
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let fixed = f.dataset("fixed").unwrap();
        let fixed_info = fixed.info().unwrap();
        assert_eq!(
            fixed_info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        assert_dataset_values::<i32>(&fixed, &data).unwrap();

        let resizable = f.dataset("resizable").unwrap();
        let resizable_info = resizable.info().unwrap();
        assert_eq!(
            resizable_info.layout.chunk_index_type,
            Some(ChunkIndexType::BTreeV2)
        );
        assert_dataset_values::<i32>(&resizable, &data).unwrap();
    }
}

#[test]
fn test_writable_file_chunked_filtered_row_slab_streams_multidimensional_run() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_chunked_filtered_row_slab.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..24).collect();
        wf.new_dataset_builder("fixed")
            .shape(&[6, 4])
            .chunk(&[2, 4])
            .shuffle()
            .deflate(3)
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("fixed").unwrap();
        let info = ds.info().unwrap();
        assert_eq!(
            info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        assert!(dataset_has_filter_id(&ds, 1).unwrap());
        assert!(dataset_has_filter_id(&ds, 2).unwrap());
        assert_dataset_values::<i32>(&ds, &data).unwrap();
    }

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-pH")
        .arg("-d")
        .arg("fixed")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -pH failed or timed out on filtered row-slab fixture: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("CHUNKED"));
        assert!(stdout.contains("DEFLATE"));
        assert!(stdout.contains("SHUFFLE"));
    }

    assert_h5py_script(
        &path,
        "d = f['fixed']\n\
         assert d.shape == (6, 4)\n\
         assert d.chunks == (2, 4)\n\
         assert d.compression == 'gzip'\n\
         assert d.shuffle\n\
         assert d[:].reshape(-1).tolist() == list(range(24))",
        "filtered fixed-array row-slab fixture",
    );
}

#[test]
fn test_writable_file_chunked_row_slab_streams_partial_edge_run() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_chunked_partial_edge_row_slab.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..20).collect();
        wf.new_dataset_builder("fixed")
            .shape(&[5, 4])
            .chunk(&[2, 4])
            .write::<i32>(&data)
            .unwrap();
        wf.new_dataset_builder("resizable")
            .shape(&[5, 4])
            .max_shape(&[12, 4])
            .chunk(&[2, 4])
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let fixed = f.dataset("fixed").unwrap();
        let fixed_info = fixed.info().unwrap();
        assert_eq!(
            fixed_info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        assert_dataset_values::<i32>(&fixed, &data).unwrap();

        let resizable = f.dataset("resizable").unwrap();
        let resizable_info = resizable.info().unwrap();
        assert_eq!(
            resizable_info.layout.chunk_index_type,
            Some(ChunkIndexType::BTreeV2)
        );
        assert_dataset_values::<i32>(&resizable, &data).unwrap();
    }

    assert_h5py_script(
        &path,
        "expected = list(range(20))\n\
         fixed = f['fixed']\n\
         resizable = f['resizable']\n\
         assert fixed.shape == (5, 4)\n\
         assert fixed.chunks == (2, 4)\n\
         assert fixed[:].reshape(-1).tolist() == expected\n\
         assert resizable.shape == (5, 4)\n\
         assert resizable.maxshape == (12, 4)\n\
         assert resizable.chunks == (2, 4)\n\
         assert resizable[:].reshape(-1).tolist() == expected",
        "partial-edge fixed-array and B-tree v2 row-slab fixture",
    );
}

#[test]
fn test_writable_file_chunked_filtered_row_slab_partial_edge_run() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_chunked_filtered_partial_edge_row_slab.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..20).collect();
        wf.new_dataset_builder("fixed")
            .shape(&[5, 4])
            .chunk(&[2, 4])
            .shuffle()
            .deflate(3)
            .write::<i32>(&data)
            .unwrap();
        wf.new_dataset_builder("resizable")
            .shape(&[5, 4])
            .max_shape(&[12, 4])
            .chunk(&[2, 4])
            .shuffle()
            .deflate(3)
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let fixed = f.dataset("fixed").unwrap();
        let fixed_info = fixed.info().unwrap();
        assert_eq!(
            fixed_info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        assert!(dataset_has_filter_id(&fixed, 1).unwrap());
        assert!(dataset_has_filter_id(&fixed, 2).unwrap());
        assert_dataset_values::<i32>(&fixed, &data).unwrap();

        let resizable = f.dataset("resizable").unwrap();
        let resizable_info = resizable.info().unwrap();
        assert_eq!(
            resizable_info.layout.chunk_index_type,
            Some(ChunkIndexType::BTreeV2)
        );
        assert!(dataset_has_filter_id(&resizable, 1).unwrap());
        assert!(dataset_has_filter_id(&resizable, 2).unwrap());
        assert_dataset_values::<i32>(&resizable, &data).unwrap();
    }

    assert_h5py_script(
        &path,
        "expected = list(range(20))\n\
         fixed = f['fixed']\n\
         resizable = f['resizable']\n\
         assert fixed.shape == (5, 4)\n\
         assert fixed.chunks == (2, 4)\n\
         assert fixed.compression == 'gzip'\n\
         assert fixed.shuffle\n\
         assert fixed[:].reshape(-1).tolist() == expected\n\
         assert resizable.shape == (5, 4)\n\
         assert resizable.maxshape == (12, 4)\n\
         assert resizable.chunks == (2, 4)\n\
         assert resizable.compression == 'gzip'\n\
         assert resizable.shuffle\n\
         assert resizable[:].reshape(-1).tolist() == expected",
        "filtered partial-edge fixed-array and B-tree v2 row-slab fixture",
    );
}

#[test]
fn test_writable_file_fixed_array_deflate_parallel_threshold_tail_round_trip() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_fixed_array_deflate_parallel_tail.h5");
    const CHUNK: usize = 2048;
    const LEN: usize = CHUNK * 8 + 17;

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..LEN).map(|value| value as i32 * 3 - 11).collect();
        wf.new_dataset_builder("data")
            .shape(&[LEN as u64])
            .chunk(&[CHUNK as u64])
            .deflate(4)
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        let info = ds.info().unwrap();
        assert_eq!(
            info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        assert!(dataset_has_filter_id(&ds, 1).unwrap());
        assert_dataset_values::<i32>(&ds, &data).unwrap();

        let mut boundary = vec![0i32; 7];
        ds.read_slice_into(CHUNK - 3..CHUNK + 4, &mut boundary)
            .unwrap();
        assert_eq!(boundary, data[CHUNK - 3..CHUNK + 4]);

        let mut tail = vec![0i32; 20];
        ds.read_slice_into(LEN - 20..LEN, &mut tail).unwrap();
        assert_eq!(tail, data[LEN - 20..LEN]);
    }
}

#[test]
fn test_writable_file_extensible_array_deflate_parallel_threshold_tail_round_trip() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_extensible_array_deflate_parallel_tail.h5");
    const CHUNK: usize = 2048;
    const LEN: usize = CHUNK * 8 + 17;

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..LEN).map(|value| 7000 - value as i32 * 5).collect();
        wf.new_dataset_builder("data")
            .shape(&[LEN as u64])
            .max_shape(&[(LEN * 2) as u64])
            .chunk(&[CHUNK as u64])
            .deflate(4)
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        let info = ds.info().unwrap();
        assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
        assert!(dataset_has_filter_id(&ds, 1).unwrap());
        assert_dataset_values::<i32>(&ds, &data).unwrap();

        let mut boundary = vec![0i32; 7];
        ds.read_slice_into(CHUNK - 3..CHUNK + 4, &mut boundary)
            .unwrap();
        assert_eq!(boundary, data[CHUNK - 3..CHUNK + 4]);

        let mut tail = vec![0i32; 20];
        ds.read_slice_into(LEN - 20..LEN, &mut tail).unwrap();
        assert_eq!(tail, data[LEN - 20..LEN]);
    }
}

#[test]
fn test_writable_file_chunked_filtered_non_slab_multidim_round_trip() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_chunked_filtered_non_slab_multidim.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..25).map(|value| value * 3 - 7).collect();
        wf.new_dataset_builder("data")
            .shape(&[5, 5])
            .chunk(&[2, 3])
            .shuffle()
            .deflate(3)
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        let info = ds.info().unwrap();
        assert_eq!(
            info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        assert!(dataset_has_filter_id(&ds, 1).unwrap());
        assert!(dataset_has_filter_id(&ds, 2).unwrap());
        assert_dataset_values::<i32>(&ds, &data).unwrap();
    }

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-pH")
        .arg("-d")
        .arg("data")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -pH failed or timed out on filtered non-slab fixture: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("CHUNKED"));
        assert!(stdout.contains("DEFLATE"));
        assert!(stdout.contains("SHUFFLE"));
    }

    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (5, 5)\n\
         assert d.chunks == (2, 3)\n\
         assert d.compression == 'gzip'\n\
         assert d.shuffle\n\
         assert d[:].reshape(-1).tolist() == [value * 3 - 7 for value in range(25)]",
        "filtered fixed-array non-slab fixture",
    );
}

#[test]
fn test_writable_file_chunked_fixed_array_beyond_one_page() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_chunked_fixed_array_deep.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..4097).collect();
        wf.new_dataset_builder("data")
            .shape(&[4097])
            .chunk(&[1])
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        assert_eq!(
            ds.info().unwrap().layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        assert_dataset_values::<i32>(&ds, &data).unwrap();
    }

    assert_h5dump_dataset_read(&path, "data", "fixed-array partial final page fixture");
    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (4097,)\n\
         assert d.chunks == (1,)\n\
         assert d[:5].tolist() == [0, 1, 2, 3, 4]\n\
         assert d[-3:].tolist() == [4094, 4095, 4096]",
        "fixed-array partial final page fixture",
    );
}

#[test]
fn test_writable_file_chunked_fletcher32() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_chunked_fletcher32.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..64).collect();
        wf.new_dataset_builder("data")
            .shape(&[64])
            .chunk(&[16])
            .fletcher32()
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        assert_dataset_values::<i32>(&ds, &data).unwrap();
        assert!(dataset_has_filter_id(&ds, 3).unwrap());
    }

    assert_h5dump_dataset_read(&path, "data", "Fletcher32 chunked writer fixture");
    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (64,)\n\
         assert d.chunks == (16,)\n\
         assert d.fletcher32 is True\n\
         assert d[:].tolist() == list(range(64))",
        "Fletcher32 chunked writer fixture",
    );
}

#[test]
fn test_dataset_builder_writes_max_shape_dataspace() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_max_shape.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("finite")
            .shape(&[4])
            .max_shape(&[10])
            .chunk(&[2])
            .write::<i32>(&[1, 2, 3, 4])
            .unwrap();
        wf.new_dataset_builder("unlimited_2d")
            .shape(&[2, 3])
            .resizable()
            .chunk(&[1, 3])
            .write::<i32>(&[1, 2, 3, 4, 5, 6])
            .unwrap();
        let f = wf.close().unwrap();

        let finite = f.dataset("finite").unwrap();
        let finite_space = finite.space().unwrap();
        assert_eq!(finite_space.shape(), &[4]);
        assert_eq!(finite_space.maxdims().unwrap(), &[10]);
        let finite_info = finite.info().unwrap();
        assert_eq!(finite_info.layout.version, 4);
        assert_eq!(
            finite_info.layout.chunk_index_type,
            Some(ChunkIndexType::BTreeV2)
        );
        assert_dataset_values::<i32>(&finite, &[1, 2, 3, 4]).unwrap();

        let unlimited = f.dataset("unlimited_2d").unwrap();
        let unlimited_space = unlimited.space().unwrap();
        assert_eq!(unlimited_space.shape(), &[2, 3]);
        assert_eq!(unlimited_space.maxdims().unwrap(), &[u64::MAX, u64::MAX]);
        let unlimited_info = unlimited.info().unwrap();
        assert_eq!(unlimited_info.layout.version, 4);
        assert_eq!(
            unlimited_info.layout.chunk_index_type,
            Some(ChunkIndexType::BTreeV2)
        );
        assert_dataset_values::<i32>(&unlimited, &[1, 2, 3, 4, 5, 6]).unwrap();
    }
}

#[test]
fn test_dataset_builder_fixed_max_shape_uses_fixed_chunk_indexes() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_fixed_max_shape_chunk_indexes.h5");
    let first = [30i16, 31, 32, 33];
    let last = [40i16, 41, 42, 43];

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let grid_data: Vec<i32> = (0..16).collect();
        wf.new_dataset_builder("fixed_grid")
            .shape(&[16])
            .max_shape(&[16])
            .chunk(&[4])
            .write::<i32>(&grid_data)
            .unwrap();
        wf.new_dataset_builder("fixed_single")
            .shape(&[3])
            .max_shape(&[3])
            .chunk(&[8])
            .write::<i32>(&[7, 8, 9])
            .unwrap();
        wf.new_dataset_builder("fixed_fill")
            .shape(&[12])
            .max_shape(&[12])
            .chunk(&[4])
            .fill_value::<i16>(-4)
            .write_fill::<i16>()
            .unwrap();
        wf.new_dataset_builder("fixed_sparse")
            .shape(&[16])
            .max_shape(&[16])
            .chunk(&[4])
            .fill_value::<i16>(-2)
            .write_chunks::<i16, _, _>([([0u64], &first[..]), ([12u64], &last[..])])
            .unwrap();
        let f = wf.close().unwrap();

        let fixed_grid = f.dataset("fixed_grid").unwrap();
        let fixed_grid_info = fixed_grid.info().unwrap();
        assert_eq!(fixed_grid_info.layout.version, 4);
        assert_eq!(
            fixed_grid_info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        assert_dataset_values::<i32>(&fixed_grid, &grid_data).unwrap();

        let fixed_single = f.dataset("fixed_single").unwrap();
        let fixed_single_info = fixed_single.info().unwrap();
        assert_eq!(fixed_single_info.layout.version, 4);
        assert_eq!(
            fixed_single_info.layout.chunk_index_type,
            Some(ChunkIndexType::SingleChunk)
        );
        assert_dataset_values::<i32>(&fixed_single, &[7, 8, 9]).unwrap();

        let fixed_fill = f.dataset("fixed_fill").unwrap();
        let fixed_fill_info = fixed_fill.info().unwrap();
        assert_eq!(fixed_fill_info.layout.version, 4);
        assert_eq!(
            fixed_fill_info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        let fill_values: Vec<i16> = fixed_fill.read().unwrap();
        assert_eq!(fill_values, vec![-4; 12]);

        let fixed_sparse = f.dataset("fixed_sparse").unwrap();
        let fixed_sparse_info = fixed_sparse.info().unwrap();
        assert_eq!(fixed_sparse_info.layout.version, 4);
        assert_eq!(
            fixed_sparse_info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        let sparse_values: Vec<i16> = fixed_sparse.read().unwrap();
        assert_eq!(
            sparse_values,
            vec![30, 31, 32, 33, -2, -2, -2, -2, -2, -2, -2, -2, 40, 41, 42, 43]
        );
    }

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-pH")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -pH failed or timed out on fixed max-shape fixture: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("fixed_grid"));
        assert!(stdout.contains("fixed_single"));
        assert!(stdout.contains("CHUNKED"));
    }

    assert_h5py_script(
        &path,
        "assert f['fixed_grid'].shape == (16,)\n\
         assert f['fixed_grid'].chunks == (4,)\n\
         assert f['fixed_grid'][:].tolist() == list(range(16))\n\
         assert f['fixed_single'].shape == (3,)\n\
         assert f['fixed_single'].chunks == (8,)\n\
         assert f['fixed_single'][:].tolist() == [7, 8, 9]\n\
         assert f['fixed_fill'][:].tolist() == [-4] * 12\n\
         assert f['fixed_sparse'][:].tolist() == [30, 31, 32, 33, -2, -2, -2, -2, -2, -2, -2, -2, 40, 41, 42, 43]",
        "fixed max-shape fixed-array and single-chunk fixture",
    );
}

#[test]
fn test_dataset_builder_finite_max_shape_full_write_uses_btree_v2() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_finite_max_shape_full_btree_v2.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..300).collect();
        wf.new_dataset_builder("data")
            .shape(&[data.len() as u64])
            .max_shape(&[600])
            .chunk(&[1])
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        let info = ds.info().unwrap();
        assert_eq!(info.layout.version, 4);
        assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
        assert_dataset_values::<i32>(&ds, &data).unwrap();
    }

    assert_h5dump_dataset_read(&path, "data", "finite max-shape full B-tree v2 fixture");
    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (300,)\n\
         assert d.maxshape == (600,)\n\
         assert d.chunks == (1,)\n\
         assert d.dtype.kind == 'i'\n\
         assert d.dtype.itemsize == 4\n\
         assert d[:].tolist() == list(range(300))",
        "finite max-shape full B-tree v2 fixture",
    );
}

#[test]
fn test_dataset_builder_large_max_shape_full_write_uses_btree_v2() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_large_max_shape_full_btree_v2.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<u8> = (0..1_000_001).map(|idx| (idx % 251) as u8).collect();
        wf.new_dataset_builder("data")
            .shape(&[data.len() as u64])
            .max_shape(&[2_000_000])
            .chunk(&[1])
            .write::<u8>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        let info = ds.info().unwrap();
        assert_eq!(info.layout.version, 4);
        assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
        assert_dataset_values::<u8>(&ds, &data).unwrap();
    }

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-pH")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -pH failed or timed out on large max-shape full B-tree v2 fixture: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("data"));
        assert!(stdout.contains("CHUNKED"));
    }

    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (1000001,)\n\
         assert d.maxshape == (2000000,)\n\
         assert d.chunks == (1,)\n\
         assert int(d[0]) == 0\n\
         assert d[250:253].tolist() == [250, 0, 1]\n\
         assert int(d[1000000]) == 16",
        "large max-shape full B-tree v2 fixture",
    );
}

#[test]
fn test_dataset_builder_chunked_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_chunked_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i32> = (0..16).collect();
        wf.new_dataset_builder("data")
            .shape(&[16])
            .chunk(&[4])
            .attr("version", 7i64)
            .unwrap()
            .attr_array("scale", &[1.0f64, 2.0])
            .unwrap()
            .write::<i32>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        assert_dataset_values::<i32>(&ds, &data).unwrap();
        assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(7));
        assert_attribute_values::<f64>(&ds.attr("scale").unwrap(), &[1.0, 2.0]).unwrap();
    }

    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (16,)\n\
         assert d.chunks == (4,)\n\
         assert d[:].tolist() == list(range(16))\n\
         assert d.attrs['version'] == 7\n\
         assert d.attrs['scale'].tolist() == [1.0, 2.0]",
        "chunked dataset attribute writer fixture",
    );
}

#[test]
fn test_dataset_builder_rejects_invalid_chunk_dimensions() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_invalid_chunk_dims.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let zero_err = wf
            .new_dataset_builder("zero_chunk")
            .shape(&[4])
            .chunk(&[0])
            .write::<i32>(&[1, 2, 3, 4])
            .expect_err("zero chunk dimension should be rejected");
        assert!(zero_err.to_string().contains("chunk dimension 0 is zero"));

        let rank_err = wf
            .new_dataset_builder("rank_mismatch")
            .shape(&[2, 2])
            .chunk(&[2])
            .write::<i32>(&[1, 2, 3, 4])
            .expect_err("chunk rank mismatch should be rejected");
        assert!(rank_err.to_string().contains("chunk dimension rank"));

        let huge_chunk_err = wf
            .new_dataset_builder("huge_chunk")
            .shape(&[1])
            .chunk(&[u32::MAX as u64 + 1])
            .write::<u8>(&[0])
            .expect_err("chunk dimensions must fit v3 layout's 32-bit fields");
        assert!(huge_chunk_err.to_string().contains("32-bit layout field"));
    }
}

#[test]
fn test_dataset_builder_rejects_invalid_deflate_level() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_invalid_deflate_level.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let err = wf
            .new_dataset_builder("bad_deflate")
            .shape(&[4])
            .chunk(&[2])
            .deflate(10)
            .write::<i32>(&[1, 2, 3, 4])
            .expect_err("deflate level above 9 should be rejected");
        assert!(err.to_string().contains("deflate compression level 10"));
    }
}

#[test]
fn test_dataset_builder_compressed_chunked_attrs_with_fill_value() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_compressed_chunked_attrs_with_fill.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data: Vec<i16> = (0..30).collect();
        wf.new_dataset_builder("data")
            .shape(&[30])
            .chunk(&[8])
            .shuffle()
            .deflate(3)
            .fill_value::<i16>(-9)
            .attr("version", 8i64)
            .unwrap()
            .write::<i16>(&data)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        assert_dataset_values::<i16>(&ds, &data).unwrap();
        assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(8));
        let plist = ds.create_plist().unwrap();
        assert_eq!(plist.fill_value, Some((-9i16).to_le_bytes().to_vec()));
    }

    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (30,)\n\
         assert d.chunks == (8,)\n\
         assert d.compression == 'gzip'\n\
         assert d.compression_opts == 3\n\
         assert d.shuffle\n\
         assert d.fillvalue == -9\n\
         assert d.attrs['version'] == 8\n\
         assert d[:].tolist() == list(range(30))",
        "compressed chunked attribute and fill-value writer fixture",
    );
}

#[test]
fn test_dataset_builder_sparse_chunked_fill_only_dataset() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_sparse_chunked_fill_only.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("data")
            .shape(&[1_000])
            .chunk(&[128])
            .fill_value::<i32>(-11)
            .attr("version", 14i64)
            .unwrap()
            .write_fill::<i32>()
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(14));
        let values: Vec<i32> = ds.read().unwrap();
        assert_eq!(values.len(), 1_000);
        assert!(values.iter().all(|&value| value == -11));
        let plist = ds.create_plist().unwrap();
        assert_eq!(plist.fill_value, Some((-11i32).to_le_bytes().to_vec()));
    }

    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (1000,)\n\
         assert d.chunks == (128,)\n\
         assert d.fillvalue == -11\n\
         assert d.attrs['version'] == 14\n\
         assert d[:10].tolist() == [-11] * 10\n\
         assert d[990:].tolist() == [-11] * 10",
        "sparse chunked fill-only writer fixture",
    );
}

#[test]
fn test_dataset_builder_sparse_max_shape_fill_only_uses_modern_indexes() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_sparse_max_shape_fill_only_modern.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("single_grow")
            .shape(&[12])
            .max_shape(&[24])
            .chunk(&[4])
            .fill_value::<i16>(-3)
            .write_fill::<i16>()
            .unwrap();
        wf.new_dataset_builder("multi_grow")
            .shape(&[4, 4])
            .max_shape(&[8, 8])
            .chunk(&[2, 2])
            .fill_value::<i32>(-8)
            .write_fill::<i32>()
            .unwrap();
        let f = wf.close().unwrap();

        let single = f.dataset("single_grow").unwrap();
        let single_info = single.info().unwrap();
        assert_eq!(single_info.layout.version, 4);
        assert_eq!(
            single_info.layout.chunk_index_type,
            Some(ChunkIndexType::BTreeV2)
        );
        let single_values: Vec<i16> = single.read().unwrap();
        assert_eq!(single_values, vec![-3; 12]);

        let multi = f.dataset("multi_grow").unwrap();
        let multi_info = multi.info().unwrap();
        assert_eq!(multi_info.layout.version, 4);
        assert_eq!(
            multi_info.layout.chunk_index_type,
            Some(ChunkIndexType::BTreeV2)
        );
        let multi_values: Vec<i32> = multi.read().unwrap();
        assert_eq!(multi_values, vec![-8; 16]);
    }

    assert_h5dump_dataset_read(
        &path,
        "single_grow",
        "single grow fill-only B-tree v2 fixture",
    );
    assert_h5dump_dataset_read(
        &path,
        "multi_grow",
        "multi grow fill-only B-tree v2 fixture",
    );
    assert_h5py_script(
        &path,
        "single = f['single_grow']\n\
         assert single.shape == (12,)\n\
         assert single.maxshape == (24,)\n\
         assert single.chunks == (4,)\n\
         assert single.fillvalue == -3\n\
         assert single[:].tolist() == [-3] * 12\n\
         multi = f['multi_grow']\n\
         assert multi.shape == (4, 4)\n\
         assert multi.maxshape == (8, 8)\n\
         assert multi.chunks == (2, 2)\n\
         assert multi.fillvalue == -8\n\
         assert multi[:].tolist() == [[-8] * 4 for _ in range(4)]",
        "fill-only growable B-tree v2 writer fixtures",
    );
}

#[test]
fn test_dataset_builder_sparse_chunked_explicit_chunks() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_sparse_chunked_explicit_chunks.h5");
    let first = [1i32; 128];
    let middle = [5i32; 128];

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("data")
            .shape(&[1_000])
            .chunk(&[128])
            .fill_value::<i32>(-7)
            .deflate(1)
            .write_chunks::<i32, _, _>([([0u64], &first[..]), ([512u64], &middle[..])])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        let values: Vec<i32> = ds.read().unwrap();
        assert_eq!(values.len(), 1_000);
        assert_eq!(&values[..128], &first);
        assert_eq!(&values[512..640], &middle);
        assert!(values[128..512].iter().all(|&value| value == -7));
        assert!(values[640..].iter().all(|&value| value == -7));
        assert!(dataset_has_filter_id(&ds, 1).unwrap());
    }
}

#[test]
fn test_dataset_builder_sparse_unfiltered_explicit_chunks_use_fixed_array() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_sparse_unfiltered_explicit_chunks_fa.h5");
    let first = [2u16; 8];
    let last = [7u16; 8];

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("data")
            .shape(&[32])
            .chunk(&[8])
            .fill_value::<u16>(99)
            .write_chunks::<u16, _, _>([([0u64], &first[..]), ([24u64], &last[..])])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        let info = ds.info().unwrap();
        assert_eq!(
            info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        let values: Vec<u16> = ds.read().unwrap();
        assert_eq!(values.len(), 32);
        assert_eq!(&values[..8], &first);
        assert!(values[8..24].iter().all(|&value| value == 99));
        assert_eq!(&values[24..], &last);
    }

    assert_h5dump_dataset_read(
        &path,
        "data",
        "sparse unfiltered fixed-array explicit-chunk fixture",
    );
    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (32,)\n\
         assert d.chunks == (8,)\n\
         assert d.dtype.kind == 'u'\n\
         assert d.dtype.itemsize == 2\n\
         assert d.fillvalue == 99\n\
         assert d[:].tolist() == [2] * 8 + [99] * 16 + [7] * 8",
        "sparse unfiltered fixed-array explicit-chunk fixture",
    );
}

#[test]
fn test_dataset_builder_sparse_filtered_explicit_chunks_use_fixed_array() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_sparse_filtered_explicit_chunks_fa.h5");
    let first = [4u16; 8];
    let last = [11u16; 8];

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("data")
            .shape(&[32])
            .chunk(&[8])
            .fill_value::<u16>(99)
            .deflate(1)
            .write_chunks::<u16, _, _>([([0u64], &first[..]), ([24u64], &last[..])])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        let info = ds.info().unwrap();
        assert_eq!(
            info.layout.chunk_index_type,
            Some(ChunkIndexType::FixedArray)
        );
        assert!(dataset_has_filter_id(&ds, 1).unwrap());
        let values: Vec<u16> = ds.read().unwrap();
        assert_eq!(values.len(), 32);
        assert_eq!(&values[..8], &first);
        assert!(values[8..24].iter().all(|&value| value == 99));
        assert_eq!(&values[24..], &last);
    }

    assert_h5dump_dataset_read(
        &path,
        "data",
        "sparse filtered fixed-array explicit-chunk fixture",
    );
    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (32,)\n\
         assert d.chunks == (8,)\n\
         assert d.compression == 'gzip'\n\
         assert d.compression_opts == 1\n\
         assert d.dtype.kind == 'u'\n\
         assert d.dtype.itemsize == 2\n\
         assert d.fillvalue == 99\n\
         assert d[:].tolist() == [4] * 8 + [99] * 16 + [11] * 8",
        "sparse filtered fixed-array explicit-chunk fixture",
    );
}

#[test]
fn test_dataset_builder_sparse_chunked_explicit_chunks_with_max_shape_uses_btree_v2() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_sparse_chunked_explicit_chunks_btree_v2.h5");
    let first = [3i16; 4];
    let last = [9i16; 4];

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("data")
            .shape(&[16])
            .max_shape(&[32])
            .chunk(&[4])
            .fill_value::<i16>(-2)
            .write_chunks::<i16, _, _>([([0u64], &first[..]), ([12u64], &last[..])])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        let info = ds.info().unwrap();
        assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
        let values: Vec<i16> = ds.read().unwrap();
        assert_eq!(values.len(), 16);
        assert_eq!(&values[..4], &first);
        assert!(values[4..12].iter().all(|&value| value == -2));
        assert_eq!(&values[12..], &last);
    }

    assert_h5dump_dataset_read(
        &path,
        "data",
        "sparse unfiltered B-tree v2 explicit-chunk fixture",
    );
    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (16,)\n\
         assert d.maxshape == (32,)\n\
         assert d.chunks == (4,)\n\
         assert d.dtype.kind == 'i'\n\
         assert d.dtype.itemsize == 2\n\
         assert d.fillvalue == -2\n\
         assert d[:].tolist() == [3] * 4 + [-2] * 8 + [9] * 4",
        "sparse unfiltered B-tree v2 explicit-chunk fixture",
    );
}

#[test]
fn test_dataset_builder_sparse_filtered_explicit_chunks_with_max_shape_uses_btree_v2() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_sparse_filtered_explicit_chunks_btree_v2.h5");
    let first = [6i16; 4];
    let last = [12i16; 4];

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("data")
            .shape(&[16])
            .max_shape(&[32])
            .chunk(&[4])
            .fill_value::<i16>(-2)
            .deflate(1)
            .write_chunks::<i16, _, _>([([0u64], &first[..]), ([12u64], &last[..])])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        assert!(ds.is_chunked().unwrap());
        let info = ds.info().unwrap();
        assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
        assert!(dataset_has_filter_id(&ds, 1).unwrap());
        let values: Vec<i16> = ds.read().unwrap();
        assert_eq!(values.len(), 16);
        assert_eq!(&values[..4], &first);
        assert!(values[4..12].iter().all(|&value| value == -2));
        assert_eq!(&values[12..], &last);
    }

    assert_h5dump_dataset_read(
        &path,
        "data",
        "sparse filtered B-tree v2 explicit-chunk fixture",
    );
    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (16,)\n\
         assert d.maxshape == (32,)\n\
         assert d.chunks == (4,)\n\
         assert d.compression == 'gzip'\n\
         assert d.compression_opts == 1\n\
         assert d.dtype.kind == 'i'\n\
         assert d.dtype.itemsize == 2\n\
         assert d.fillvalue == -2\n\
         assert d[:].tolist() == [6] * 4 + [-2] * 8 + [12] * 4",
        "sparse filtered B-tree v2 explicit-chunk fixture",
    );
}

#[test]
fn test_dataset_builder_sparse_multidim_growable_chunks_use_btree_v2() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_sparse_multidim_growable_chunks_btree_v2.h5");
    let first = [10i32, 11, 12, 13];
    let last = [30i32, 31, 32, 33];

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("data")
            .shape(&[4, 4])
            .max_shape(&[8, 8])
            .chunk(&[2, 2])
            .fill_value::<i32>(-5)
            .write_chunks::<i32, _, _>([([0u64, 0u64], &first[..]), ([2u64, 2u64], &last[..])])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        let info = ds.info().unwrap();
        assert_eq!(info.layout.version, 4);
        assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
        let values: Vec<i32> = ds.read().unwrap();
        assert_eq!(
            values,
            vec![10, 11, -5, -5, 12, 13, -5, -5, -5, -5, 30, 31, -5, -5, 32, 33,]
        );
    }

    assert_h5dump_dataset_read(
        &path,
        "data",
        "sparse multidimensional growable B-tree v2 fixture",
    );
    assert_h5py_script(
        &path,
        "d = f['data']\n\
         assert d.shape == (4, 4)\n\
         assert d.maxshape == (8, 8)\n\
         assert d.chunks == (2, 2)\n\
         assert d.dtype.kind == 'i'\n\
         assert d.dtype.itemsize == 4\n\
         assert int(d[1, 2]) == -5\n\
         assert d[:].tolist() == [[10, 11, -5, -5], [12, 13, -5, -5], [-5, -5, 30, 31], [-5, -5, 32, 33]]",
        "sparse multidimensional growable B-tree v2 fixture",
    );
}

#[test]
fn test_dataset_builder_sparse_explicit_chunks_large_max_shape_uses_btree_v2() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_sparse_explicit_chunks_btree_v2.h5");
    let first = [5u8];
    let far = [23u8];

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("data")
            .shape(&[1_000_001])
            .max_shape(&[2_000_000])
            .chunk(&[1])
            .fill_value::<u8>(9)
            .write_chunks::<u8, _, _>([([0u64], &first[..]), ([1_000_000u64], &far[..])])
            .unwrap();
        wf.new_dataset_builder("filtered")
            .shape(&[1_000_001])
            .max_shape(&[2_000_000])
            .chunk(&[1])
            .fill_value::<u8>(7)
            .deflate(1)
            .write_chunks::<u8, _, _>([([0u64], &first[..]), ([1_000_000u64], &far[..])])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("data").unwrap();
        let info = ds.info().unwrap();
        assert_eq!(info.layout.version, 4);
        assert_eq!(info.layout.chunk_index_type, Some(ChunkIndexType::BTreeV2));
        let values: Vec<u8> = ds.read().unwrap();
        assert_eq!(values.len(), 1_000_001);
        assert_eq!(values[0], 5);
        assert_eq!(values[1], 9);
        assert_eq!(values[999_999], 9);
        assert_eq!(values[1_000_000], 23);

        let filtered = f.dataset("filtered").unwrap();
        let filtered_info = filtered.info().unwrap();
        assert_eq!(filtered_info.layout.version, 4);
        assert_eq!(
            filtered_info.layout.chunk_index_type,
            Some(ChunkIndexType::BTreeV2)
        );
        assert!(dataset_has_filter_id(&filtered, 1).unwrap());
        let filtered_values: Vec<u8> = filtered.read().unwrap();
        assert_eq!(filtered_values.len(), 1_000_001);
        assert_eq!(filtered_values[0], 5);
        assert_eq!(filtered_values[1], 7);
        assert_eq!(filtered_values[999_999], 7);
        assert_eq!(filtered_values[1_000_000], 23);
    }

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-pH")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -pH failed or timed out on large sparse B-tree v2 explicit-chunk fixtures: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("data"));
        assert!(stdout.contains("filtered"));
        assert!(stdout.contains("CHUNKED"));
        assert!(stdout.contains("DEFLATE"));
    }
    assert_h5py_script(
        &path,
        "data = f['data']\n\
         assert data.shape == (1000001,)\n\
         assert data.maxshape == (2000000,)\n\
         assert data.chunks == (1,)\n\
         assert data[0:3].tolist() == [5, 9, 9]\n\
         assert data[999998:].tolist() == [9, 9, 23]\n\
         filtered = f['filtered']\n\
         assert filtered.shape == (1000001,)\n\
         assert filtered.maxshape == (2000000,)\n\
         assert filtered.chunks == (1,)\n\
         assert filtered.compression == 'gzip'\n\
         assert filtered[0:3].tolist() == [5, 7, 7]\n\
         assert filtered[999998:].tolist() == [7, 7, 23]",
        "large sparse B-tree v2 explicit-chunk fixtures",
    );
}

#[test]
fn test_writable_file_scalar() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_scalar.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("pi")
            .write_scalar::<f64>(std::f64::consts::PI)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("pi").unwrap();
        let val = dataset_scalar::<f64>(&ds).unwrap();
        assert!((val - std::f64::consts::PI).abs() < 1e-15);
    }

    assert_h5py_script(
        &path,
        "d = f['pi']\n\
         assert d.shape == ()\n\
         assert abs(float(d[()]) - 3.141592653589793) < 1e-15",
        "scalar dataset fixture",
    );
}

#[test]
fn test_dataset_builder_compact_scalar_and_rejected_scalar_options() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_scalar_options.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("compact_scalar")
            .compact()
            .attr("version", 13i64)
            .unwrap()
            .write_scalar::<i32>(99)
            .unwrap();

        let chunked_err = wf
            .new_dataset_builder("bad_chunked_scalar")
            .chunk(&[1])
            .write_scalar::<i32>(1)
            .expect_err("scalar chunked storage should be rejected");
        assert!(chunked_err.to_string().contains("scalar dataset writer"));

        let max_shape_err = wf
            .new_dataset_builder("bad_max_scalar")
            .max_shape(&[1])
            .write_scalar::<i32>(1)
            .expect_err("scalar max dimensions should be rejected");
        assert!(max_shape_err.to_string().contains("max dimensions"));

        let f = wf.close().unwrap();
        let ds = f.dataset("compact_scalar").unwrap();
        assert_eq!(dataset_scalar::<i32>(&ds).unwrap(), 99);
        assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(13));
    }
}

#[test]
fn test_writable_file_compact() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_compact.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("tiny")
            .compact()
            .write::<u8>(&[1, 2, 3, 4, 5])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("tiny").unwrap();
        assert_dataset_values::<u8>(&ds, &[1, 2, 3, 4, 5]).unwrap();
    }
}

#[test]
fn test_dataset_builder_rejects_invalid_compact_options() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_compact_option_errors.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let chunked_err = wf
            .new_dataset_builder("bad_chunked_compact")
            .compact()
            .chunk(&[1])
            .write::<u8>(&[1, 2, 3])
            .expect_err("compact plus chunked storage should be rejected");
        assert!(chunked_err.to_string().contains("compact dataset writer"));

        let max_shape_err = wf
            .new_dataset_builder("bad_max_compact")
            .compact()
            .max_shape(&[8])
            .write::<u8>(&[1, 2, 3])
            .expect_err("compact plus max dimensions should be rejected");
        assert!(max_shape_err.to_string().contains("max dimensions"));
    }
}

#[test]
fn test_dataset_builder_rejects_oversized_compact_payload() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_compact_too_large.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let data = vec![0u8; u16::MAX as usize + 1];
        let err = wf
            .new_dataset_builder("too_large")
            .compact()
            .write::<u8>(&data)
            .expect_err("compact payload larger than u16 should be rejected");
        assert!(err.to_string().contains("compact dataset payload"));
    }
}

#[test]
fn test_writable_file_explicit_fill_value_properties() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_fill_value.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("with_fill")
            .fill_properties(1, 2)
            .fill_value::<i32>(-7)
            .write::<i32>(&[1, 2, 3])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("with_fill").unwrap();
        let plist = ds.create_plist().unwrap();
        assert_eq!(plist.fill_alloc_time, Some(1));
        assert_eq!(plist.fill_time, Some(2));
        assert!(plist.fill_value_defined);
        assert_eq!(plist.fill_value, Some((-7i32).to_le_bytes().to_vec()));
        assert_dataset_values::<i32>(&ds, &[1, 2, 3]).unwrap();
    }
}

#[test]
fn test_writable_file_compact_fixed_strings() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_compact_strings.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("names")
            .compact()
            .write_fixed_ascii_strings(&["red", "green", "blue"], 8)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("names").unwrap();
        assert_dataset_strings(&ds, &["red", "green", "blue"]).unwrap();
    }
}

#[test]
fn test_dataset_builder_fixed_string_datasets_with_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_fixed_string_dataset_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("contiguous")
            .attr("version", 10i64)
            .unwrap()
            .write_fixed_ascii_strings(&["red", "green"], 8)
            .unwrap();
        wf.new_dataset_builder("compact")
            .compact()
            .attr("version", 11i64)
            .unwrap()
            .write_fixed_utf8_strings(&["猫", "å"], 8)
            .unwrap();
        let f = wf.close().unwrap();

        let contiguous = f.dataset("contiguous").unwrap();
        assert_dataset_strings(&contiguous, &["red", "green"]).unwrap();
        assert_eq!(
            contiguous.attr("version").unwrap().read_scalar_i64(),
            Some(10)
        );

        let compact = f.dataset("compact").unwrap();
        assert_dataset_strings(&compact, &["猫", "å"]).unwrap();
        assert_eq!(compact.attr("version").unwrap().read_scalar_i64(), Some(11));
    }
}

#[test]
fn test_dataset_builder_chunked_fixed_string_dataset() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_chunked_fixed_strings.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("names")
            .shape(&[4])
            .chunk(&[2])
            .shuffle()
            .deflate(3)
            .attr("version", 12i64)
            .unwrap()
            .write_fixed_ascii_strings(&["red", "green", "blue", "gold"], 8)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("names").unwrap();
        assert!(ds.is_chunked().unwrap());
        assert_dataset_strings(&ds, &["red", "green", "blue", "gold"]).unwrap();
        assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(12));
    }

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-pH")
        .arg("-d")
        .arg("names")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -pH failed or timed out on chunked fixed string fixture: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("STRSIZE 8"));
        assert!(stdout.contains("CHUNKED"));
        assert!(stdout.contains("DEFLATE"));
        assert!(stdout.contains("SHUFFLE"));
    }

    assert_h5py_script(
        &path,
        "d = f['names']\n\
         assert d.shape == (4,)\n\
         assert d.chunks == (2,)\n\
         assert d.compression == 'gzip'\n\
         assert d.compression_opts == 3\n\
         assert d.shuffle\n\
         assert [value.decode('ascii').rstrip('\\x00') for value in d[:].tolist()] == ['red', 'green', 'blue', 'gold']\n\
         assert d.attrs['version'] == 12",
        "chunked fixed ASCII string fixture",
    );
}

#[test]
fn test_writable_file_vlen_utf8_strings() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_vlen_strings.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("names")
            .write_vlen_utf8_strings(&["", "猫", "alpha"])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("names").unwrap();
        assert!(ds.dtype().unwrap().is_vlen());
        assert_dataset_strings(&ds, &["", "猫", "alpha"]).unwrap();
    }

    let out = std::process::Command::new("h5dump")
        .arg("-H")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump failed on vlen string writer fixture: {}",
            String::from_utf8_lossy(&out.stderr)
        );
    }

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-d")
        .arg("names")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -d failed or timed out on vlen string writer fixture: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("alpha"));
        assert!(stdout.contains("STRSIZE H5T_VARIABLE"));
    }

    assert_h5py_script(
        &path,
        "d = f['names']\n\
         import h5py\n\
         assert d.shape == (3,)\n\
         assert h5py.check_string_dtype(d.dtype).encoding == 'utf-8'\n\
         assert [value.decode('utf-8') for value in d[:].tolist()] == ['', '猫', 'alpha']",
        "contiguous vlen UTF-8 string fixture",
    );
}

#[test]
fn test_writable_file_chunked_filtered_vlen_utf8_strings() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_chunked_vlen_strings.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("names")
            .shape(&[5])
            .chunk(&[2])
            .shuffle()
            .deflate(3)
            .attr("version", 13i64)
            .unwrap()
            .write_vlen_utf8_strings(&["", "猫", "alpha", "beta", "delta"])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("names").unwrap();
        assert!(ds.dtype().unwrap().is_vlen());
        assert!(ds.is_chunked().unwrap());
        assert!(dataset_has_filter_id(&ds, 1).unwrap());
        assert!(dataset_has_filter_id(&ds, 2).unwrap());
        assert_dataset_strings(&ds, &["", "猫", "alpha", "beta", "delta"]).unwrap();
        assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(13));
    }

    let out = std::process::Command::new("timeout")
        .arg("10")
        .arg("h5dump")
        .arg("-pH")
        .arg("-d")
        .arg("names")
        .arg(&path)
        .output();
    if let Ok(out) = out {
        assert!(
            out.status.success(),
            "h5dump -pH failed or timed out on chunked vlen string fixture: status={:?}, stderr={}",
            out.status.code(),
            String::from_utf8_lossy(&out.stderr)
        );
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(stdout.contains("STRSIZE H5T_VARIABLE"));
        assert!(stdout.contains("CHUNKED"));
        assert!(stdout.contains("DEFLATE"));
        assert!(stdout.contains("SHUFFLE"));
    }

    assert_h5py_script(
        &path,
        "d = f['names']\n\
         assert d.shape == (5,)\n\
         assert d.chunks == (2,)\n\
         assert d.compression == 'gzip'\n\
         assert d.compression_opts == 3\n\
         assert d.shuffle\n\
         assert [value.decode('utf-8') for value in d[:].tolist()] == ['', '猫', 'alpha', 'beta', 'delta']\n\
         assert d.attrs['version'] == 13",
        "chunked filtered vlen UTF-8 string fixture",
    );
}

#[test]
fn test_writable_file_chunked_vlen_utf8_strings_with_fletcher32() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir
        .path()
        .join("api_write_chunked_vlen_strings_fletcher32.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("names")
            .shape(&[3])
            .chunk(&[1])
            .fletcher32()
            .write_vlen_utf8_strings(&["alpha", "", "猫"])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("names").unwrap();
        assert!(ds.dtype().unwrap().is_vlen());
        assert!(ds.is_chunked().unwrap());
        assert!(dataset_has_filter_id(&ds, 3).unwrap());
        assert_dataset_strings(&ds, &["alpha", "", "猫"]).unwrap();
    }

    assert_h5dump_dataset_read(
        &path,
        "names",
        "chunked VLEN UTF-8 Fletcher32 string fixture",
    );
    assert_h5py_script(
        &path,
        "d = f['names']\n\
         assert d.shape == (3,)\n\
         assert d.chunks == (1,)\n\
         assert d.fletcher32 is True\n\
         assert [value.decode('utf-8') for value in d[:].tolist()] == ['alpha', '', '猫']",
        "chunked VLEN UTF-8 Fletcher32 string fixture",
    );
}

#[test]
fn test_writable_file_vlen_utf8_strings_accept_fill_values() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_vlen_string_fill.h5");
    let empty_vlen_descriptor = 0u128.to_le_bytes().to_vec();

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("contiguous")
            .shape(&[2])
            .fill_value::<u128>(0)
            .write_vlen_utf8_strings(&["alpha", "beta"])
            .unwrap();
        wf.new_dataset_builder("chunked")
            .shape(&[3])
            .chunk(&[1])
            .fill_value::<u128>(0)
            .write_vlen_utf8_strings(&["", "猫", "gamma"])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("contiguous").unwrap();
        assert!(ds.dtype().unwrap().is_vlen());
        assert_dataset_strings(&ds, &["alpha", "beta"]).unwrap();
        let plist = ds.create_plist().unwrap();
        assert!(plist.fill_value_defined);
        assert_eq!(plist.fill_value, Some(empty_vlen_descriptor.clone()));

        let ds = f.dataset("chunked").unwrap();
        assert!(ds.dtype().unwrap().is_vlen());
        assert!(ds.is_chunked().unwrap());
        assert_dataset_strings(&ds, &["", "猫", "gamma"]).unwrap();
        let plist = ds.create_plist().unwrap();
        assert!(plist.fill_value_defined);
        assert_eq!(plist.fill_value, Some(empty_vlen_descriptor));
    }

    assert_h5py_script(
        &path,
        "contiguous = f['contiguous']\n\
         assert h5py.check_string_dtype(contiguous.dtype).encoding == 'utf-8'\n\
         assert [value.decode('utf-8') for value in contiguous[:].tolist()] == ['alpha', 'beta']\n\
         assert contiguous.fillvalue == b''\n\
         chunked = f['chunked']\n\
         assert h5py.check_string_dtype(chunked.dtype).encoding == 'utf-8'\n\
         assert chunked.chunks == (1,)\n\
         assert [value.decode('utf-8') for value in chunked[:].tolist()] == ['', '猫', 'gamma']\n\
         assert chunked.fillvalue == b''",
        "VLEN UTF-8 zero-descriptor fill-value writer fixture",
    );
}

#[test]
fn test_writable_file_vlen_utf8_strings_encode_string_fill_value() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_vlen_string_text_fill.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("contiguous")
            .shape(&[2])
            .vlen_utf8_fill_value("fallback")
            .write_vlen_utf8_strings(&["alpha", "beta"])
            .unwrap();
        wf.new_dataset_builder("chunked")
            .shape(&[3])
            .chunk(&[1])
            .vlen_utf8_fill_value("missing")
            .write_vlen_utf8_strings(&["", "猫", "gamma"])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("contiguous").unwrap();
        assert_dataset_strings(&ds, &["alpha", "beta"]).unwrap();
        let plist = ds.create_plist().unwrap();
        let fill = plist
            .fill_value
            .expect("vlen string fill should be encoded");
        assert_eq!(fill.len(), 16);
        assert_eq!(u32::from_le_bytes(fill[..4].try_into().unwrap()), 8);
        assert_ne!(&fill[4..12], &[0; 8]);
        assert_eq!(u32::from_le_bytes(fill[12..16].try_into().unwrap()), 1);

        let ds = f.dataset("chunked").unwrap();
        assert!(ds.is_chunked().unwrap());
        assert_dataset_strings(&ds, &["", "猫", "gamma"]).unwrap();
        let plist = ds.create_plist().unwrap();
        let fill = plist
            .fill_value
            .expect("chunked vlen string fill should be encoded");
        assert_eq!(fill.len(), 16);
        assert_eq!(u32::from_le_bytes(fill[..4].try_into().unwrap()), 7);
        assert_ne!(&fill[4..12], &[0; 8]);
        assert_eq!(u32::from_le_bytes(fill[12..16].try_into().unwrap()), 1);
    }

    assert_h5py_script(
        &path,
        "contiguous = f['contiguous']\n\
         assert h5py.check_string_dtype(contiguous.dtype).encoding == 'utf-8'\n\
         assert [value.decode('utf-8') for value in contiguous[:].tolist()] == ['alpha', 'beta']\n\
         assert contiguous.fillvalue == b'fallback'\n\
         chunked = f['chunked']\n\
         assert h5py.check_string_dtype(chunked.dtype).encoding == 'utf-8'\n\
         assert chunked.chunks == (1,)\n\
         assert [value.decode('utf-8') for value in chunked[:].tolist()] == ['', '猫', 'gamma']\n\
         assert chunked.fillvalue == b'missing'",
        "VLEN UTF-8 string fill-value writer fixture",
    );
}

#[test]
fn test_writable_file_vlen_utf8_strings_split_global_heap_collections() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_many_vlen_strings.h5");
    let values: Vec<String> = (0..=u16::MAX).map(|i| format!("kmer-{i}")).collect();
    let refs: Vec<&str> = values.iter().map(String::as_str).collect();

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("kmers")
            .write_vlen_utf8_strings(&refs)
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("kmers").unwrap();
        assert!(ds.dtype().unwrap().is_vlen());
        let strings = ds.read_strings().unwrap();
        assert_eq!(strings.len(), refs.len());
        assert_eq!(strings.first().map(String::as_str), Some("kmer-0"));
        assert_eq!(
            strings.get(u16::MAX as usize).map(String::as_str),
            Some("kmer-65535")
        );
    }
}

#[test]
fn test_dataset_builder_vlen_utf8_strings_with_attrs() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_vlen_string_attrs.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("names")
            .attr("version", 9i64)
            .unwrap()
            .fixed_utf8_attr("label", "猫", 8)
            .unwrap()
            .write_vlen_utf8_strings(&["", "alpha", "猫"])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("names").unwrap();
        assert!(ds.dtype().unwrap().is_vlen());
        assert_dataset_strings(&ds, &["", "alpha", "猫"]).unwrap();
        assert_eq!(ds.attr("version").unwrap().read_scalar_i64(), Some(9));
        assert_eq!(attribute_string(&ds.attr("label").unwrap()).unwrap(), "猫");
    }
}

#[test]
fn test_writable_file_compact_compound() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_compact_compound.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("points")
            .compact()
            .write::<Point>(&[Point { x: 1.5, label: 10 }, Point { x: 2.5, label: 20 }])
            .unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("points").unwrap();
        assert_dataset_field::<f64>(&ds, "x", &[1.5, 2.5]).unwrap();
        assert_dataset_field::<i32>(&ds, "label", &[10, 20]).unwrap();
    }
}

#[test]
fn test_dataset_builder_write_raw_with_explicit_complex_dtype() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_raw_complex_dtype.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let enum_data: Vec<u8> = [0u16, 1, 2]
            .into_iter()
            .flat_map(u16::to_le_bytes)
            .collect();
        wf.new_dataset_builder("status")
            .write_raw_with_dtype(
                DtypeSpec::Enum {
                    base: Box::new(DtypeSpec::U16),
                    members: vec![
                        ("zero".to_string(), 0),
                        ("one".to_string(), 1),
                        ("two".to_string(), 2),
                    ],
                },
                &enum_data,
            )
            .unwrap();

        wf.new_dataset_builder("opaque")
            .shape(&[2])
            .write_raw_with_dtype(
                DtypeSpec::Opaque {
                    size: 4,
                    tag: "payload".to_string(),
                },
                b"abcdwxyz",
            )
            .unwrap();

        let array_data: Vec<u8> = (0i16..12).flat_map(i16::to_le_bytes).collect();
        wf.new_dataset_builder("cells")
            .shape(&[2])
            .write_raw_with_dtype(
                DtypeSpec::Array {
                    dims: vec![2, 3],
                    base: Box::new(DtypeSpec::I16),
                },
                &array_data,
            )
            .unwrap();

        let nested = DtypeSpec::Compound {
            size: 8,
            fields: vec![
                CompoundFieldSpec {
                    name: "a".to_string(),
                    offset: 0,
                    dtype: DtypeSpec::I32,
                },
                CompoundFieldSpec {
                    name: "b".to_string(),
                    offset: 4,
                    dtype: DtypeSpec::F32,
                },
            ],
        };
        let nested_compound = DtypeSpec::Compound {
            size: 16,
            fields: vec![
                CompoundFieldSpec {
                    name: "id".to_string(),
                    offset: 0,
                    dtype: DtypeSpec::I32,
                },
                CompoundFieldSpec {
                    name: "nested".to_string(),
                    offset: 8,
                    dtype: nested,
                },
            ],
        };
        let mut compound_data = Vec::new();
        for (id, a, b) in [(7i32, 10i32, 1.25f32), (8, 20, 2.5)] {
            compound_data.extend_from_slice(&id.to_le_bytes());
            compound_data.extend_from_slice(&[0; 4]);
            compound_data.extend_from_slice(&a.to_le_bytes());
            compound_data.extend_from_slice(&b.to_le_bytes());
        }
        wf.new_dataset_builder("nested")
            .shape(&[2])
            .write_raw_with_dtype(nested_compound, &compound_data)
            .unwrap();

        let f = wf.close().unwrap();

        let status = f.dataset("status").unwrap();
        assert!(status.dtype().unwrap().is_enum());
        assert_dataset_values::<u16>(&status, &[0, 1, 2]).unwrap();

        let opaque = f.dataset("opaque").unwrap();
        assert_eq!(opaque.dtype().unwrap().opaque_tag_str(), Some("payload"));
        assert_dataset_raw(&opaque, b"abcdwxyz").unwrap();

        let cells = f.dataset("cells").unwrap();
        let cells_dtype = cells.dtype().unwrap();
        let dims = cells_dtype
            .array_dims_iter()
            .unwrap()
            .collect::<Result<Vec<_>>>()
            .unwrap();
        let base = cells_dtype.array_base().unwrap();
        assert_eq!(dims, vec![2, 3]);
        assert_eq!(base.size(), 2);
        assert_dataset_raw(&cells, &array_data).unwrap();

        let nested = f.dataset("nested").unwrap();
        assert_eq!(dataset_field_value_count(&nested, "nested").unwrap(), 2);
    }
}

#[test]
fn test_dataset_builder_rejects_unsupported_enum_base_types() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_bad_enum_base.h5");
    let mut wf = WritableFile::create(&path).unwrap();

    let err = wf
        .new_dataset_builder("wide_enum")
        .write_raw_with_dtype(
            DtypeSpec::Enum {
                base: Box::new(DtypeSpec::U128),
                members: vec![("wide".to_string(), 1)],
            },
            &1u128.to_le_bytes(),
        )
        .expect_err("16-byte enum base should be rejected until member values are widened");
    assert!(err
        .to_string()
        .contains("enum writer supports only integer base datatypes up to 8 bytes"));

    let err = wf
        .new_dataset_builder("float_enum")
        .write_raw_with_dtype(
            DtypeSpec::Enum {
                base: Box::new(DtypeSpec::F32),
                members: vec![("bad".to_string(), 1)],
            },
            &1f32.to_le_bytes(),
        )
        .expect_err("non-integer enum base should be rejected");
    assert!(err
        .to_string()
        .contains("enum writer supports only integer base datatypes up to 8 bytes"));
}

#[test]
fn test_dataset_builder_rejects_unencodable_explicit_dtype() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_bad_explicit_dtype.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let array_err = wf
            .new_dataset_builder("bad_array")
            .write_raw_with_dtype(
                DtypeSpec::Array {
                    dims: vec![1; u8::MAX as usize + 1],
                    base: Box::new(DtypeSpec::U8),
                },
                &[0],
            )
            .expect_err("array datatype rank should fit in the encoded rank byte");
        assert!(array_err.to_string().contains("array datatype rank"));

        let opaque_err = wf
            .new_dataset_builder("bad_opaque")
            .write_raw_with_dtype(
                DtypeSpec::Opaque {
                    size: 1,
                    tag: "x".repeat(u8::MAX as usize),
                },
                &[0],
            )
            .expect_err("opaque tag length should fit in the encoded tag-length byte");
        assert!(opaque_err.to_string().contains("opaque tag"));
    }
}

#[test]
fn test_writable_file_h5dump_interop() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_h5dump.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.new_dataset_builder("values")
            .write::<f64>(&[1.0, 2.0, 3.0])
            .unwrap();
        wf.flush().unwrap();
    }

    let out = std::process::Command::new("h5dump").arg(&path).output();
    if let Ok(out) = out {
        let stdout = String::from_utf8_lossy(&out.stdout);
        assert!(
            out.status.success(),
            "h5dump failed: {}",
            String::from_utf8_lossy(&out.stderr)
        );
        assert!(stdout.contains("1, 2, 3"));
    }
}

#[test]
fn test_writable_file_root_attr() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_attr.h5");

    {
        let mut wf = WritableFile::create(&path).unwrap();
        wf.add_attr("version", 42i64).unwrap();
        wf.new_dataset_builder("data")
            .write::<f32>(&[1.0, 2.0])
            .unwrap();
        let f = wf.close().unwrap();

        let attr = f.attr("version").unwrap();
        let val: i64 = attr.read_scalar::<i64>().unwrap();
        assert_eq!(val, 42);
    }
}

#[test]
fn test_writable_file_oversized_object_header_uses_continuation_chunk() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("api_write_header_continuation.h5");
    let payload = vec![7u8; 60_000];
    let attr_len = 60_000;

    {
        let mut wf = WritableFile::create(&path).unwrap();
        let mut builder = wf.new_dataset_builder("compact");
        for idx in 0..8 {
            builder = builder
                .fixed_ascii_attr(&format!("attr_{idx}"), "x", attr_len)
                .unwrap();
        }
        builder.compact().write::<u8>(&payload).unwrap();
        let f = wf.close().unwrap();

        let ds = f.dataset("compact").unwrap();
        assert_dataset_values::<u8>(&ds, &payload).unwrap();
        let expected = [
            "attr_0", "attr_1", "attr_2", "attr_3", "attr_4", "attr_5", "attr_6", "attr_7",
        ];
        let mut found = [false; 8];
        let count = location_attr_summary_into(&ds, &expected, &mut found).unwrap();
        assert_eq!(count, expected.len());
        assert!(found.iter().all(|present| *present));
        assert_eq!(attribute_string(&ds.attr("attr_7").unwrap()).unwrap(), "x");
    }

    let image = std::fs::read(&path).unwrap();
    let continuation_chunks = image.windows(4).filter(|window| *window == b"OCHK").count();
    assert!(
        continuation_chunks >= 2,
        "oversized compact root header should be split into chained v2 continuation chunks, found {continuation_chunks}"
    );
}
