--[[----------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2025 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
and its suppliers and are protected by all applicable intellectual
property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Adobe.

------------------------------------------------------------------------------]]

	-- WF.core.test
local WFRxTestCase = require 'WFRxTestCase'

	-- WF.core.utils
local WFVectorUtils = require 'WFVectorUtils'

--------------------------------------------------------------------------------

local WFVectorUtilsTest = {}

--------------------------------------------------------------------------------
--@mark Helper Functions
--------------------------------------------------------------------------------

-- Helper function for floating-point comparison

local function isClose( a, b, epsilon )

	epsilon = epsilon or 1e-5
	local diff = math.abs( a - b )
	return diff <= epsilon

end

--------------------------------------------------------------------------------

local function toFP16( x )

	-- Convert to fp16 by truncating to 16-bit precision
	-- This is a simplified version - in practice you'd want to handle
	-- exponent and mantissa properly

	return math.floor( x * 65536 ) / 65536

end

--------------------------------------------------------------------------------

local function toFP8( x )

	-- Convert to fp8 by truncating to 8-bit precision
	-- This is a simplified version - in practice you'd want to handle
	-- exponent and mantissa properly

	return math.floor( x * 256 ) / 256

end

--------------------------------------------------------------------------------

local function toInt32( x )

	-- Convert to int32 by scaling and rounding
	-- Scale to use full int32 range (-2^31 to 2^31-1)

	return math.floor( x * 2147483647 )

end

--------------------------------------------------------------------------------

local function convertVector( vec, converter )

	local result = {}

	for i = 1, #vec do
		result[ i ] = converter( vec[ i ] )
	end

	return result

end

--------------------------------------------------------------------------------

-- Helper function to create a point with small variations

local function createPoint( baseValue, dimensions, maxVariation )

	local point = {}
	maxVariation = maxVariation or 0.1

	for i = 1, dimensions do

		-- Scale variation proportionally to base value

		local variation = 0

		if baseValue ~= 0 then
			variation = math.random() * maxVariation * math.abs( baseValue )
		end

		if baseValue < 0 then
			variation = -variation  -- Keep negative for negative base values
		end

		point[ i ] = baseValue + variation

	end

	return point

end

--------------------------------------------------------------------------------
--@mark Dot Product & Cosine Similarity & Normalization Testing
--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestBasicOperations( rxTest )

	-- Test dot product

	local a = { 1, 2, 3 }
	local b = { 4, 5, 6 }
	local dot = WFVectorUtils.dotProduct( a, b )
	rxTest:expectEqual( 32, dot ) -- 1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32

	-- Test cosine similarity with orthogonal vectors

	local v1 = { 1, 0, 0 }
	local v2 = { 0, 1, 0 }
	local similarity = WFVectorUtils.cosineSimilarity( v1, v2 )
	rxTest:expectEqual( 0, similarity ) -- Orthogonal vectors have similarity 0

	-- Test cosine similarity with identical vectors

	local v3 = { 1, 2, 3 }
	local v4 = { 1, 2, 3 }
	local similarity2 = WFVectorUtils.cosineSimilarity( v3, v4 )
	rxTest:expectEqual( true, isClose( similarity2, 1 ) ) -- Identical vectors have similarity 1

	-- Test cosine distance

	local distance = WFVectorUtils.cosineDistance( v1, v2 )
	rxTest:expectEqual( true, isClose( distance, 1 ) ) -- Orthogonal vectors have distance 1

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestCosineSimilarityScaling( rxTest )

	-- Test that cosine similarity is invariant to scaling

	local v1 = { 1, 2, 3 }
	local v2 = { 2, 4, 6 } -- v2 = 2 * v1
	local similarity1 = WFVectorUtils.cosineSimilarity( v1, v1 )
	local similarity2 = WFVectorUtils.cosineSimilarity( v1, v2 )
	rxTest:expectEqual( true, isClose( similarity1, similarity2 ) )

	-- Test with negative scaling

	local v3 = { -1, -2, -3 } -- v3 = -1 * v1
	local similarity3 = WFVectorUtils.cosineSimilarity( v1, v3 )
	rxTest:expectEqual( true, isClose( similarity1, -similarity3 ) ) -- Should be opposite direction

	-- Test with different scaling factors

	local v4 = { 0.5, 1, 1.5 } -- v4 = 0.5 * v1
	local similarity4 = WFVectorUtils.cosineSimilarity( v1, v4 )
	rxTest:expectEqual( true, isClose( similarity1, similarity4 ) )

	-- Test with very small scaling

	local v5 = { 1e-10, 2e-10, 3e-10 } -- v5 = 1e-10 * v1
	local similarity5 = WFVectorUtils.cosineSimilarity( v1, v5 )
	rxTest:expectEqual( true, isClose( similarity1, similarity5 ) )

	-- Test with very large scaling

	local v6 = { 1e10, 2e10, 3e10 } -- v6 = 1e10 * v1
	local similarity6 = WFVectorUtils.cosineSimilarity( v1, v6 )
	rxTest:expectEqual( true, isClose( similarity1, similarity6 ) )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestEdgeCases( rxTest )

	-- Test empty vectors

	local empty = {}
	local success = pcall( WFVectorUtils.dotProduct, empty, empty )
	rxTest:expectEqual( true, success )

	-- Test vectors of different lengths

	local v1 = { 1, 2, 3 }
	local v2 = { 1, 2 }
	success = pcall( WFVectorUtils.cosineSimilarity, v1, v2 )
	rxTest:expectEqual( false, success ) -- Vectors must have the same length

	-- Test zero vectors

	local zero = { 0, 0, 0 }
	local v3 = { 1, 2, 3 }
	success = pcall( WFVectorUtils.cosineSimilarity, zero, v3 )
	rxTest:expectEqual( false, success ) -- Division by zero

	-- Test with very small numbers

	local v4 = { 1e-10, 2e-10, 3e-10 }
	local v5 = { 2e-10, 4e-10, 6e-10 }
	local similarity = WFVectorUtils.cosineSimilarity( v4, v5 )
	rxTest:expectEqual( true, isClose( similarity, 1 ) )

	-- Test with very large numbers

	local v6 = { 1e10, 2e10, 3e10 }
	local v7 = { 2e10, 4e10, 6e10 }
	local similarity2 = WFVectorUtils.cosineSimilarity( v6, v7 )
	rxTest:expectEqual( true, isClose( similarity2, 1 ) )

	-- Test with mixed signs

	local v8 = { 1, -2, 3 }
	local v9 = { -1, 2, -3 }
	local similarity3 = WFVectorUtils.cosineSimilarity( v8, v9 )
	rxTest:expectEqual( true, isClose( similarity3, -1 ) ) -- Opposite vectors have similarity -1

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestSIMDEdgeCases( rxTest )

	-- Test vector sizes that trigger the remainder path in SIMD implementations
	-- These sizes are not divisible by 4 (SSE/NEON) or 8 (AVX2)

	local testSizes = { 3, 5, 7, 10, 13, 15 }

	for _, size in ipairs( testSizes ) do

		local v1 = createPoint( math.random(), size )
		local v2 = createPoint( math.random(), size )

		-- These should not crash and should match Lua implementation

		local result = WFVectorUtils.cosineSimilarity( v1, v2 )
		local expected = WFVectorUtils.cosineSimilarityLua( v1, v2 )
		rxTest:expectEqual( true, isClose( result, expected ) )

		-- Also test dot product

		local dotResult = WFVectorUtils.dotProduct( v1, v2 )
		local dotExpected = WFVectorUtils.dotProductLua( v1, v2 )
		rxTest:expectEqual( true, isClose( dotResult, dotExpected ) )

	end

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestCompareDotProductVariants( rxTest )

	-- Test with small vectors

	local v1 = { 1, 2, 3 }
	local v2 = { 4, 5, 6 }

	local dot1 = WFVectorUtils.dotProduct( v1, v2 )
	local dot2 = WFVectorUtils.dotProductLua( v1, v2 )
	local dot3 = WFVectorUtils._test_dotProductAlternate( v1, v2 )

	rxTest:expectEqual( true, isClose( dot1, dot2 ) )
	rxTest:expectEqual( true, isClose( dot1, dot3 ) )
	rxTest:expectEqual( true, isClose( dot2, dot3 ) )

	-- Test with larger vectors

	local size = 100
	local v3 = {}
	local v4 = {}

	for i = 1, size do
		v3[ i ] = i
		v4[ i ] = size - i + 1
	end

	local dot4 = WFVectorUtils.dotProduct( v3, v4 )
	local dot5 = WFVectorUtils.dotProductLua( v3, v4 )
	local dot6 = WFVectorUtils._test_dotProductAlternate( v3, v4 )

	rxTest:expectEqual( true, isClose( dot4, dot5 ) )
	rxTest:expectEqual( true, isClose( dot4, dot6 ) )
	rxTest:expectEqual( true, isClose( dot5, dot6 ) )

	-- Test with integers

	local size2 = 100
	local v5 = {}
	local v6 = {}

	for i = 1, size2 do
		v5[ i ] = i
		v6[ i ] = size2 - i + 1
	end

	local dot7 = WFVectorUtils.dotProduct( v5, v6 )
	local dot8 = WFVectorUtils.dotProductLua( v5, v6 )
	local dot9 = WFVectorUtils._test_dotProductAlternate( v5, v6 )

	rxTest:expectEqual( true, isClose( dot7, dot8 ) )
	rxTest:expectEqual( true, isClose( dot7, dot9 ) )
	rxTest:expectEqual( true, isClose( dot8, dot9 ) )

	-- Test with floating point values

	local v7 = { 1.5, 2.5, 3.5 }
	local v8 = { 4.5, 5.5, 6.5 }

	local dot10 = WFVectorUtils.dotProduct( v7, v8 )
	local dot11 = WFVectorUtils.dotProductLua( v7, v8 )
	local dot12 = WFVectorUtils._test_dotProductAlternate( v7, v8 )

	rxTest:expectEqual( true, isClose( dot10, dot11 ) )
	rxTest:expectEqual( true, isClose( dot10, dot12 ) )
	rxTest:expectEqual( true, isClose( dot11, dot12 ) )

	-- Test with negative values

	local v9 = { -1, 2, -3 }
	local v10 = { 4, -5, 6 }

	local dot13 = WFVectorUtils.dotProduct( v9, v10 )
	local dot14 = WFVectorUtils.dotProductLua( v9, v10 )
	local dot15 = WFVectorUtils._test_dotProductAlternate( v9, v10 )

	rxTest:expectEqual( true, isClose( dot13, dot14 ) )
	rxTest:expectEqual( true, isClose( dot13, dot15 ) )
	rxTest:expectEqual( true, isClose( dot14, dot15 ) )

	-- Test with very small values

	local v11 = { 1e-10, 2e-10, 3e-10 }
	local v12 = { 4e-10, 5e-10, 6e-10 }

	local dot16 = WFVectorUtils.dotProduct( v11, v12 )
	local dot17 = WFVectorUtils.dotProductLua( v11, v12 )
	local dot18 = WFVectorUtils._test_dotProductAlternate( v11, v12 )

	rxTest:expectEqual( true, isClose( dot16, dot17 ) )
	rxTest:expectEqual( true, isClose( dot16, dot18 ) )
	rxTest:expectEqual( true, isClose( dot17, dot18 ) )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestCompareCosineSimilarityVariants( rxTest )

	-- Test with small vectors

	local v1 = { 1, 2, 3 }
	local v2 = { 4, 5, 6 }

	local sim1 = WFVectorUtils.cosineSimilarity( v1, v2 )
	local sim2 = WFVectorUtils.cosineSimilarityLua( v1, v2 )
	local sim3 = WFVectorUtils._test_cosineSimilarityAlternate( v1, v2 )

	rxTest:expectEqual( true, isClose( sim1, sim2 ) )
	rxTest:expectEqual( true, isClose( sim1, sim3 ) )
	rxTest:expectEqual( true, isClose( sim2, sim3 ) )

	-- Test with larger vectors

	local size = 100
	local v3 = {}
	local v4 = {}

	for i = 1, size do
		v3[ i ] = i
		v4[ i ] = size - i + 1
	end

	local sim4 = WFVectorUtils.cosineSimilarity( v3, v4 )
	local sim5 = WFVectorUtils.cosineSimilarityLua( v3, v4 )
	local sim6 = WFVectorUtils._test_cosineSimilarityAlternate( v3, v4 )

	rxTest:expectEqual( true, isClose( sim4, sim5 ) )
	rxTest:expectEqual( true, isClose( sim4, sim6 ) )
	rxTest:expectEqual( true, isClose( sim5, sim6 ) )

	-- Test with integers

	local size2 = 100
	local v5 = {}
	local v6 = {}

	for i = 1, size2 do
		v5[ i ] = i
		v6[ i ] = size2 - i + 1
	end

	local sim7 = WFVectorUtils.cosineSimilarity( v5, v6 )
	local sim8 = WFVectorUtils.cosineSimilarityLua( v5, v6 )
	local sim9 = WFVectorUtils._test_cosineSimilarityAlternate( v5, v6 )

	rxTest:expectEqual( true, isClose( sim7, sim8 ) )
	rxTest:expectEqual( true, isClose( sim7, sim9 ) )
	rxTest:expectEqual( true, isClose( sim8, sim9 ) )

	-- Test with floating point values

	-- Test with floating point values
	local v7 = { 1.5, 2.5, 3.5 }
	local v8 = { 4.5, 5.5, 6.5 }

	local sim10 = WFVectorUtils.cosineSimilarity( v7, v8 )
	local sim11 = WFVectorUtils.cosineSimilarityLua( v7, v8 )
	local sim12 = WFVectorUtils._test_cosineSimilarityAlternate( v7, v8 )

	rxTest:expectEqual( true, isClose( sim10, sim11 ) )
	rxTest:expectEqual( true, isClose( sim10, sim12 ) )
	rxTest:expectEqual( true, isClose( sim11, sim12 ) )

	-- Test with negative values

	local v9 = { -1, 2, -3 }
	local v10 = { 4, -5, 6 }

	local sim13 = WFVectorUtils.cosineSimilarity( v9, v10 )
	local sim14 = WFVectorUtils.cosineSimilarityLua( v9, v10 )
	local sim15 = WFVectorUtils._test_cosineSimilarityAlternate( v9, v10 )

	rxTest:expectEqual( true, isClose( sim13, sim14 ) )
	rxTest:expectEqual( true, isClose( sim13, sim15 ) )
	rxTest:expectEqual( true, isClose( sim14, sim15 ) )

	-- Test with very small values

	local v11 = { 1e-10, 2e-10, 3e-10 }
	local v12 = { 4e-10, 5e-10, 6e-10 }

	local sim16 = WFVectorUtils.cosineSimilarity( v11, v12 )
	local sim17 = WFVectorUtils.cosineSimilarityLua( v11, v12 )
	local sim18 = WFVectorUtils._test_cosineSimilarityAlternate( v11, v12 )

	rxTest:expectEqual( true, isClose( sim16, sim17 ) )
	rxTest:expectEqual( true, isClose( sim16, sim18 ) )
	rxTest:expectEqual( true, isClose( sim17, sim18 ) )

	-- Test with orthogonal vectors

	local v13 = { 1, 0, 0 }
	local v14 = { 0, 1, 0 }

	local sim19 = WFVectorUtils.cosineSimilarity( v13, v14 )
	local sim20 = WFVectorUtils.cosineSimilarityLua( v13, v14 )
	local sim21 = WFVectorUtils._test_cosineSimilarityAlternate( v13, v14 )

	rxTest:expectEqual( true, isClose( sim19, sim20 ) )
	rxTest:expectEqual( true, isClose( sim19, sim21 ) )
	rxTest:expectEqual( true, isClose( sim20, sim21 ) )
	rxTest:expectEqual( true, isClose( sim19, 0 ) ) -- Orthogonal vectors should have similarity 0

	-- Test with identical vectors

	local v15 = { 1, 2, 3 }
	local v16 = { 1, 2, 3 }

	local sim22 = WFVectorUtils.cosineSimilarity( v15, v16 )
	local sim23 = WFVectorUtils.cosineSimilarityLua( v15, v16 )
	local sim24 = WFVectorUtils._test_cosineSimilarityAlternate( v15, v16 )

	rxTest:expectEqual( true, isClose( sim22, sim23 ) )
	rxTest:expectEqual( true, isClose( sim22, sim24 ) )
	rxTest:expectEqual( true, isClose( sim23, sim24 ) )
	rxTest:expectEqual( true, isClose( sim22, 1 ) ) -- Identical vectors should have similarity 1

	-- Test with opposite vectors

	local v17 = { 1, 2, 3 }
	local v18 = { -1, -2, -3 }

	local sim25 = WFVectorUtils.cosineSimilarity( v17, v18 )
	local sim26 = WFVectorUtils.cosineSimilarityLua( v17, v18 )
	local sim27 = WFVectorUtils._test_cosineSimilarityAlternate( v17, v18 )

	rxTest:expectEqual( true, isClose( sim25, sim26 ) )
	rxTest:expectEqual( true, isClose( sim25, sim27 ) )
	rxTest:expectEqual( true, isClose( sim26, sim27 ) )
	rxTest:expectEqual( true, isClose( sim25, -1 ) ) -- Opposite vectors should have similarity -1

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDotProduct_512D( rxTest )

	rxTest:setTimeLimit( 10 )

	-- Generate test vectors

	local v1 = createPoint( math.random(), 512 )
	local v2 = createPoint( 0, 512 )
	
	-- Compare all three implementations

	local result1 = WFVectorUtils.dotProduct( v1, v2 )
	local result2 = WFVectorUtils.dotProductLua( v1, v2 )
	local result3 = WFVectorUtils._test_dotProductAlternate( v1, v2 )

	rxTest:expectEqual( true, isClose( result1, result2 ) )
	rxTest:expectEqual( true, isClose( result1, result3 ) )
	rxTest:expectEqual( true, isClose( result2, result3 ) )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestCosineSimilarity_512D( rxTest )

	rxTest:setTimeLimit( 10 )

	-- Generate test vectors

	local v1 = createPoint( math.random(), 512 )
	local v2 = createPoint( math.random(), 512 )
	
	-- Compare C++ and Lua implementations

	local result = WFVectorUtils.cosineSimilarity( v1, v2 )
	local expected = WFVectorUtils.cosineSimilarityLua( v1, v2 )
	rxTest:expectEqual( true, isClose( result, expected ) )
	
	-- Large numbers test

	v1 = createPoint( 3e16, 512 )
	v2 = createPoint( 7e16, 512 )
	
	-- Compare C++ and Lua implementations

	result = WFVectorUtils.cosineSimilarity( v1, v2 )
	expected = WFVectorUtils.cosineSimilarityLua( v1, v2 )
	rxTest:expectEqual( true, isClose( result, expected ) )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDotProductDifferentFormats( rxTest )

	rxTest:setTimeLimit( 10 )

	-- Generate test vectors

	local v1 = createPoint( math.random(), 512 )
	local v2 = createPoint( math.random(), 512 )

	-- Convert to different formats

	local v1_fp16 = convertVector( v1, toFP16 )
	local v2_fp16 = convertVector( v2, toFP16 )
	local v1_fp8 = convertVector( v1, toFP8 )
	local v2_fp8 = convertVector( v2, toFP8 )

	-- Compare results across formats

	local result_fp32 = WFVectorUtils.dotProduct( v1, v2 )
	local result_fp16 = WFVectorUtils.dotProduct( v1_fp16, v2_fp16 )
	local result_fp8 = WFVectorUtils.dotProduct( v1_fp8, v2_fp8 )

	-- Allow for larger error margins with lower precision formats

	rxTest:expectEqual( true, isClose( result_fp32, result_fp16, 1e-2 ) )
	rxTest:expectEqual( true, isClose( result_fp32, result_fp8, 3 ) )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestCosineSimilarityDifferentFormats( rxTest )

	rxTest:setTimeLimit( 10 )

	-- Generate test vectors

	local v1 = createPoint( math.random(), 512 )
	local v2 = createPoint( math.random(), 512 )

	-- Convert to different formats

	local v1_fp16 = convertVector( v1, toFP16 )
	local v2_fp16 = convertVector( v2, toFP16 )
	local v1_fp8 = convertVector( v1, toFP8 )
	local v2_fp8 = convertVector( v2, toFP8 )
	local v1_int32 = convertVector( v1, toInt32 )
	local v2_int32 = convertVector( v2, toInt32 )

	-- Compare results across formats

	local result_fp32 = WFVectorUtils.cosineSimilarity( v1, v2 )
	local result_fp16 = WFVectorUtils.cosineSimilarity( v1_fp16, v2_fp16 )
	local result_fp8 = WFVectorUtils.cosineSimilarity( v1_fp8, v2_fp8 )
	local result_int32 = WFVectorUtils.cosineSimilarity( v1_int32, v2_int32 )

	-- Allow for larger error margins with lower precision formats

	rxTest:expectEqual( true, isClose( result_fp32, result_fp16, 1e-2 ) )
	rxTest:expectEqual( true, isClose( result_fp32, result_fp8, 1e-1 ) )
	rxTest:expectEqual( true, isClose( result_fp32, result_int32, 1 ) )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestVectorNormalization( rxTest )

	-- Test with a simple 2D vector

	local v1 = { 3, 4 }
	local normalized1 = WFVectorUtils.normalizeVector( v1 )
	local normalized1_lua = WFVectorUtils.normalizeVectorLua( v1 )
	local normalized1_alt = WFVectorUtils._test_normalizeVectorAlternate( v1 )
	rxTest:expectEqual( true, isClose( normalized1[ 1 ], normalized1_lua[ 1 ] ) )
	rxTest:expectEqual( true, isClose( normalized1[ 2 ], normalized1_lua[ 2 ] ) )
	rxTest:expectEqual( true, isClose( normalized1[ 1 ], normalized1_alt[ 1 ] ) )
	rxTest:expectEqual( true, isClose( normalized1[ 2 ], normalized1_alt[ 2 ] ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized1 ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized1_lua ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized1_alt ) )
	
	-- Test with a 3D vector

	local v2 = { 1, 2, 2 }
	local normalized2 = WFVectorUtils.normalizeVector( v2 )
	local normalized2_lua = WFVectorUtils.normalizeVectorLua( v2 )
	local normalized2_alt = WFVectorUtils._test_normalizeVectorAlternate( v2 )
	rxTest:expectEqual( true, isClose( normalized2[ 1 ], normalized2_lua[ 1 ] ) )
	rxTest:expectEqual( true, isClose( normalized2[ 2 ], normalized2_lua[ 2 ] ) )
	rxTest:expectEqual( true, isClose( normalized2[ 3 ], normalized2_lua[ 3 ] ) )
	rxTest:expectEqual( true, isClose( normalized2[ 1 ], normalized2_alt[ 1 ] ) )
	rxTest:expectEqual( true, isClose( normalized2[ 2 ], normalized2_alt[ 2 ] ) )
	rxTest:expectEqual( true, isClose( normalized2[ 3 ], normalized2_alt[ 3 ] ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized2 ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized2_lua ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized2_alt ) )
	
	-- Test with a high-dimensional vector

	local v3 = {}
	for i = 1, 512 do
		v3[ i ] = i
	end
	local normalized3 = WFVectorUtils.normalizeVector( v3 )
	local normalized3_lua = WFVectorUtils.normalizeVectorLua( v3 )
	local normalized3_alt = WFVectorUtils._test_normalizeVectorAlternate( v3 )
	
	-- Compare results

	for i = 1, #normalized3 do
		rxTest:expectEqual( true, isClose( normalized3[ i ], normalized3_lua[ i ] ) )
		rxTest:expectEqual( true, isClose( normalized3[ i ], normalized3_alt[ i ] ) )
	end
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized3 ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized3_lua ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized3_alt ) )
	
	-- Test with very small numbers

	local v4 = { 1e-10, 2e-10, 3e-10 }
	local normalized4 = WFVectorUtils.normalizeVector( v4 )
	local normalized4_lua = WFVectorUtils.normalizeVectorLua( v4 )
	local normalized4_alt = WFVectorUtils._test_normalizeVectorAlternate( v4 )
	for i = 1, #normalized4 do
		rxTest:expectEqual( true, isClose( normalized4[ i ], normalized4_lua[ i ] ) )
		rxTest:expectEqual( true, isClose( normalized4[ i ], normalized4_alt[ i ] ) )
	end
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized4 ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized4_lua ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized4_alt ) )
	
	-- Test with very large numbers

	local v5 = { 1e10, 2e10, 3e10 }
	local normalized5 = WFVectorUtils.normalizeVector( v5 )
	local normalized5_lua = WFVectorUtils.normalizeVectorLua( v5 )
	local normalized5_alt = WFVectorUtils._test_normalizeVectorAlternate( v5 )
	for i = 1, #normalized5 do
		rxTest:expectEqual( true, isClose( normalized5[ i ], normalized5_lua[ i ] ) )
		rxTest:expectEqual( true, isClose( normalized5[ i ], normalized5_alt[ i ] ) )
	end
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized5 ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized5_lua ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized5_alt ) )
	
	-- Test with negative numbers

	local v6 = { -1, -2, -2 }
	local normalized6 = WFVectorUtils.normalizeVector( v6 )
	local normalized6_lua = WFVectorUtils.normalizeVectorLua( v6 )
	local normalized6_alt = WFVectorUtils._test_normalizeVectorAlternate( v6 )
	for i = 1, #normalized6 do
		rxTest:expectEqual( true, isClose( normalized6[ i ], normalized6_lua[ i ] ) )
		rxTest:expectEqual( true, isClose( normalized6[ i ], normalized6_alt[ i ] ) )
	end
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized6 ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized6_lua ) )
	rxTest:expectEqual( true, WFVectorUtils.isNormalized( normalized6_alt ) )

end

--------------------------------------------------------------------------------
--@mark DBSCAN Testing
--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDBSCAN_2D( rxTest )

	-- Create a simple dataset with two clear clusters

	local embeddings = {
		{ 0.7071, 0.7071 }, -- Cluster 1 (normalized)
		{ 0.7071, 0.7071 },
		{ 0.7071, 0.7071 },
		{ 0.7071, 0.7071 },
		{ -0.7071, 0.7071 }, -- Cluster 2 (normalized)
		{ -0.7071, 0.7071 },
		{ -0.7071, 0.7071 },
		{ -0.7071, 0.7071 },
		{ 0.7071, -0.7071 },  -- Noise point (normalized)
		{ 0.7071, -0.7071 }   -- Noise point (normalized)
	}

	-- Test with appropriate threshold (should find 2 clusters)

	local clusters = WFVectorUtils.dbscan( embeddings, 0.2, 3 )
	rxTest:expectEqual( 2, #clusters ) -- Should find 2 clusters
	rxTest:expectEqual( 4, #clusters[ 1 ] ) -- First cluster should have 4 points
	rxTest:expectEqual( 4, #clusters[ 2 ] ) -- Second cluster should have 4 points

	-- Test with very large threshold (should find 1 cluster)

	local clusters2 = WFVectorUtils.dbscan( embeddings, 2, 3 )
	rxTest:expectEqual( 1, #clusters2 ) -- Should find 1 cluster
	rxTest:expectEqual( 10, #clusters2[ 1 ] ) -- All points should be in one cluster

	-- Test with minPoints = 2 (noise points becomes a cluster)

	local clusters3 = WFVectorUtils.dbscan( embeddings, 0.2, 2 )
	rxTest:expectEqual( 3, #clusters3 ) -- Should find 2 clusters plus noise point as cluster

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDBSCAN_10D( rxTest )

	local dimensions = 10

	-- Create a simple dataset with two clear clusters

	local embeddings = {}
	
	-- Cluster 1: Points around (1,1,1,...,1)

	local maxVariation = 0.01
	for i = 1, 4 do
		table.insert( embeddings, createPoint( 1.0, dimensions, maxVariation ) )
	end
	
	-- Cluster 2: Points around (-1,-1,-1,...,-1)

	for i = 1, 4 do
		table.insert( embeddings, createPoint( -1.0, dimensions, maxVariation ) )
	end

	-- Normalize all vectors

	local normalized_embeddings = {}
	for _, vec in ipairs( embeddings ) do
		table.insert( normalized_embeddings, WFVectorUtils.normalizeVector( vec ) )
	end

	-- Find Clusters with minPoints = 4

	local clusters = WFVectorUtils.dbscan( normalized_embeddings, 0.1, 4 )
	rxTest:expectEqual( 2, #clusters )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDBSCAN_512D( rxTest )

	-- Create a 512D dataset with two clear clusters

	local embeddings = {}

	-- Cluster 1: Points around (1,1,1,...,1)

	for i = 1, 4 do
		table.insert( embeddings, createPoint( 0.9, 512 ) )
	end

	-- Cluster 2: Points around (-1,-1,-1,...,-1)

	for i = 1, 4 do
		table.insert( embeddings, createPoint( -0.9, 512 ) )
	end

	-- Normalize all vectors

	local normalized_embeddings = {}
	for _, vec in ipairs( embeddings ) do
		table.insert( normalized_embeddings, WFVectorUtils.normalizeVector( vec ) )
	end

	-- Test with appropriate threshold (should find 2 clusters)

	local clusters = WFVectorUtils.dbscan( normalized_embeddings, 0.1, 3 )
	rxTest:expectEqual( 2, #clusters ) -- Should find 2 clusters
	rxTest:expectEqual( 4, #clusters[ 1 ] ) -- First cluster should have 4 points
	rxTest:expectEqual( 4, #clusters[ 2 ] ) -- Second cluster should have 4 points

	-- Test with very large threshold (should find 1 cluster)

	local clusters2 = WFVectorUtils.dbscan( normalized_embeddings, 2, 3 )
	rxTest:expectEqual( 1, #clusters2 ) -- Should find 1 cluster
	rxTest:expectEqual( 8, #clusters2[ 1 ] ) -- All points should be in one cluster

end

--------------------------------------------------------------------------------
--@mark  Performance Testing
--------------------------------------------------------------------------------

local function testDBSCANPerformance( rxTest, numPoints, dimensions )

	rxTest:setTimeLimit( 500 )

	local numClusters = 5

	-- Helper function to generate a dataset

	local function generateDataset( numPoints, dimensions )

		local embeddings = {}
		local pointsPerCluster = math.floor( numPoints / numClusters )
		
		-- Generate clusters
	
		for i = 1, numClusters do
	
			local baseValue = ( i - 1 ) * 0.5  -- Space clusters out
	
			for j = 1, pointsPerCluster do
				table.insert( embeddings, createPoint( baseValue, dimensions ) )
			end

		end

		-- Normalize all vectors

		local normalized_embeddings = {}
		for _, vec in ipairs( embeddings ) do
			table.insert( normalized_embeddings, WFVectorUtils.normalizeVector( vec ) )
		end

		return normalized_embeddings

	end

	-- Generate dataset

	local embeddings = generateDataset( numPoints, dimensions )
	
	-- Use fixed epsilon value that works for normalized vectors

	local epsilon = 0.1
	
	-- Test performance

	rxTest:measurePerformance( function()
		WFVectorUtils.dbscan( embeddings, epsilon, 2 )
	end )

end

--------------------------------------------------------------------------------

local kSmall = 100
local kMedium = kSmall * 10
local kLarge = kMedium * 10

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDBSCAN_512D_Small_testPerformance( rxTest )

	testDBSCANPerformance( rxTest, kSmall, 512 )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDBSCAN_512D_Medium_testPerformance( rxTest )

	testDBSCANPerformance( rxTest, kMedium, 512 )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDBSCAN_512D_Large_testPerformance( rxTest )

	testDBSCANPerformance( rxTest, kLarge, 512 )

end

------------------------------------------------------------------------------

-- Helper for cosine similarity performance tests
local function testCosineSimilarityPerformance( rxTest, vectorSize, iterations, cosineFunc )

	rxTest:setTimeLimit( 600 )

	-- Generate test vectors
	local v1 = createPoint( math.random(), vectorSize )
	local v2 = createPoint( math.random(), vectorSize )

	-- Test performance
	rxTest:measurePerformance( function()
		for i = 1, iterations do
			cosineFunc( v1, v2 )
		end
	end )

end

--------------------------------------------------------------------------------

-- Helper for normalization performance tests
local function testNormalizationPerformance( rxTest, vectorSize, iterations, normalizeFunc )

	rxTest:setTimeLimit( 600 )

	-- Generate test vector
	local v = createPoint( math.random(), vectorSize )

	-- Test performance
	rxTest:measurePerformance( function()
		for i = 1, iterations do
			normalizeFunc( v )
		end
	end )

end

--------------------------------------------------------------------------------

local function testDotProductPerformance( rxTest, vectorSize, iterations, dotProductFunc )

	rxTest:setTimeLimit( 600 )

	-- Generate test vectors

	local v1 = createPoint( math.random(), vectorSize )
	local v2 = createPoint( math.random(), vectorSize )

	-- Test performance

	rxTest:measurePerformance( function()
		for i = 1, iterations do
			dotProductFunc( v1, v2 )
		end
	end )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDotProduct_512D_testPerformance( rxTest )

	testDotProductPerformance( rxTest, 512, 100000, WFVectorUtils.dotProduct )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDotProductLua_512D_testPerformance( rxTest )

	testDotProductPerformance( rxTest, 512, 100000, WFVectorUtils.dotProductLua )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestDotProductAlternate_512D_testPerformance( rxTest )

	testDotProductPerformance( rxTest, 512, 100000, WFVectorUtils._test_dotProductAlternate )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestCosineSimilarity_512D_testPerformance( rxTest )

	testCosineSimilarityPerformance( rxTest, 512, 100000, WFVectorUtils.cosineSimilarity )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestCosineSimilarityLua_512D_testPerformance( rxTest )

	testCosineSimilarityPerformance( rxTest, 512, 100000, WFVectorUtils.cosineSimilarityLua )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestCosineSimilarityAlternate_512D_testPerformance( rxTest )

	testCosineSimilarityPerformance( rxTest, 512, 100000, WFVectorUtils._test_cosineSimilarityAlternate )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestNormalizeVector_512D_testPerformance( rxTest )

	testNormalizationPerformance( rxTest, 512, 100000, WFVectorUtils.normalizeVector )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestNormalizeVectorLua_512D_testPerformance( rxTest )

	testNormalizationPerformance( rxTest, 512, 100000, WFVectorUtils.normalizeVectorLua )

end

--------------------------------------------------------------------------------

function WFVectorUtilsTest.rxTestNormalizeVectorAlternate_512D_testPerformance( rxTest )

	testNormalizationPerformance( rxTest, 512, 100000, WFVectorUtils._test_normalizeVectorAlternate )

end

--------------------------------------------------------------------------------

return WFRxTestCase.wrapSuite( 'WFVectorUtilsTest', WFVectorUtilsTest )
