Tests/Tests-NewGradientColorArray.ps1

#Requires -Modules Pester

BeforeAll {
    # Import the module
    $ModuleRoot = Split-Path -Parent $PSScriptRoot
    Import-Module "$ModuleRoot\PSWriteColorEX.psd1" -Force

    # The private function, run inside the module, where the ColorMath class is defined
    function New-GradientColorArray {
        param([object[]]$Colors, [int]$Steps, [string]$Mode, [string]$Space = 'OKLab')
        & (Get-Module PSWriteColorEX) {
            param($Colors, $Steps, $Mode, $Space)
            New-GradientColorArray -Colors $Colors -Steps $Steps -Mode $Mode -Space $Space
        } $Colors $Steps $Mode $Space
    }
}

Describe 'New-GradientColorArray' -Tag 'Unit', 'Function', 'Gradient' {

    Context 'Basic Functionality' {
        It 'Generates gradient array' {
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 10 -Mode TrueColor

            $result | Should -Not -BeNullOrEmpty
            $result.Count | Should -Be 10
        }

        It 'Accepts color names' {
            $result = New-GradientColorArray -Colors @('Red', 'Green', 'Blue') -Steps 5 -Mode TrueColor

            $result.Count | Should -Be 5
        }

        It 'Accepts hex colors' {
            $result = New-GradientColorArray -Colors @('#FF0000', '#0000FF') -Steps 10 -Mode TrueColor

            $result.Count | Should -Be 10
        }

        It 'Accepts RGB arrays' {
            $result = New-GradientColorArray -Colors @(@(255, 0, 0), @(0, 0, 255)) -Steps 10 -Mode TrueColor

            $result.Count | Should -Be 10
        }

        It 'Accepts mixed color formats' {
            $result = New-GradientColorArray -Colors @('Red', '#00FF00', @(0, 0, 255)) -Steps 10 -Mode TrueColor

            $result.Count | Should -Be 10
        }
    }

    Context 'Parameter Validation' {
        It 'Requires at least 2 colors' {
            { New-GradientColorArray -Colors @('Red') -Steps 10 -Mode TrueColor } | Should -Throw
        }

        It 'Accepts exactly 2 colors' {
            { New-GradientColorArray -Colors @('Red', 'Blue') -Steps 10 -Mode TrueColor } | Should -Not -Throw
        }

        It 'Accepts more than 2 colors' {
            { New-GradientColorArray -Colors @('Red', 'Green', 'Blue', 'Yellow') -Steps 20 -Mode TrueColor } | Should -Not -Throw
        }

        # A call without a mandatory parameter would prompt for it in an interactive session,
        # so these read the parameter's attributes
        It 'Requires Steps parameter' {
            $attributes = (& (Get-Module PSWriteColorEX) { Get-Command New-GradientColorArray }).Parameters['Steps'].Attributes
            ($attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }).Mandatory | Should -Be $true
        }

        It 'Requires Mode parameter' {
            $attributes = (& (Get-Module PSWriteColorEX) { Get-Command New-GradientColorArray }).Parameters['Mode'].Attributes
            ($attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }).Mandatory | Should -Be $true
        }

        It 'Accepts TrueColor mode' {
            { New-GradientColorArray -Colors @('Red', 'Blue') -Steps 10 -Mode TrueColor } | Should -Not -Throw
        }

        It 'Accepts ANSI8 mode' {
            { New-GradientColorArray -Colors @('Red', 'Blue') -Steps 10 -Mode ANSI8 } | Should -Not -Throw
        }

        It 'Rejects invalid mode' {
            { New-GradientColorArray -Colors @('Red', 'Blue') -Steps 10 -Mode ANSI4 } | Should -Throw
        }

        It 'Requires positive Steps value' {
            { New-GradientColorArray -Colors @('Red', 'Blue') -Steps 0 -Mode TrueColor } | Should -Throw
        }
    }

    Context 'TrueColor Mode Output' {
        It 'Returns RGB arrays in TrueColor mode' {
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 5 -Mode TrueColor

            foreach ($color in $result) {
                $color.Count | Should -Be 3
            }
        }

        It 'First color matches start color' {
            $result = New-GradientColorArray -Colors @(@(255, 0, 0), @(0, 0, 255)) -Steps 10 -Mode TrueColor

            $result[0][0] | Should -Be 255
            $result[0][1] | Should -Be 0
            $result[0][2] | Should -Be 0
        }

        It 'Last color matches end color' {
            $result = New-GradientColorArray -Colors @(@(255, 0, 0), @(0, 0, 255)) -Steps 10 -Mode TrueColor

            $result[9][0] | Should -Be 0
            $result[9][1] | Should -Be 0
            $result[9][2] | Should -Be 255
        }

        It 'Middle colors are interpolated in RGB with -Space RGB' {
            $result = New-GradientColorArray -Colors @(@(0, 0, 0), @(100, 100, 100)) -Steps 3 -Mode TrueColor -Space RGB

            # Middle value should be approximately 50
            $result[1][0] | Should -BeGreaterOrEqual 45
            $result[1][0] | Should -BeLessOrEqual 55
        }

        It 'Blends in OKLab by default, where the middle of black and a gray is the gray that looks halfway' {
            $result = New-GradientColorArray -Colors @(@(0, 0, 0), @(100, 100, 100)) -Steps 3 -Mode TrueColor

            $result[1] | Should -Be @(34, 34, 34)
        }

        It 'Blends red to blue in OKLab through a lighter purple than RGB gives' {
            $result = New-GradientColorArray -Colors 'Red', 'Blue' -Steps 6 -Mode TrueColor

            ($result | ForEach-Object { $_ -join ',' }) -join ' ' | Should -Be '255,0,0 209,68,96 163,82,142 117,81,182 68,65,219 0,0,255'
        }

        It 'Keeps each waypoint exactly in OKLab' {
            $result = New-GradientColorArray -Colors '#123456', '#ABCDEF', '#FEDCBA' -Steps 5 -Mode TrueColor

            $result[0] | Should -Be @(0x12, 0x34, 0x56)
            $result[2] | Should -Be @(0xAB, 0xCD, 0xEF)
            $result[4] | Should -Be @(0xFE, 0xDC, 0xBA)
        }

        It 'RGB values stay within 0-255 range' {
            $result = New-GradientColorArray -Colors @('Red', 'Blue', 'Green') -Steps 20 -Mode TrueColor

            foreach ($color in $result) {
                $color[0] | Should -BeGreaterOrEqual 0
                $color[0] | Should -BeLessOrEqual 255
                $color[1] | Should -BeGreaterOrEqual 0
                $color[1] | Should -BeLessOrEqual 255
                $color[2] | Should -BeGreaterOrEqual 0
                $color[2] | Should -BeLessOrEqual 255
            }
        }
    }

    Context 'ANSI8 Mode Output' {
        It 'Returns ANSI8 codes in ANSI8 mode' {
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 5 -Mode ANSI8

            foreach ($code in $result) {
                $code | Should -BeOfType [int]
                $code | Should -BeGreaterOrEqual 0
                $code | Should -BeLessOrEqual 255
            }
        }

        It 'Generates correct number of codes' {
            $result = New-GradientColorArray -Colors @('Red', 'Green') -Steps 15 -Mode ANSI8

            $result.Count | Should -Be 15
        }

        It 'All codes are within ANSI8 range' {
            $result = New-GradientColorArray -Colors @('#FF0000', '#00FF00', '#0000FF') -Steps 30 -Mode ANSI8

            foreach ($code in $result) {
                $code | Should -BeGreaterOrEqual 0
                $code | Should -BeLessOrEqual 255
            }
        }
    }

    Context 'Two-Color Gradient (Optimized Path)' {
        It 'Handles simple two-color gradient' {
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 10 -Mode TrueColor

            $result.Count | Should -Be 10
        }

        It 'Interpolates smoothly between two colors' {
            $result = New-GradientColorArray -Colors @(@(0, 0, 0), @(100, 0, 0)) -Steps 11 -Mode TrueColor

            # Check that red channel increases gradually
            for ($i = 0; $i -lt $result.Count - 1; $i++) {
                $result[$i + 1][0] | Should -BeGreaterOrEqual $result[$i][0]
            }
        }

        It 'Handles minimum steps (1)' {
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 1 -Mode TrueColor

            $result.Count | Should -Be 1
            $result[0] | Should -Not -BeNullOrEmpty
        }

        It 'Handles two identical colors' {
            $result = New-GradientColorArray -Colors @(@(100, 100, 100), @(100, 100, 100)) -Steps 5 -Mode TrueColor

            foreach ($color in $result) {
                $color[0] | Should -Be 100
                $color[1] | Should -Be 100
                $color[2] | Should -Be 100
            }
        }
    }

    Context 'Multi-Stop Gradient (3+ Colors)' {
        It 'Handles three-color gradient' {
            $result = New-GradientColorArray -Colors @('Red', 'Green', 'Blue') -Steps 15 -Mode TrueColor

            $result.Count | Should -Be 15
        }

        It 'Handles four-color gradient' {
            $result = New-GradientColorArray -Colors @('Red', 'Yellow', 'Green', 'Blue') -Steps 20 -Mode TrueColor

            $result.Count | Should -Be 20
        }

        It 'Handles five-color gradient' {
            $result = New-GradientColorArray -Colors @('Red', 'Orange', 'Yellow', 'Green', 'Blue') -Steps 25 -Mode TrueColor

            $result.Count | Should -Be 25
        }

        It 'Distributes colors evenly across waypoints' {
            $result = New-GradientColorArray -Colors @(@(255, 0, 0), @(0, 255, 0), @(0, 0, 255)) -Steps 9 -Mode TrueColor

            # First waypoint (Red)
            $result[0][0] | Should -Be 255
            # Last waypoint (Blue)
            $result[8][2] | Should -Be 255
        }

        It 'Creates smooth transitions between waypoints' {
            $result = New-GradientColorArray -Colors @('Black', 'Gray', 'White') -Steps 21 -Mode TrueColor

            # Should gradually increase from black to white
            $result[0][0] | Should -BeLessOrEqual $result[10][0]
            $result[10][0] | Should -BeLessOrEqual $result[20][0]
        }
    }

    Context 'Color Format Handling' {
        It 'Converts hex colors to RGB' {
            $result = New-GradientColorArray -Colors @('#FF0000', '#0000FF') -Steps 5 -Mode TrueColor

            $result[0][0] | Should -Be 255  # Red
            $result[4][2] | Should -Be 255  # Blue
        }

        It 'Converts color names to RGB' {
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 5 -Mode TrueColor

            $result[0] | Should -Not -BeNullOrEmpty
            $result[0].Count | Should -Be 3
        }

        It 'Handles 0x prefix hex colors' {
            $result = New-GradientColorArray -Colors @('0xFF0000', '0x0000FF') -Steps 5 -Mode TrueColor

            $result.Count | Should -Be 5
        }

        It 'Clamps RGB values to 0-255 range' {
            # Test with already valid values
            $result = New-GradientColorArray -Colors @(@(255, 255, 255), @(0, 0, 0)) -Steps 5 -Mode TrueColor

            foreach ($color in $result) {
                $color[0] | Should -BeGreaterOrEqual 0
                $color[0] | Should -BeLessOrEqual 255
            }
        }

        It 'Handles invalid color names gracefully' {
            # Should fall back to gray or handle gracefully
            { New-GradientColorArray -Colors @('InvalidColor', 'Blue') -Steps 5 -Mode TrueColor } | Should -Not -Throw
        }
    }

    Context 'Edge Cases' {
        It 'Handles single step' {
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 1 -Mode TrueColor

            $result.Count | Should -Be 1
        }

        It 'Handles large number of steps' {
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 1000 -Mode TrueColor

            $result.Count | Should -Be 1000
        }

        It 'Handles very small RGB values' {
            $result = New-GradientColorArray -Colors @(@(1, 1, 1), @(2, 2, 2)) -Steps 5 -Mode TrueColor

            $result.Count | Should -Be 5
        }

        It 'Handles very large RGB values' {
            $result = New-GradientColorArray -Colors @(@(254, 254, 254), @(255, 255, 255)) -Steps 5 -Mode TrueColor

            $result.Count | Should -Be 5
        }

        It 'Handles black to white gradient' {
            $result = New-GradientColorArray -Colors @('Black', 'White') -Steps 11 -Mode TrueColor

            $result[0][0] | Should -Be 0
            $result[10][0] | Should -Be 255
        }

        It 'Handles white to black gradient (reverse)' {
            $result = New-GradientColorArray -Colors @('White', 'Black') -Steps 11 -Mode TrueColor

            $result[0][0] | Should -Be 255
            $result[10][0] | Should -Be 0
        }
    }

    Context 'Performance and Optimization' {
        It 'Builds 1000 steps quickly' {
            $start = Get-Date
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 1000 -Mode TrueColor
            $duration = (Get-Date) - $start

            $result.Count | Should -Be 1000
            $duration.TotalMilliseconds | Should -BeLessThan 1000
        }

        It 'Caches color conversions' {
            # Multiple calls with same colors should be consistent
            $result1 = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 10 -Mode TrueColor
            $result2 = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 10 -Mode TrueColor

            for ($i = 0; $i -lt 10; $i++) {
                $result1[$i][0] | Should -Be $result2[$i][0]
                $result1[$i][1] | Should -Be $result2[$i][1]
                $result1[$i][2] | Should -Be $result2[$i][2]
            }
        }

        It 'Handles command availability checks efficiently' {
            # Should not fail if color conversion functions are available
            { New-GradientColorArray -Colors @('Red', 'Blue') -Steps 10 -Mode TrueColor } | Should -Not -Throw
        }
    }

    Context 'Consistency and Reproducibility' {
        It 'Returns same results for same input' {
            $result1 = New-GradientColorArray -Colors @('Red', 'Green', 'Blue') -Steps 20 -Mode TrueColor
            $result2 = New-GradientColorArray -Colors @('Red', 'Green', 'Blue') -Steps 20 -Mode TrueColor

            $result1.Count | Should -Be $result2.Count

            for ($i = 0; $i -lt $result1.Count; $i++) {
                $result1[$i][0] | Should -Be $result2[$i][0]
                $result1[$i][1] | Should -Be $result2[$i][1]
                $result1[$i][2] | Should -Be $result2[$i][2]
            }
        }

        It 'TrueColor and ANSI8 return same count' {
            $resultTC = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 15 -Mode TrueColor
            $resultA8 = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 15 -Mode ANSI8

            $resultTC.Count | Should -Be $resultA8.Count
        }
    }

    Context 'Rainbow Gradients' {
        It 'Creates full spectrum rainbow' {
            $result = New-GradientColorArray -Colors @('Red', 'Orange', 'Yellow', 'Green', 'Cyan', 'Blue', 'Magenta') -Steps 70 -Mode TrueColor

            $result.Count | Should -Be 70
        }

        It 'Creates custom rainbow' {
            $result = New-GradientColorArray -Colors @('#FF0000', '#FF8000', '#FFFF00', '#00FF00', '#0000FF') -Steps 50 -Mode TrueColor

            $result.Count | Should -Be 50
        }
    }

    Context 'Fallback Behavior' {
        It 'Returns gray when color table not available and color invalid' {
            # This tests graceful degradation
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 5 -Mode TrueColor

            $result | Should -Not -BeNullOrEmpty
        }

        It 'Returns grayscale ANSI8 code (7) when conversion fails' {
            # Should handle gracefully
            $result = New-GradientColorArray -Colors @('Red', 'Blue') -Steps 5 -Mode ANSI8

            $result | Should -Not -BeNullOrEmpty
        }
    }
}