/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of HDF5.  The full HDF5 copyright notice, including     *
 * terms governing use, modification, and redistribution, is contained in    *
 * the COPYING file, which can be found at the root of the source code       *
 * distribution tree, or in https://www.hdfgroup.org/licenses.               *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

package jtest;

import static org.junit.Assert.*;

import static jtest.FfmTestSupport.*;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;

import org.hdfgroup.javahdf5.H5A_info_t;
import org.hdfgroup.javahdf5.H5A_operator2_t;
import org.hdfgroup.javahdf5.hdf5_h;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

/**
 * TestH5Affm - FFM-based tests for HDF5 Attribute operations.
 * Tests the H5A* API using Foreign Function & Memory (FFM) bindings.
 */
public class TestH5Affm {
    private static final String H5_FILE = "testA.h5";
    private static final int DIM_X      = 4;
    private static final int DIM_Y      = 6;
    private static final int RANK       = 2;

    @Rule
    public TestName testname = new TestName();

    long H5fid = hdf5_h.H5I_INVALID_HID();
    long H5did = hdf5_h.H5I_INVALID_HID();
    long H5sid = hdf5_h.H5I_INVALID_HID();
    long H5aid = hdf5_h.H5I_INVALID_HID();

    @Before
    public void createH5file() throws Exception
    {
        try (Arena arena = Arena.ofConfined()) {
            // Create file
            MemorySegment fileName = stringToSegment(arena, H5_FILE);
            H5fid                  = hdf5_h.H5Fcreate(fileName, hdf5_h.H5F_ACC_TRUNC(), hdf5_h.H5P_DEFAULT(),
                                                      hdf5_h.H5P_DEFAULT());
            assertTrue("H5Fcreate failed", isValidId(H5fid));

            // Create dataspace
            long[] dims               = {DIM_X, DIM_Y};
            MemorySegment dimsSegment = allocateLongArray(arena, RANK);
            copyToSegment(dimsSegment, dims);
            H5sid = hdf5_h.H5Screate_simple(RANK, dimsSegment, MemorySegment.NULL);
            assertTrue("H5Screate_simple failed", isValidId(H5sid));

            // Create dataset for attaching attributes
            MemorySegment dsetName = stringToSegment(arena, "dset");
            H5did = hdf5_h.H5Dcreate2(H5fid, dsetName, hdf5_h.H5T_NATIVE_INT_g(), H5sid, hdf5_h.H5P_DEFAULT(),
                                      hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Dcreate2 failed", isValidId(H5did));
        }
    }

    @After
    public void deleteH5file() throws Exception
    {
        closeQuietly(H5aid, hdf5_h::H5Aclose);
        closeQuietly(H5did, hdf5_h::H5Dclose);
        closeQuietly(H5sid, hdf5_h::H5Sclose);
        closeQuietly(H5fid, hdf5_h::H5Fclose);

        H5aid = hdf5_h.H5I_INVALID_HID();
        H5did = hdf5_h.H5I_INVALID_HID();
        H5sid = hdf5_h.H5I_INVALID_HID();
        H5fid = hdf5_h.H5I_INVALID_HID();
    }

    static
    {
        try {
            System.loadLibrary("hdf5");
            hdf5_h.H5open();
        }
        catch (UnsatisfiedLinkError e) {
            System.err.println("Failed to load HDF5 library: " + e.getMessage());
        }
    }

    @Test
    public void testH5Acreate()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create scalar attribute space
            long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            assertTrue("H5Screate scalar failed", isValidId(attr_sid));

            // Create attribute
            MemorySegment attrName = stringToSegment(arena, "attr1");
            H5aid                  = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                                       hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(H5aid));

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Awrite_read()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute with array dataspace
            long[] attr_dims              = {3};
            MemorySegment attrDimsSegment = allocateLongArray(arena, 1);
            attrDimsSegment.setAtIndex(ValueLayout.JAVA_LONG, 0, attr_dims[0]);

            long attr_sid = hdf5_h.H5Screate_simple(1, attrDimsSegment, MemorySegment.NULL);
            assertTrue("H5Screate_simple failed", isValidId(attr_sid));

            MemorySegment attrName = stringToSegment(arena, "int_array_attr");
            H5aid                  = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                                       hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(H5aid));

            // Write data
            int[] write_data           = {10, 20, 30};
            MemorySegment writeSegment = allocateIntArray(arena, 3);
            copyToSegment(writeSegment, write_data);

            int result = hdf5_h.H5Awrite(H5aid, hdf5_h.H5T_NATIVE_INT_g(), writeSegment);
            assertTrue("H5Awrite failed", isSuccess(result));

            // Read back
            MemorySegment readSegment = allocateIntArray(arena, 3);
            result                    = hdf5_h.H5Aread(H5aid, hdf5_h.H5T_NATIVE_INT_g(), readSegment);
            assertTrue("H5Aread failed", isSuccess(result));

            // Verify
            int[] read_data = new int[3];
            copyFromSegment(readSegment, read_data);
            assertArrayEquals("Data mismatch", write_data, read_data);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aopen()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute first
            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment attrName = stringToSegment(arena, "test_attr");

            long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                         hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(aid));
            hdf5_h.H5Aclose(aid);

            // Open attribute
            H5aid = hdf5_h.H5Aopen(H5did, attrName, hdf5_h.H5P_DEFAULT());
            assertTrue("H5Aopen failed", isValidId(H5aid));

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aclose()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment attrName = stringToSegment(arena, "temp_attr");

            H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                      hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(H5aid));

            int result = hdf5_h.H5Aclose(H5aid);
            assertTrue("H5Aclose failed", isSuccess(result));

            H5aid = hdf5_h.H5I_INVALID_HID();
            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aget_name()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            String expectedName    = "my_attribute";
            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment attrName = stringToSegment(arena, expectedName);

            H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                      hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(H5aid));

            // Get name size
            long nameSize = hdf5_h.H5Aget_name(H5aid, 0, MemorySegment.NULL);
            assertTrue("H5Aget_name size query failed", nameSize > 0);

            // Get name
            MemorySegment nameBuffer = arena.allocate(nameSize + 1);
            hdf5_h.H5Aget_name(H5aid, nameSize + 1, nameBuffer);

            String retrievedName = nameBuffer.getString(0);
            assertEquals("Attribute name mismatch", expectedName, retrievedName);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aget_space()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute with specific dimensions
            long[] dims               = {5, 3};
            MemorySegment dimsSegment = allocateLongArray(arena, 2);
            copyToSegment(dimsSegment, dims);

            long attr_sid          = hdf5_h.H5Screate_simple(2, dimsSegment, MemorySegment.NULL);
            MemorySegment attrName = stringToSegment(arena, "array_attr");

            H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                      hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(H5aid));

            // Get dataspace
            long retrieved_sid = hdf5_h.H5Aget_space(H5aid);
            assertTrue("H5Aget_space failed", isValidId(retrieved_sid));

            // Verify dimensions
            MemorySegment retrievedDims = allocateLongArray(arena, 2);
            hdf5_h.H5Sget_simple_extent_dims(retrieved_sid, retrievedDims, MemorySegment.NULL);

            long[] readDims = new long[2];
            copyFromSegment(retrievedDims, readDims);
            assertArrayEquals("Dimensions mismatch", dims, readDims);

            hdf5_h.H5Sclose(retrieved_sid);
            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aget_type()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment attrName = stringToSegment(arena, "type_attr");

            H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_DOUBLE_g(), attr_sid,
                                      hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(H5aid));

            // Get type
            long retrieved_tid = hdf5_h.H5Aget_type(H5aid);
            assertTrue("H5Aget_type failed", isValidId(retrieved_tid));

            // Verify it's a double type
            int equal = hdf5_h.H5Tequal(retrieved_tid, hdf5_h.H5T_NATIVE_DOUBLE_g());
            assertTrue("Type should be H5T_NATIVE_DOUBLE", equal > 0);

            hdf5_h.H5Tclose(retrieved_tid);
            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aexists()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            MemorySegment existingName = stringToSegment(arena, "existing_attr");
            MemorySegment missingName  = stringToSegment(arena, "missing_attr");

            // Create one attribute
            long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            long aid      = hdf5_h.H5Acreate2(H5did, existingName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                              hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(aid));
            hdf5_h.H5Aclose(aid);

            // Check existing
            int exists = hdf5_h.H5Aexists(H5did, existingName);
            assertTrue("Attribute should exist", exists > 0);

            // Check non-existing
            exists = hdf5_h.H5Aexists(H5did, missingName);
            assertEquals("Attribute should not exist", 0, exists);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Adelete()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            MemorySegment attrName = stringToSegment(arena, "deletable_attr");

            // Create attribute
            long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            long aid      = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                              hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(aid));
            hdf5_h.H5Aclose(aid);

            // Verify it exists
            int exists = hdf5_h.H5Aexists(H5did, attrName);
            assertTrue("Attribute should exist before delete", exists > 0);

            // Delete
            int result = hdf5_h.H5Adelete(H5did, attrName);
            assertTrue("H5Adelete failed", isSuccess(result));

            // Verify it's gone
            exists = hdf5_h.H5Aexists(H5did, attrName);
            assertEquals("Attribute should not exist after delete", 0, exists);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aget_storage_size()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute with 10 integers
            long[] attr_dims              = {10};
            MemorySegment attrDimsSegment = allocateLongArray(arena, 1);
            attrDimsSegment.setAtIndex(ValueLayout.JAVA_LONG, 0, attr_dims[0]);

            long attr_sid          = hdf5_h.H5Screate_simple(1, attrDimsSegment, MemorySegment.NULL);
            MemorySegment attrName = stringToSegment(arena, "storage_attr");

            H5aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                      hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(H5aid));

            // Write data
            int[] data                = new int[10];
            MemorySegment dataSegment = allocateIntArray(arena, 10);
            copyToSegment(dataSegment, data);
            hdf5_h.H5Awrite(H5aid, hdf5_h.H5T_NATIVE_INT_g(), dataSegment);

            // Get storage size
            long storage_size = hdf5_h.H5Aget_storage_size(H5aid);
            assertEquals("Storage size should be 10 * sizeof(int)", 40L, storage_size);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Awrite_readStr()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            String testString = "Hello HDF5 Attributes!";

            // Create string type
            long str_tid = hdf5_h.H5Tcopy(hdf5_h.H5T_C_S1_g());
            assertTrue("H5Tcopy failed", isValidId(str_tid));

            hdf5_h.H5Tset_size(str_tid, testString.length() + 1);
            hdf5_h.H5Tset_strpad(str_tid, hdf5_h.H5T_STR_NULLTERM());

            // Create attribute
            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment attrName = stringToSegment(arena, "str_attr");

            H5aid = hdf5_h.H5Acreate2(H5did, attrName, str_tid, attr_sid, hdf5_h.H5P_DEFAULT(),
                                      hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(H5aid));

            // Write string
            MemorySegment writeData = stringToSegment(arena, testString);
            int writeResult         = hdf5_h.H5Awrite(H5aid, str_tid, writeData);
            assertTrue("H5Awrite failed", isSuccess(writeResult));

            // Read string back
            MemorySegment readData = arena.allocate(testString.length() + 1);
            int readResult         = hdf5_h.H5Aread(H5aid, str_tid, readData);
            assertTrue("H5Aread failed", isSuccess(readResult));

            String retrievedString = readData.getString(0);
            assertEquals("String mismatch", testString, retrievedString);

            hdf5_h.H5Tclose(str_tid);
            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Arename()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            MemorySegment oldName = stringToSegment(arena, "old_name");
            MemorySegment newName = stringToSegment(arena, "new_name");

            // Create attribute
            long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            long aid      = hdf5_h.H5Acreate2(H5did, oldName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                              hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(aid));
            hdf5_h.H5Aclose(aid);

            // Verify old name exists
            int exists = hdf5_h.H5Aexists(H5did, oldName);
            assertTrue("Old name should exist", exists > 0);

            // Rename
            int result = hdf5_h.H5Arename(H5did, oldName, newName);
            assertTrue("H5Arename failed", isSuccess(result));

            // Verify new name exists and old doesn't
            exists = hdf5_h.H5Aexists(H5did, newName);
            assertTrue("New name should exist", exists > 0);

            exists = hdf5_h.H5Aexists(H5did, oldName);
            assertEquals("Old name should not exist", 0, exists);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aget_num_attrs()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create 3 attributes
            long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());

            for (int i = 0; i < 3; i++) {
                MemorySegment attrName = stringToSegment(arena, "attr_" + i);
                long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                             hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
                assertTrue("H5Acreate2 failed for attr_" + i, isValidId(aid));
                hdf5_h.H5Aclose(aid);
            }

            // Get number of attributes
            int num_attrs = hdf5_h.H5Aget_num_attrs(H5did);
            assertTrue("Should have at least 3 attributes", num_attrs >= 3);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    // =========================
    // Phase 1: Essential Query Operations
    // =========================

    @Test
    public void testH5Aget_info()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute with scalar dataspace
            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment attrName = stringToSegment(arena, "info_test_attr");

            long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                         hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(aid));

            // Write some data
            MemorySegment data = allocateInt(arena);
            setInt(data, 42);
            int writeResult = hdf5_h.H5Awrite(aid, hdf5_h.H5T_NATIVE_INT_g(), data);
            assertTrue("H5Awrite failed", isSuccess(writeResult));

            // Get attribute info
            MemorySegment info = H5A_info_t.allocate(arena);
            int result         = hdf5_h.H5Aget_info(aid, info);
            assertTrue("H5Aget_info failed", isSuccess(result));

            // Verify info fields
            long data_size = H5A_info_t.data_size(info);
            assertEquals("Data size should be 4 bytes (sizeof int)", 4L, data_size);

            // corder_valid may be false if creation order tracking is not enabled
            boolean corder_valid = H5A_info_t.corder_valid(info);
            // Just verify we can read it (no assertion on value)
            assertNotNull("corder_valid should be readable", Boolean.valueOf(corder_valid));

            // cset should be ASCII by default
            int cset = H5A_info_t.cset(info);
            assertEquals("Character set should be ASCII", hdf5_h.H5T_CSET_ASCII(), cset);

            hdf5_h.H5Aclose(aid);
            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aget_info_by_idx()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create multiple attributes to test indexing
            long attr_sid = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());

            String[] attrNames = {"first_attr", "second_attr", "third_attr"};
            for (String name : attrNames) {
                MemorySegment attrName = stringToSegment(arena, name);
                long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_DOUBLE_g(), attr_sid,
                                             hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
                assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
                hdf5_h.H5Aclose(aid);
            }

            // Get info for the second attribute (index 1) by name order
            MemorySegment objName = stringToSegment(arena, ".");
            MemorySegment info    = H5A_info_t.allocate(arena);

            int result = hdf5_h.H5Aget_info_by_idx(H5did, objName, hdf5_h.H5_INDEX_NAME(),
                                                   hdf5_h.H5_ITER_INC(), 1, info, hdf5_h.H5P_DEFAULT());
            assertTrue("H5Aget_info_by_idx failed", isSuccess(result));

            // Verify we got valid info
            long data_size = H5A_info_t.data_size(info);
            assertEquals("Data size should be 8 bytes (sizeof double)", 8L, data_size);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aget_info_by_name()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute on dataset
            String attrNameStr     = "named_info_attr";
            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment attrName = stringToSegment(arena, attrNameStr);

            long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_FLOAT_g(), attr_sid,
                                         hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(aid));
            hdf5_h.H5Aclose(aid);

            // Get info by name from the file (using dataset path)
            MemorySegment objName = stringToSegment(arena, "dset");
            MemorySegment info    = H5A_info_t.allocate(arena);

            int result = hdf5_h.H5Aget_info_by_name(H5fid, objName, attrName, info, hdf5_h.H5P_DEFAULT());
            assertTrue("H5Aget_info_by_name failed", isSuccess(result));

            // Verify info
            long data_size = H5A_info_t.data_size(info);
            assertEquals("Data size should be 4 bytes (sizeof float)", 4L, data_size);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aget_name_by_idx()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create multiple attributes with known names
            long attr_sid        = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            String[] attrNames   = {"alpha", "beta", "gamma"};
            String[] sortedNames = {"alpha", "beta", "gamma"}; // Already sorted alphabetically

            for (String name : attrNames) {
                MemorySegment attrName = stringToSegment(arena, name);
                long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                             hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
                assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
                hdf5_h.H5Aclose(aid);
            }

            // Get name of each attribute by index (alphabetical order)
            MemorySegment objName = stringToSegment(arena, ".");

            for (int i = 0; i < sortedNames.length; i++) {
                // Get name size first
                long nameSize =
                    hdf5_h.H5Aget_name_by_idx(H5did, objName, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), i,
                                              MemorySegment.NULL, 0, hdf5_h.H5P_DEFAULT());
                assertTrue("H5Aget_name_by_idx size query failed for index " + i, nameSize > 0);

                // Get actual name
                MemorySegment nameBuffer = arena.allocate(nameSize + 1);
                long result =
                    hdf5_h.H5Aget_name_by_idx(H5did, objName, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), i,
                                              nameBuffer, nameSize + 1, hdf5_h.H5P_DEFAULT());
                assertTrue("H5Aget_name_by_idx failed for index " + i, result > 0);

                String retrievedName = nameBuffer.getString(0);
                assertEquals("Name at index " + i + " should match", sortedNames[i], retrievedName);
            }

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aget_create_plist()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute creation property list with specific settings
            long acpl = hdf5_h.H5Pcreate(hdf5_h.H5P_CLS_ATTRIBUTE_CREATE_ID_g());
            assertTrue("H5Pcreate acpl failed", isValidId(acpl));

            // Set character encoding to UTF-8
            int setResult = hdf5_h.H5Pset_char_encoding(acpl, hdf5_h.H5T_CSET_UTF8());
            assertTrue("H5Pset_char_encoding failed", isSuccess(setResult));

            // Create attribute with this property list
            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment attrName = stringToSegment(arena, "plist_attr");

            long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid, acpl,
                                         hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(aid));

            // Get the creation property list back
            long retrieved_acpl = hdf5_h.H5Aget_create_plist(aid);
            assertTrue("H5Aget_create_plist failed", isValidId(retrieved_acpl));

            // Verify the encoding is UTF-8
            MemorySegment encoding = allocateInt(arena);
            int getResult          = hdf5_h.H5Pget_char_encoding(retrieved_acpl, encoding);
            assertTrue("H5Pget_char_encoding failed", isSuccess(getResult));

            int retrievedEncoding = getInt(encoding);
            assertEquals("Character encoding should be UTF-8", hdf5_h.H5T_CSET_UTF8(), retrievedEncoding);

            hdf5_h.H5Pclose(retrieved_acpl);
            hdf5_h.H5Pclose(acpl);
            hdf5_h.H5Aclose(aid);
            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aexists_by_name()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute on dataset
            String existingAttrName    = "existing_by_name";
            String nonExistingAttrName = "nonexistent_by_name";

            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment attrName = stringToSegment(arena, existingAttrName);

            long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                         hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(aid));
            hdf5_h.H5Aclose(aid);

            // Check existence from file using dataset path
            MemorySegment objName             = stringToSegment(arena, "dset");
            MemorySegment existingAttrNameSeg = stringToSegment(arena, existingAttrName);
            MemorySegment missingAttrNameSeg  = stringToSegment(arena, nonExistingAttrName);

            // Should exist
            int exists = hdf5_h.H5Aexists_by_name(H5fid, objName, existingAttrNameSeg, hdf5_h.H5P_DEFAULT());
            assertTrue("Attribute should exist", exists > 0);

            // Should not exist
            exists = hdf5_h.H5Aexists_by_name(H5fid, objName, missingAttrNameSeg, hdf5_h.H5P_DEFAULT());
            assertEquals("Attribute should not exist", 0, exists);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    // =========================
    // Phase 2: Advanced Operations
    // =========================

    @Test
    public void testH5Aopen_by_idx()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create multiple attributes with known names
            long attr_sid        = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            String[] attrNames   = {"apple", "banana", "cherry"};
            String[] sortedNames = {"apple", "banana", "cherry"}; // Already sorted

            for (String name : attrNames) {
                MemorySegment attrName = stringToSegment(arena, name);
                long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                             hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
                assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
                hdf5_h.H5Aclose(aid);
            }

            // Open second attribute by index (index 1)
            MemorySegment objName = stringToSegment(arena, ".");
            long aid = hdf5_h.H5Aopen_by_idx(H5did, objName, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), 1,
                                             hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Aopen_by_idx failed", isValidId(aid));

            // Verify we opened the correct attribute by checking its name
            long nameSize         = hdf5_h.H5Aget_name(aid, 0, MemorySegment.NULL);
            MemorySegment nameBuf = arena.allocate(nameSize + 1);
            hdf5_h.H5Aget_name(aid, nameSize + 1, nameBuf);
            String retrievedName = nameBuf.getString(0);
            assertEquals("Should have opened 'banana' (index 1)", sortedNames[1], retrievedName);

            hdf5_h.H5Aclose(aid);
            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Adelete_by_idx()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create 3 attributes
            long attr_sid      = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            String[] attrNames = {"first", "second", "third"};

            for (String name : attrNames) {
                MemorySegment attrName = stringToSegment(arena, name);
                long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                             hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
                assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
                hdf5_h.H5Aclose(aid);
            }

            // Delete middle attribute (index 1 = "second")
            MemorySegment objName = stringToSegment(arena, ".");
            int result = hdf5_h.H5Adelete_by_idx(H5did, objName, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(),
                                                 1, hdf5_h.H5P_DEFAULT());
            assertTrue("H5Adelete_by_idx failed", isSuccess(result));

            // Verify "second" is gone
            MemorySegment secondName = stringToSegment(arena, "second");
            int exists               = hdf5_h.H5Aexists(H5did, secondName);
            assertEquals("'second' should not exist after deletion", 0, exists);

            // Verify others still exist
            MemorySegment firstName = stringToSegment(arena, "first");
            MemorySegment thirdName = stringToSegment(arena, "third");
            exists                  = hdf5_h.H5Aexists(H5did, firstName);
            assertTrue("'first' should still exist", exists > 0);
            exists = hdf5_h.H5Aexists(H5did, thirdName);
            assertTrue("'third' should still exist", exists > 0);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Adelete_by_name()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute on dataset
            String attrNameStr     = "deletable_by_name";
            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment attrName = stringToSegment(arena, attrNameStr);

            long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                         hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(aid));
            hdf5_h.H5Aclose(aid);

            // Verify it exists
            int exists = hdf5_h.H5Aexists(H5did, attrName);
            assertTrue("Attribute should exist before delete", exists > 0);

            // Delete by name from file using dataset path
            MemorySegment objName = stringToSegment(arena, "dset");
            int result            = hdf5_h.H5Adelete_by_name(H5fid, objName, attrName, hdf5_h.H5P_DEFAULT());
            assertTrue("H5Adelete_by_name failed", isSuccess(result));

            // Verify it's gone
            exists = hdf5_h.H5Aexists(H5did, attrName);
            assertEquals("Attribute should not exist after delete", 0, exists);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Arename_by_name()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute on dataset
            String oldNameStr     = "old_name_by_path";
            String newNameStr     = "new_name_by_path";
            long attr_sid         = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment oldName = stringToSegment(arena, oldNameStr);
            MemorySegment newName = stringToSegment(arena, newNameStr);

            long aid = hdf5_h.H5Acreate2(H5did, oldName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                         hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate2 failed", isValidId(aid));
            hdf5_h.H5Aclose(aid);

            // Verify old name exists
            int exists = hdf5_h.H5Aexists(H5did, oldName);
            assertTrue("Old name should exist", exists > 0);

            // Rename by name from file using dataset path
            MemorySegment objName = stringToSegment(arena, "dset");
            int result = hdf5_h.H5Arename_by_name(H5fid, objName, oldName, newName, hdf5_h.H5P_DEFAULT());
            assertTrue("H5Arename_by_name failed", isSuccess(result));

            // Verify new name exists and old doesn't
            exists = hdf5_h.H5Aexists(H5did, newName);
            assertTrue("New name should exist", exists > 0);

            exists = hdf5_h.H5Aexists(H5did, oldName);
            assertEquals("Old name should not exist", 0, exists);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aiterate2()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create multiple attributes
            long attr_sid      = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            String[] attrNames = {"attr_A", "attr_B", "attr_C", "attr_D"};

            for (String name : attrNames) {
                MemorySegment attrName = stringToSegment(arena, name);
                long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                             hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
                assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
                hdf5_h.H5Aclose(aid);
            }

            // Create iteration callback
            final int[] count           = {0};
            final String[] names        = new String[10];
            H5A_operator2_t.Function cb = (loc_id, attr_name, ainfo, op_data) ->
            {
                try {
                    String name     = attr_name.getString(0);
                    names[count[0]] = name;
                    count[0]++;
                    return 0; // Continue iteration
                }
                catch (Exception e) {
                    return -1; // Stop on error
                }
            };

            MemorySegment callback = H5A_operator2_t.allocate(cb, arena);
            MemorySegment idx      = allocateLong(arena);
            setLong(idx, 0);

            // Iterate
            int result = hdf5_h.H5Aiterate2(H5did, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), idx,
                                            callback, MemorySegment.NULL);
            assertTrue("H5Aiterate2 failed", isSuccess(result));

            // Verify we iterated all attributes (at least the 4 we created)
            assertTrue("Should have iterated at least 4 attributes", count[0] >= 4);

            // Verify all our attributes were seen
            boolean foundA = false, foundB = false, foundC = false, foundD = false;
            for (int i = 0; i < count[0]; i++) {
                if ("attr_A".equals(names[i]))
                    foundA = true;
                if ("attr_B".equals(names[i]))
                    foundB = true;
                if ("attr_C".equals(names[i]))
                    foundC = true;
                if ("attr_D".equals(names[i]))
                    foundD = true;
            }
            assertTrue("Should have found attr_A", foundA);
            assertTrue("Should have found attr_B", foundB);
            assertTrue("Should have found attr_C", foundC);
            assertTrue("Should have found attr_D", foundD);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aiterate_by_name()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attributes on dataset
            long attr_sid      = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            String[] attrNames = {"iter_1", "iter_2", "iter_3"};

            for (String name : attrNames) {
                MemorySegment attrName = stringToSegment(arena, name);
                long aid = hdf5_h.H5Acreate2(H5did, attrName, hdf5_h.H5T_NATIVE_INT_g(), attr_sid,
                                             hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
                assertTrue("H5Acreate2 failed for " + name, isValidId(aid));
                hdf5_h.H5Aclose(aid);
            }

            // Create iteration callback
            final int[] count           = {0};
            H5A_operator2_t.Function cb = (loc_id, attr_name, ainfo, op_data) ->
            {
                count[0]++;
                return 0; // Continue
            };

            MemorySegment callback = H5A_operator2_t.allocate(cb, arena);
            MemorySegment idx      = allocateLong(arena);
            setLong(idx, 0);

            // Iterate from file using dataset path
            MemorySegment objName = stringToSegment(arena, "dset");
            int result =
                hdf5_h.H5Aiterate_by_name(H5fid, objName, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), idx,
                                          callback, MemorySegment.NULL, hdf5_h.H5P_DEFAULT());
            assertTrue("H5Aiterate_by_name failed", isSuccess(result));

            // Verify we iterated at least our 3 attributes
            assertTrue("Should have iterated at least 3 attributes", count[0] >= 3);

            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Acreate_by_name_comprehensive()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            // Create attribute on dataset using path from file
            String attrNameStr     = "created_by_path_comprehensive";
            long attr_sid          = hdf5_h.H5Screate(hdf5_h.H5S_SCALAR());
            MemorySegment objName  = stringToSegment(arena, "dset");
            MemorySegment attrName = stringToSegment(arena, attrNameStr);

            long aid =
                hdf5_h.H5Acreate_by_name(H5fid, objName, attrName, hdf5_h.H5T_NATIVE_DOUBLE_g(), attr_sid,
                                         hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT(), hdf5_h.H5P_DEFAULT());
            assertTrue("H5Acreate_by_name failed", isValidId(aid));

            // Write data
            MemorySegment data = allocateDoubleArray(arena, 1);
            data.setAtIndex(ValueLayout.JAVA_DOUBLE, 0, 3.14159);
            int writeResult = hdf5_h.H5Awrite(aid, hdf5_h.H5T_NATIVE_DOUBLE_g(), data);
            assertTrue("H5Awrite failed", isSuccess(writeResult));

            hdf5_h.H5Aclose(aid);

            // Verify attribute is accessible from dataset
            MemorySegment attrNameCheck = stringToSegment(arena, attrNameStr);
            int exists                  = hdf5_h.H5Aexists(H5did, attrNameCheck);
            assertTrue("Attribute should exist on dataset", exists > 0);

            // Open and read back to verify data
            long aid2 = hdf5_h.H5Aopen(H5did, attrNameCheck, hdf5_h.H5P_DEFAULT());
            assertTrue("H5Aopen failed", isValidId(aid2));

            MemorySegment readData = allocateDoubleArray(arena, 1);
            int readResult         = hdf5_h.H5Aread(aid2, hdf5_h.H5T_NATIVE_DOUBLE_g(), readData);
            assertTrue("H5Aread failed", isSuccess(readResult));

            double value = readData.getAtIndex(ValueLayout.JAVA_DOUBLE, 0);
            assertEquals("Data should match", 3.14159, value, 0.00001);

            hdf5_h.H5Aclose(aid2);
            hdf5_h.H5Sclose(attr_sid);
        }
    }

    @Test
    public void testH5Aiterate_nullCallback()
    {
        System.out.print(testname.getMethodName());

        try (Arena arena = Arena.ofConfined()) {
            MemorySegment idx = allocateLongArray(arena, 1);
            copyToSegment(idx, new long[] {0});

            // NULL callback must be rejected gracefully (H5E_BADVALUE), not crash
            long result = hdf5_h.H5Aiterate2(H5did, hdf5_h.H5_INDEX_NAME(), hdf5_h.H5_ITER_INC(), idx,
                                             MemorySegment.NULL, MemorySegment.NULL);
            assertTrue("H5Aiterate2 should fail when callback is NULL", result < 0);
        }
    }
}
