// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

//! This includes utilities for hashing and murmur3 hashing.

#[macro_export]
macro_rules! hash_array {
    ($array_type: ident, $column: ident, $hashes: ident, $hash_method: ident) => {
        let array = $column
            .as_any()
            .downcast_ref::<$array_type>()
            .unwrap_or_else(|| {
                panic!(
                    "Failed to downcast column to {}. Actual data type: {:?}.",
                    stringify!($array_type),
                    $column.data_type()
                )
            });
        if array.null_count() == 0 {
            // Fast path: no nulls, use direct indexing
            for i in 0..$hashes.len() {
                $hashes[i] = $hash_method(&array.value(i), $hashes[i]);
            }
        } else {
            // Slow path: check nulls
            for i in 0..$hashes.len() {
                if !array.is_null(i) {
                    $hashes[i] = $hash_method(&array.value(i), $hashes[i]);
                }
            }
        }
    };
}

#[macro_export]
macro_rules! hash_array_boolean {
    ($array_type: ident, $column: ident, $hash_input_type: ident, $hashes: ident, $hash_method: ident) => {
        let array = $column
            .as_any()
            .downcast_ref::<$array_type>()
            .unwrap_or_else(|| {
                panic!(
                    "Failed to downcast column to {}. Actual data type: {:?}.",
                    stringify!($array_type),
                    $column.data_type()
                )
            });
        if array.null_count() == 0 {
            // Fast path: no nulls, use direct indexing
            for i in 0..$hashes.len() {
                $hashes[i] = $hash_method(
                    $hash_input_type::from(array.value(i)).to_le_bytes(),
                    $hashes[i],
                );
            }
        } else {
            // Slow path: check nulls
            for i in 0..$hashes.len() {
                if !array.is_null(i) {
                    $hashes[i] = $hash_method(
                        $hash_input_type::from(array.value(i)).to_le_bytes(),
                        $hashes[i],
                    );
                }
            }
        }
    };
}

#[macro_export]
macro_rules! hash_array_primitive {
    ($array_type: ident, $column: ident, $ty: ident, $hashes: ident, $hash_method: ident) => {
        let array = $column
            .as_any()
            .downcast_ref::<$array_type>()
            .unwrap_or_else(|| {
                panic!(
                    "Failed to downcast column to {}. Actual data type: {:?}.",
                    stringify!($array_type),
                    $column.data_type()
                )
            });
        let values = array.values();

        if array.null_count() == 0 {
            // Fast path: no nulls, use direct indexing
            for i in 0..values.len() {
                $hashes[i] = $hash_method((values[i] as $ty).to_le_bytes(), $hashes[i]);
            }
        } else {
            // Slow path: check nulls
            for i in 0..values.len() {
                if !array.is_null(i) {
                    $hashes[i] = $hash_method((values[i] as $ty).to_le_bytes(), $hashes[i]);
                }
            }
        }
    };
}

#[macro_export]
macro_rules! hash_array_primitive_float {
    ($array_type: ident, $column: ident, $ty: ident, $ty2: ident, $hashes: ident, $hash_method: ident) => {
        let array = $column
            .as_any()
            .downcast_ref::<$array_type>()
            .unwrap_or_else(|| {
                panic!(
                    "Failed to downcast column to {}. Actual data type: {:?}.",
                    stringify!($array_type),
                    $column.data_type()
                )
            });
        let values = array.values();

        if array.null_count() == 0 {
            // Fast path: no nulls, use direct indexing
            for i in 0..values.len() {
                let value = values[i];
                // Spark uses 0 as hash for -0.0, see `Murmur3Hash` expression.
                if value == 0.0 && value.is_sign_negative() {
                    $hashes[i] = $hash_method((0 as $ty2).to_le_bytes(), $hashes[i]);
                } else {
                    $hashes[i] = $hash_method((value as $ty).to_le_bytes(), $hashes[i]);
                }
            }
        } else {
            // Slow path: check nulls
            for i in 0..values.len() {
                if !array.is_null(i) {
                    let value = values[i];
                    // Spark uses 0 as hash for -0.0, see `Murmur3Hash` expression.
                    if value == 0.0 && value.is_sign_negative() {
                        $hashes[i] = $hash_method((0 as $ty2).to_le_bytes(), $hashes[i]);
                    } else {
                        $hashes[i] = $hash_method((value as $ty).to_le_bytes(), $hashes[i]);
                    }
                }
            }
        }
    };
}

#[macro_export]
macro_rules! hash_array_small_decimal {
    ($array_type:ident, $column: ident, $hashes: ident, $hash_method: ident) => {
        let array = $column
            .as_any()
            .downcast_ref::<$array_type>()
            .unwrap_or_else(|| {
                panic!(
                    "Failed to downcast column to {}. Actual data type: {:?}.",
                    stringify!($array_type),
                    $column.data_type()
                )
            });

        if array.null_count() == 0 {
            // Fast path: no nulls, use direct indexing
            for i in 0..$hashes.len() {
                $hashes[i] = $hash_method(
                    i64::try_from(array.value(i))
                        .map(|v| v.to_le_bytes())
                        .map_err(|e| DataFusionError::Execution(e.to_string()))?,
                    $hashes[i],
                );
            }
        } else {
            // Slow path: check nulls
            for i in 0..$hashes.len() {
                if !array.is_null(i) {
                    $hashes[i] = $hash_method(
                        i64::try_from(array.value(i))
                            .map(|v| v.to_le_bytes())
                            .map_err(|e| DataFusionError::Execution(e.to_string()))?,
                        $hashes[i],
                    );
                }
            }
        }
    };
}

#[macro_export]
macro_rules! hash_array_decimal {
    ($array_type:ident, $column: ident, $hashes: ident, $hash_method: ident) => {
        let array = $column
            .as_any()
            .downcast_ref::<$array_type>()
            .unwrap_or_else(|| {
                panic!(
                    "Failed to downcast column to {}. Actual data type: {:?}.",
                    stringify!($array_type),
                    $column.data_type()
                )
            });

        if array.null_count() == 0 {
            // Fast path: no nulls, use direct indexing
            for i in 0..$hashes.len() {
                $hashes[i] = $hash_method(array.value(i).to_le_bytes(), $hashes[i]);
            }
        } else {
            // Slow path: check nulls
            for i in 0..$hashes.len() {
                if !array.is_null(i) {
                    $hashes[i] = $hash_method(array.value(i).to_le_bytes(), $hashes[i]);
                }
            }
        }
    };
}

/// Hash a list array with primitive elements by directly accessing the underlying buffer.
/// This avoids the overhead of slicing and recursive calls for common cases.
/// Supports both variable-length lists (with offsets) and fixed-size lists.
#[macro_export]
macro_rules! hash_list_primitive {
    // Variable-length list variant (List/LargeList)
    (offsets: $offsets:expr, $list_array:ident, $elem_array:ident, $hashes:ident, $hash_method:ident, $value_transform:expr) => {
        if $list_array.null_count() == 0 && $elem_array.null_count() == 0 {
            for (row_idx, hash) in $hashes.iter_mut().enumerate() {
                let start = $offsets[row_idx] as usize;
                let end = $offsets[row_idx + 1] as usize;
                for elem_idx in start..end {
                    let value = $elem_array.value(elem_idx);
                    *hash = $hash_method($value_transform(value), *hash);
                }
            }
        } else {
            for (row_idx, hash) in $hashes.iter_mut().enumerate() {
                if !$list_array.is_null(row_idx) {
                    let start = $offsets[row_idx] as usize;
                    let end = $offsets[row_idx + 1] as usize;
                    for elem_idx in start..end {
                        if !$elem_array.is_null(elem_idx) {
                            let value = $elem_array.value(elem_idx);
                            *hash = $hash_method($value_transform(value), *hash);
                        }
                    }
                }
            }
        }
    };
    // Fixed-size list variant
    (fixed_size: $list_size:expr, $list_array:ident, $elem_array:ident, $hashes:ident, $hash_method:ident, $value_transform:expr) => {
        if $list_array.null_count() == 0 && $elem_array.null_count() == 0 {
            for (row_idx, hash) in $hashes.iter_mut().enumerate() {
                let start = row_idx * $list_size;
                for elem_idx in 0..$list_size {
                    let value = $elem_array.value(start + elem_idx);
                    *hash = $hash_method($value_transform(value), *hash);
                }
            }
        } else {
            for (row_idx, hash) in $hashes.iter_mut().enumerate() {
                if !$list_array.is_null(row_idx) {
                    let start = row_idx * $list_size;
                    for elem_idx in 0..$list_size {
                        if !$elem_array.is_null(start + elem_idx) {
                            let value = $elem_array.value(start + elem_idx);
                            *hash = $hash_method($value_transform(value), *hash);
                        }
                    }
                }
            }
        }
    };
}

/// Hash a list array by recursively hashing each element.
/// For each row, we hash all elements in the list.
/// Spark hashes arrays by recursively hashing each element, where each
/// element's hash is computed using the previous element's hash as the seed.
/// This creates a chain: hash(elem_n, hash(elem_n-1, ... hash(elem_0, seed)...))
/// Dispatches hash operations for List/LargeList/FixedSizeList arrays with primitive element types.
/// This macro eliminates duplication by handling the type-to-array mapping for all supported primitives.
#[macro_export]
macro_rules! hash_list_with_primitive_elements {
    // Variant for List/LargeList with offsets
    (offsets: $list_array_type:ident, $list_array:ident, $values:ident, $offsets:ident, $field:expr, $hashes_buffer:ident, $hash_method:ident, $recursive_hash_method:ident, $fallback_offset_type:ty, $col:ident) => {
        match $field.data_type() {
            DataType::Int8 => {
                let elem_array = $values.as_any().downcast_ref::<Int8Array>().unwrap();
                $crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i8| (v as i32).to_le_bytes());
            }
            DataType::Int16 => {
                let elem_array = $values.as_any().downcast_ref::<Int16Array>().unwrap();
                $crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i16| (v as i32).to_le_bytes());
            }
            DataType::Int32 => {
                let elem_array = $values.as_any().downcast_ref::<Int32Array>().unwrap();
                $crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i32| v.to_le_bytes());
            }
            DataType::Int64 => {
                let elem_array = $values.as_any().downcast_ref::<Int64Array>().unwrap();
                $crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i64| v.to_le_bytes());
            }
            DataType::Float32 => {
                let elem_array = $values.as_any().downcast_ref::<Float32Array>().unwrap();
                $crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method,
                    |v: f32| if v == 0.0 && v.is_sign_negative() { (0_i32).to_le_bytes() } else { v.to_le_bytes() });
            }
            DataType::Float64 => {
                let elem_array = $values.as_any().downcast_ref::<Float64Array>().unwrap();
                $crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method,
                    |v: f64| if v == 0.0 && v.is_sign_negative() { (0_i64).to_le_bytes() } else { v.to_le_bytes() });
            }
            DataType::Boolean => {
                let elem_array = $values.as_any().downcast_ref::<BooleanArray>().unwrap();
                $crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: bool| (i32::from(v)).to_le_bytes());
            }
            DataType::Utf8 => {
                let elem_array = $values.as_any().downcast_ref::<StringArray>().unwrap();
                if $list_array.null_count() == 0 && elem_array.null_count() == 0 {
                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                        let start = $offsets[row_idx] as usize;
                        let end = $offsets[row_idx + 1] as usize;
                        for elem_idx in start..end {
                            *hash = $hash_method(elem_array.value(elem_idx), *hash);
                        }
                    }
                } else {
                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                        if !$list_array.is_null(row_idx) {
                            let start = $offsets[row_idx] as usize;
                            let end = $offsets[row_idx + 1] as usize;
                            for elem_idx in start..end {
                                if !elem_array.is_null(elem_idx) {
                                    *hash = $hash_method(elem_array.value(elem_idx), *hash);
                                }
                            }
                        }
                    }
                }
            }
            DataType::Binary => {
                let elem_array = $values.as_any().downcast_ref::<BinaryArray>().unwrap();
                if $list_array.null_count() == 0 && elem_array.null_count() == 0 {
                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                        let start = $offsets[row_idx] as usize;
                        let end = $offsets[row_idx + 1] as usize;
                        for elem_idx in start..end {
                            *hash = $hash_method(elem_array.value(elem_idx), *hash);
                        }
                    }
                } else {
                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                        if !$list_array.is_null(row_idx) {
                            let start = $offsets[row_idx] as usize;
                            let end = $offsets[row_idx + 1] as usize;
                            for elem_idx in start..end {
                                if !elem_array.is_null(elem_idx) {
                                    *hash = $hash_method(elem_array.value(elem_idx), *hash);
                                }
                            }
                        }
                    }
                }
            }
            DataType::Date32 => {
                let elem_array = $values.as_any().downcast_ref::<Date32Array>().unwrap();
                $crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i32| v.to_le_bytes());
            }
            DataType::Timestamp(TimeUnit::Microsecond, _) => {
                let elem_array = $values.as_any().downcast_ref::<TimestampMicrosecondArray>().unwrap();
                $crate::hash_list_primitive!(offsets: $offsets, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i64| v.to_le_bytes());
            }
            _ => {
                // Fall back to recursive approach for complex element types
                $crate::hash_list_array!($list_array_type, $fallback_offset_type, $col, $hashes_buffer, $recursive_hash_method);
            }
        }
    };
    // Variant for FixedSizeList with fixed size
    (fixed_size: $list_array:ident, $values:ident, $list_size:ident, $field:expr, $hashes_buffer:ident, $hash_method:ident, $recursive_hash_method:ident) => {
        match $field.data_type() {
            DataType::Int8 => {
                let elem_array = $values.as_any().downcast_ref::<Int8Array>().unwrap();
                $crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i8| (v as i32).to_le_bytes());
            }
            DataType::Int16 => {
                let elem_array = $values.as_any().downcast_ref::<Int16Array>().unwrap();
                $crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i16| (v as i32).to_le_bytes());
            }
            DataType::Int32 => {
                let elem_array = $values.as_any().downcast_ref::<Int32Array>().unwrap();
                $crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i32| v.to_le_bytes());
            }
            DataType::Int64 => {
                let elem_array = $values.as_any().downcast_ref::<Int64Array>().unwrap();
                $crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i64| v.to_le_bytes());
            }
            DataType::Float32 => {
                let elem_array = $values.as_any().downcast_ref::<Float32Array>().unwrap();
                $crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method,
                    |v: f32| if v == 0.0 && v.is_sign_negative() { (0_i32).to_le_bytes() } else { v.to_le_bytes() });
            }
            DataType::Float64 => {
                let elem_array = $values.as_any().downcast_ref::<Float64Array>().unwrap();
                $crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method,
                    |v: f64| if v == 0.0 && v.is_sign_negative() { (0_i64).to_le_bytes() } else { v.to_le_bytes() });
            }
            DataType::Boolean => {
                let elem_array = $values.as_any().downcast_ref::<BooleanArray>().unwrap();
                $crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: bool| (i32::from(v)).to_le_bytes());
            }
            DataType::Utf8 => {
                let elem_array = $values.as_any().downcast_ref::<StringArray>().unwrap();
                if $list_array.null_count() == 0 && elem_array.null_count() == 0 {
                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                        let start = row_idx * $list_size;
                        for elem_idx in 0..$list_size {
                            *hash = $hash_method(elem_array.value(start + elem_idx), *hash);
                        }
                    }
                } else {
                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                        if !$list_array.is_null(row_idx) {
                            let start = row_idx * $list_size;
                            for elem_idx in 0..$list_size {
                                if !elem_array.is_null(start + elem_idx) {
                                    *hash = $hash_method(elem_array.value(start + elem_idx), *hash);
                                }
                            }
                        }
                    }
                }
            }
            DataType::Binary => {
                let elem_array = $values.as_any().downcast_ref::<BinaryArray>().unwrap();
                if $list_array.null_count() == 0 && elem_array.null_count() == 0 {
                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                        let start = row_idx * $list_size;
                        for elem_idx in 0..$list_size {
                            *hash = $hash_method(elem_array.value(start + elem_idx), *hash);
                        }
                    }
                } else {
                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                        if !$list_array.is_null(row_idx) {
                            let start = row_idx * $list_size;
                            for elem_idx in 0..$list_size {
                                if !elem_array.is_null(start + elem_idx) {
                                    *hash = $hash_method(elem_array.value(start + elem_idx), *hash);
                                }
                            }
                        }
                    }
                }
            }
            DataType::Date32 => {
                let elem_array = $values.as_any().downcast_ref::<Date32Array>().unwrap();
                $crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i32| v.to_le_bytes());
            }
            DataType::Timestamp(TimeUnit::Microsecond, _) => {
                let elem_array = $values.as_any().downcast_ref::<TimestampMicrosecondArray>().unwrap();
                $crate::hash_list_primitive!(fixed_size: $list_size, $list_array, elem_array, $hashes_buffer, $hash_method, |v: i64| v.to_le_bytes());
            }
            _ => {
                // Fall back to recursive approach for complex element types
                if $list_array.null_count() == 0 {
                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                        let start = row_idx * $list_size;
                        for elem_idx in 0..$list_size {
                            let elem_array = $values.slice(start + elem_idx, 1);
                            let mut single_hash = [*hash];
                            $recursive_hash_method(&[elem_array], &mut single_hash)?;
                            *hash = single_hash[0];
                        }
                    }
                } else {
                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                        if !$list_array.is_null(row_idx) {
                            let start = row_idx * $list_size;
                            for elem_idx in 0..$list_size {
                                let elem_array = $values.slice(start + elem_idx, 1);
                                let mut single_hash = [*hash];
                                $recursive_hash_method(&[elem_array], &mut single_hash)?;
                                *hash = single_hash[0];
                            }
                        }
                    }
                }
            }
        }
    };
}

#[macro_export]
macro_rules! hash_list_array {
    ($array_type:ident, $offset_type:ty, $column: ident, $hashes: ident, $recursive_hash_method: ident) => {
        let list_array = $column
            .as_any()
            .downcast_ref::<$array_type>()
            .unwrap_or_else(|| {
                panic!(
                    "Failed to downcast column to {}. Actual data type: {:?}.",
                    stringify!($array_type),
                    $column.data_type()
                )
            });

        let values = list_array.values();
        let offsets = list_array.offsets();

        if list_array.null_count() == 0 {
            // Fast path: no nulls, skip null checks
            for (row_idx, hash) in $hashes.iter_mut().enumerate() {
                let start = offsets[row_idx] as usize;
                let end = offsets[row_idx + 1] as usize;
                let len = end - start;
                // Hash each element in sequence, chaining the hash values
                for elem_idx in 0..len {
                    let elem_array = values.slice(start + elem_idx, 1);
                    let mut single_hash = [*hash];
                    $recursive_hash_method(&[elem_array], &mut single_hash)?;
                    *hash = single_hash[0];
                }
            }
        } else {
            // Slow path: array has nulls, check each row
            for (row_idx, hash) in $hashes.iter_mut().enumerate() {
                if !list_array.is_null(row_idx) {
                    let start = offsets[row_idx] as usize;
                    let end = offsets[row_idx + 1] as usize;
                    let len = end - start;
                    // Hash each element in sequence, chaining the hash values
                    for elem_idx in 0..len {
                        let elem_array = values.slice(start + elem_idx, 1);
                        let mut single_hash = [*hash];
                        $recursive_hash_method(&[elem_array], &mut single_hash)?;
                        *hash = single_hash[0];
                    }
                }
            }
        }
    };
}

/// Creates hash values for every row, based on the values in the
/// columns.
///
/// The number of rows to hash is determined by `hashes_buffer.len()`.
/// `hashes_buffer` should be pre-sized appropriately
///
/// `hash_method` is the hash function to use.
/// `create_dictionary_hash_method` is the function to create hashes for dictionary arrays input.
/// `recursive_hash_method` is the function to call for recursive hashing of complex types.
#[macro_export]
macro_rules! create_hashes_internal {
    ($arrays: ident, $hashes_buffer: ident, $hash_method: ident, $create_dictionary_hash_method: ident, $recursive_hash_method: ident) => {
        use arrow::datatypes::{DataType, TimeUnit};
        use arrow::array::{types::*, *};

        for (i, col) in $arrays.iter().enumerate() {
            let first_col = i == 0;
            match col.data_type() {
                DataType::Boolean => {
                    $crate::hash_array_boolean!(
                        BooleanArray,
                        col,
                        i32,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Int8 => {
                    $crate::hash_array_primitive!(
                        Int8Array,
                        col,
                        i32,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Int16 => {
                    $crate::hash_array_primitive!(
                        Int16Array,
                        col,
                        i32,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Int32 => {
                    $crate::hash_array_primitive!(
                        Int32Array,
                        col,
                        i32,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Int64 => {
                    $crate::hash_array_primitive!(
                        Int64Array,
                        col,
                        i64,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Float32 => {
                    $crate::hash_array_primitive_float!(
                        Float32Array,
                        col,
                        f32,
                        i32,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Float64 => {
                    $crate::hash_array_primitive_float!(
                        Float64Array,
                        col,
                        f64,
                        i64,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Timestamp(TimeUnit::Second, _) => {
                    $crate::hash_array_primitive!(
                        TimestampSecondArray,
                        col,
                        i64,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Timestamp(TimeUnit::Millisecond, _) => {
                    $crate::hash_array_primitive!(
                        TimestampMillisecondArray,
                        col,
                        i64,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Timestamp(TimeUnit::Microsecond, _) => {
                    $crate::hash_array_primitive!(
                        TimestampMicrosecondArray,
                        col,
                        i64,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Timestamp(TimeUnit::Nanosecond, _) => {
                    $crate::hash_array_primitive!(
                        TimestampNanosecondArray,
                        col,
                        i64,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Date32 => {
                    $crate::hash_array_primitive!(
                        Date32Array,
                        col,
                        i32,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Date64 => {
                    $crate::hash_array_primitive!(
                        Date64Array,
                        col,
                        i64,
                        $hashes_buffer,
                        $hash_method
                    );
                }
                DataType::Utf8 => {
                    $crate::hash_array!(StringArray, col, $hashes_buffer, $hash_method);
                }
                DataType::LargeUtf8 => {
                    $crate::hash_array!(LargeStringArray, col, $hashes_buffer, $hash_method);
                }
                DataType::Binary => {
                    $crate::hash_array!(BinaryArray, col, $hashes_buffer, $hash_method);
                }
                DataType::LargeBinary => {
                    $crate::hash_array!(LargeBinaryArray, col, $hashes_buffer, $hash_method);
                }
                DataType::FixedSizeBinary(_) => {
                    $crate::hash_array!(FixedSizeBinaryArray, col, $hashes_buffer, $hash_method);
                }
                // Apache Spark: if it's a small decimal, i.e. precision <= 18, turn it into long and hash it.
                // Else, turn it into bytes and hash it.
                DataType::Decimal128(precision, _) if *precision <= 18 => {
                    $crate::hash_array_small_decimal!(Decimal128Array, col, $hashes_buffer, $hash_method);
                }
                DataType::Decimal128(_, _) => {
                    $crate::hash_array_decimal!(Decimal128Array, col, $hashes_buffer, $hash_method);
                }
                DataType::Dictionary(index_type, _) => match **index_type {
                    DataType::Int8 => {
                        $create_dictionary_hash_method::<Int8Type>(col, $hashes_buffer, first_col)?;
                    }
                    DataType::Int16 => {
                        $create_dictionary_hash_method::<Int16Type>(
                            col,
                            $hashes_buffer,
                            first_col,
                        )?;
                    }
                    DataType::Int32 => {
                        $create_dictionary_hash_method::<Int32Type>(
                            col,
                            $hashes_buffer,
                            first_col,
                        )?;
                    }
                    DataType::Int64 => {
                        $create_dictionary_hash_method::<Int64Type>(
                            col,
                            $hashes_buffer,
                            first_col,
                        )?;
                    }
                    DataType::UInt8 => {
                        $create_dictionary_hash_method::<UInt8Type>(
                            col,
                            $hashes_buffer,
                            first_col,
                        )?;
                    }
                    DataType::UInt16 => {
                        $create_dictionary_hash_method::<UInt16Type>(
                            col,
                            $hashes_buffer,
                            first_col,
                        )?;
                    }
                    DataType::UInt32 => {
                        $create_dictionary_hash_method::<UInt32Type>(
                            col,
                            $hashes_buffer,
                            first_col,
                        )?;
                    }
                    DataType::UInt64 => {
                        $create_dictionary_hash_method::<UInt64Type>(
                            col,
                            $hashes_buffer,
                            first_col,
                        )?;
                    }
                    _ => {
                        return Err(DataFusionError::Internal(format!(
                            "Unsupported dictionary type in hasher hashing: {}",
                            col.data_type(),
                        )))
                    }
                },
                DataType::List(field) => {
                    let list_array = col.as_any().downcast_ref::<ListArray>().unwrap();
                    let values = list_array.values();
                    let offsets = list_array.offsets();

                    $crate::hash_list_with_primitive_elements!(offsets: ListArray, list_array, values, offsets, field, $hashes_buffer, $hash_method, $recursive_hash_method, i32, col);
                }
                DataType::LargeList(field) => {
                    let list_array = col.as_any().downcast_ref::<LargeListArray>().unwrap();
                    let values = list_array.values();
                    let offsets = list_array.offsets();

                    $crate::hash_list_with_primitive_elements!(offsets: LargeListArray, list_array, values, offsets, field, $hashes_buffer, $hash_method, $recursive_hash_method, i64, col);
                }
                DataType::FixedSizeList(field, size) => {
                    let list_array = col.as_any().downcast_ref::<FixedSizeListArray>().unwrap();
                    let values = list_array.values();
                    let list_size = *size as usize;

                    $crate::hash_list_with_primitive_elements!(fixed_size: list_array, values, list_size, field, $hashes_buffer, $hash_method, $recursive_hash_method);
                }
                DataType::Struct(_) => {
                    let struct_array = col.as_any().downcast_ref::<StructArray>().unwrap();
                    // Hash each field of the struct - Spark hashes all fields recursively
                    let columns: Vec<ArrayRef> = struct_array.columns().to_vec();
                    if !columns.is_empty() {
                        $recursive_hash_method(&columns, $hashes_buffer)?;
                    }
                }
                DataType::Map(field, _) => {
                    let map_array = col.as_any().downcast_ref::<MapArray>().unwrap();
                    let keys = map_array.keys();
                    let values = map_array.values();
                    let offsets = map_array.offsets();

                    // Get key and value types from the struct field
                    if let DataType::Struct(fields) = field.data_type() {
                        let key_type = &fields[0].data_type();
                        let value_type = &fields[1].data_type();

                        // Specialize for common map key/value combinations
                        match (key_type, value_type) {
                            (DataType::Utf8, DataType::Int32) => {
                                let key_array = keys.as_any().downcast_ref::<StringArray>().unwrap();
                                let value_array = values.as_any().downcast_ref::<Int32Array>().unwrap();
                                if map_array.null_count() == 0 && key_array.null_count() == 0 && value_array.null_count() == 0 {
                                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                                        let start = offsets[row_idx] as usize;
                                        let end = offsets[row_idx + 1] as usize;
                                        for entry_idx in start..end {
                                            *hash = $hash_method(key_array.value(entry_idx), *hash);
                                            *hash = $hash_method(value_array.value(entry_idx).to_le_bytes(), *hash);
                                        }
                                    }
                                } else {
                                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                                        if !map_array.is_null(row_idx) {
                                            let start = offsets[row_idx] as usize;
                                            let end = offsets[row_idx + 1] as usize;
                                            for entry_idx in start..end {
                                                if !key_array.is_null(entry_idx) {
                                                    *hash = $hash_method(key_array.value(entry_idx), *hash);
                                                }
                                                if !value_array.is_null(entry_idx) {
                                                    *hash = $hash_method(value_array.value(entry_idx).to_le_bytes(), *hash);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            (DataType::Int32, DataType::Utf8) => {
                                let key_array = keys.as_any().downcast_ref::<Int32Array>().unwrap();
                                let value_array = values.as_any().downcast_ref::<StringArray>().unwrap();
                                if map_array.null_count() == 0 && key_array.null_count() == 0 && value_array.null_count() == 0 {
                                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                                        let start = offsets[row_idx] as usize;
                                        let end = offsets[row_idx + 1] as usize;
                                        for entry_idx in start..end {
                                            *hash = $hash_method(key_array.value(entry_idx).to_le_bytes(), *hash);
                                            *hash = $hash_method(value_array.value(entry_idx), *hash);
                                        }
                                    }
                                } else {
                                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                                        if !map_array.is_null(row_idx) {
                                            let start = offsets[row_idx] as usize;
                                            let end = offsets[row_idx + 1] as usize;
                                            for entry_idx in start..end {
                                                if !key_array.is_null(entry_idx) {
                                                    *hash = $hash_method(key_array.value(entry_idx).to_le_bytes(), *hash);
                                                }
                                                if !value_array.is_null(entry_idx) {
                                                    *hash = $hash_method(value_array.value(entry_idx), *hash);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            (DataType::Utf8, DataType::Utf8) => {
                                let key_array = keys.as_any().downcast_ref::<StringArray>().unwrap();
                                let value_array = values.as_any().downcast_ref::<StringArray>().unwrap();
                                if map_array.null_count() == 0 && key_array.null_count() == 0 && value_array.null_count() == 0 {
                                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                                        let start = offsets[row_idx] as usize;
                                        let end = offsets[row_idx + 1] as usize;
                                        for entry_idx in start..end {
                                            *hash = $hash_method(key_array.value(entry_idx), *hash);
                                            *hash = $hash_method(value_array.value(entry_idx), *hash);
                                        }
                                    }
                                } else {
                                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                                        if !map_array.is_null(row_idx) {
                                            let start = offsets[row_idx] as usize;
                                            let end = offsets[row_idx + 1] as usize;
                                            for entry_idx in start..end {
                                                if !key_array.is_null(entry_idx) {
                                                    *hash = $hash_method(key_array.value(entry_idx), *hash);
                                                }
                                                if !value_array.is_null(entry_idx) {
                                                    *hash = $hash_method(value_array.value(entry_idx), *hash);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            (DataType::Int32, DataType::Int32) => {
                                let key_array = keys.as_any().downcast_ref::<Int32Array>().unwrap();
                                let value_array = values.as_any().downcast_ref::<Int32Array>().unwrap();
                                if map_array.null_count() == 0 && key_array.null_count() == 0 && value_array.null_count() == 0 {
                                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                                        let start = offsets[row_idx] as usize;
                                        let end = offsets[row_idx + 1] as usize;
                                        for entry_idx in start..end {
                                            *hash = $hash_method(key_array.value(entry_idx).to_le_bytes(), *hash);
                                            *hash = $hash_method(value_array.value(entry_idx).to_le_bytes(), *hash);
                                        }
                                    }
                                } else {
                                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                                        if !map_array.is_null(row_idx) {
                                            let start = offsets[row_idx] as usize;
                                            let end = offsets[row_idx + 1] as usize;
                                            for entry_idx in start..end {
                                                if !key_array.is_null(entry_idx) {
                                                    *hash = $hash_method(key_array.value(entry_idx).to_le_bytes(), *hash);
                                                }
                                                if !value_array.is_null(entry_idx) {
                                                    *hash = $hash_method(value_array.value(entry_idx).to_le_bytes(), *hash);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            _ => {
                                // Fall back to recursive approach for other type combinations
                                if map_array.null_count() == 0 {
                                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                                        let start = offsets[row_idx] as usize;
                                        let end = offsets[row_idx + 1] as usize;
                                        for entry_idx in start..end {
                                            let key_array = keys.slice(entry_idx, 1);
                                            let mut single_hash = [*hash];
                                            $recursive_hash_method(&[key_array], &mut single_hash)?;
                                            *hash = single_hash[0];

                                            let value_array = values.slice(entry_idx, 1);
                                            single_hash = [*hash];
                                            $recursive_hash_method(&[value_array], &mut single_hash)?;
                                            *hash = single_hash[0];
                                        }
                                    }
                                } else {
                                    for (row_idx, hash) in $hashes_buffer.iter_mut().enumerate() {
                                        if !map_array.is_null(row_idx) {
                                            let start = offsets[row_idx] as usize;
                                            let end = offsets[row_idx + 1] as usize;
                                            for entry_idx in start..end {
                                                let key_array = keys.slice(entry_idx, 1);
                                                let mut single_hash = [*hash];
                                                $recursive_hash_method(&[key_array], &mut single_hash)?;
                                                *hash = single_hash[0];

                                                let value_array = values.slice(entry_idx, 1);
                                                single_hash = [*hash];
                                                $recursive_hash_method(&[value_array], &mut single_hash)?;
                                                *hash = single_hash[0];
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } else {
                        return Err(DataFusionError::Internal(format!(
                            "Map field type must be a struct, got: {}",
                            field.data_type()
                        )));
                    }
                }
                _ => {
                    // This is internal because we should have caught this before.
                    return Err(DataFusionError::Internal(format!(
                        "Unsupported data type in hasher: {}",
                        col.data_type()
                    )));
                }
            }
        }
    };
}

pub(crate) mod test_utils {

    #[macro_export]
    macro_rules! test_hashes_internal {
        ($hash_method: ident, $input: expr, $initial_seeds: expr, $expected: expr) => {
            let i = $input;
            let mut hashes = $initial_seeds.clone();
            $hash_method(&[i], &mut hashes).unwrap();
            assert_eq!(hashes, $expected);
        };
    }

    #[macro_export]
    macro_rules! test_hashes_with_nulls {
        ($method: ident, $t: ty, $values: ident, $expected: ident, $seed_type: ty) => {
            // copied before inserting nulls
            let mut input_with_nulls = $values.clone();
            let mut expected_with_nulls = $expected.clone();
            // test before inserting nulls
            let len = $values.len();
            let initial_seeds = vec![42 as $seed_type; len];
            let i = Arc::new(<$t>::from($values)) as ArrayRef;
            $crate::test_hashes_internal!($method, i, initial_seeds, $expected);

            // test with nulls
            let median = len / 2;
            input_with_nulls.insert(0, None);
            input_with_nulls.insert(median, None);
            expected_with_nulls.insert(0, 42 as $seed_type);
            expected_with_nulls.insert(median, 42 as $seed_type);
            let len_with_nulls = len + 2;
            let initial_seeds_with_nulls = vec![42 as $seed_type; len_with_nulls];
            let nullable_input = Arc::new(<$t>::from(input_with_nulls)) as ArrayRef;
            $crate::test_hashes_internal!(
                $method,
                nullable_input,
                initial_seeds_with_nulls,
                expected_with_nulls
            );
        };
    }
}
