Tests/Tests-PSColorStyle.ps1

#Requires -Modules Pester

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

    # Dot-source the class file directly into test scope (same as module does)
    . "$ModuleRoot\Classes\PSColorStyle.ps1"
}

Describe 'PSColorStyle Class' -Tag 'Unit', 'Class' {

    Context 'Constructors' {
        It 'Creates instance with default constructor' {
            $style = [PSColorStyle]::new()

            $style | Should -Not -BeNullOrEmpty
            $style.Name | Should -Be 'Custom'
            $style.ForegroundColor | Should -Be 'Gray'
            $style.BackgroundColor | Should -BeNullOrEmpty
        }

        It 'Creates instance with name only' {
            $style = [PSColorStyle]::new('TestStyle')

            $style.Name | Should -Be 'TestStyle'
            $style.ForegroundColor | Should -Be 'Gray'
            $style.BackgroundColor | Should -BeNullOrEmpty
        }

        It 'Creates instance with name, foreground, and background' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', 'Blue')

            $style.Name | Should -Be 'TestStyle'
            $style.ForegroundColor | Should -Be 'Red'
            $style.BackgroundColor | Should -Be 'Blue'
        }

        It 'Accepts null background color' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)

            $style.Name | Should -Be 'TestStyle'
            $style.ForegroundColor | Should -Be 'Red'
            $style.BackgroundColor | Should -BeNullOrEmpty
        }
    }

    Context 'Property Initialization' {
        BeforeAll {
            $style = [PSColorStyle]::new('TestStyle', 'Red', 'Blue')
        }

        It 'Initializes boolean properties to false' {
            $style.Bold | Should -Be $false
            $style.Italic | Should -Be $false
            $style.Underline | Should -Be $false
            $style.Blink | Should -Be $false
            $style.Faint | Should -Be $false
            $style.CrossedOut | Should -Be $false
            $style.DoubleUnderline | Should -Be $false
            $style.Overline | Should -Be $false
            $style.ShowTime | Should -Be $false
            $style.NoNewLine | Should -Be $false
            $style.HorizontalCenter | Should -Be $false
        }

        It 'Initializes numeric properties to zero' {
            $style.StartTab | Should -Be 0
            $style.StartSpaces | Should -Be 0
            $style.LinesBefore | Should -Be 0
            $style.LinesAfter | Should -Be 0
        }

        It 'Initializes Style as empty or null' {
            # Style can be null or empty array
            if ($style.Style) {
                $style.Style.Count | Should -Be 0
            } else {
                $style.Style | Should -BeNullOrEmpty
            }
        }

        It 'Initializes Gradient as null' {
            $style.Gradient | Should -BeNullOrEmpty
        }
    }

    Context 'Property Modification' {
        BeforeEach {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
        }

        It 'Allows setting Bold to true' {
            $style.Bold = $true
            $style.Bold | Should -Be $true
        }

        It 'Allows setting multiple style properties' {
            $style.Bold = $true
            $style.Italic = $true
            $style.Underline = $true

            $style.Bold | Should -Be $true
            $style.Italic | Should -Be $true
            $style.Underline | Should -Be $true
        }

        It 'Allows setting numeric properties' {
            $style.StartTab = 2
            $style.StartSpaces = 4
            $style.LinesBefore = 1
            $style.LinesAfter = 1

            $style.StartTab | Should -Be 2
            $style.StartSpaces | Should -Be 4
            $style.LinesBefore | Should -Be 1
            $style.LinesAfter | Should -Be 1
        }

        It 'Allows setting Gradient array' {
            $style.Gradient = @('Red', 'Blue', 'Green')

            $style.Gradient | Should -Not -BeNullOrEmpty
            $style.Gradient.Count | Should -Be 3
            $style.Gradient[0] | Should -Be 'Red'
        }

        It 'Allows setting Style array' {
            $style.Style = @('Bold', 'Italic')

            $style.Style.Count | Should -Be 2
            $style.Style[0] | Should -Be 'Bold'
            $style.Style[1] | Should -Be 'Italic'
        }
    }

    Context 'Static Default Property' {
        It 'Has a Default static property' {
            [PSColorStyle]::Default | Should -Not -BeNullOrEmpty
        }

        It 'Default property is of type PSColorStyle' {
            [PSColorStyle]::Default.GetType().Name | Should -Be 'PSColorStyle'
        }
    }

    Context 'Static Profiles Property' {
        It 'Has a Profiles static property' {
            [PSColorStyle]::Profiles | Should -Not -BeNullOrEmpty
        }

        It 'Profiles is a hashtable' {
            [PSColorStyle]::Profiles | Should -BeOfType [hashtable]
        }

        It 'Profiles contains default profiles' {
            [PSColorStyle]::Profiles.ContainsKey('Default') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Error') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Warning') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Info') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Success') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Critical') | Should -Be $true
            [PSColorStyle]::Profiles.ContainsKey('Debug') | Should -Be $true
        }
    }

    Context 'SetAsDefault Method' {
        It 'Sets the style as default' {
            $originalDefault = [PSColorStyle]::Default
            $newStyle = [PSColorStyle]::new('NewDefault', 'Magenta', $null)

            $newStyle.SetAsDefault()

            [PSColorStyle]::Default.Name | Should -Be 'NewDefault'
            [PSColorStyle]::Default.ForegroundColor | Should -Be 'Magenta'

            # Restore original default
            $originalDefault.SetAsDefault()
        }
    }

    Context 'AddToProfiles Method' {
        It 'Adds style to profiles collection' {
            $style = [PSColorStyle]::new('CustomProfile', 'Orange', $null)
            $style.AddToProfiles()

            [PSColorStyle]::Profiles.ContainsKey('CustomProfile') | Should -Be $true
            [PSColorStyle]::Profiles['CustomProfile'].Name | Should -Be 'CustomProfile'
        }

        It 'Overwrites existing profile with same name' {
            $style1 = [PSColorStyle]::new('TestProfile', 'Red', $null)
            $style1.AddToProfiles()

            $style2 = [PSColorStyle]::new('TestProfile', 'Blue', $null)
            $style2.AddToProfiles()

            [PSColorStyle]::Profiles['TestProfile'].ForegroundColor | Should -Be 'Blue'
        }
    }

    Context 'GetProfile Static Method' {
        BeforeAll {
            $testStyle = [PSColorStyle]::new('GetProfileTest', 'Cyan', $null)
            $testStyle.AddToProfiles()
        }

        It 'Retrieves existing profile by name' {
            $retrieved = [PSColorStyle]::GetProfile('GetProfileTest')

            $retrieved | Should -Not -BeNullOrEmpty
            $retrieved.Name | Should -Be 'GetProfileTest'
            $retrieved.ForegroundColor | Should -Be 'Cyan'
        }

        It 'Returns null for non-existent profile' {
            $retrieved = [PSColorStyle]::GetProfile('NonExistentProfile')

            $retrieved | Should -BeNullOrEmpty
        }

        It 'Retrieves built-in Error profile' {
            $errorProfile = [PSColorStyle]::GetProfile('Error')

            $errorProfile | Should -Not -BeNullOrEmpty
            $errorProfile.Name | Should -Be 'Error'
            $errorProfile.ForegroundColor | Should -Be 'Red'
            $errorProfile.Bold | Should -Be $true
        }
    }

    Context 'ToWriteColorParams Method' {
        It 'Returns hashtable with color properties' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', 'Blue')
            $params = $style.ToWriteColorParams()

            $params | Should -BeOfType [hashtable]
            $params['Color'] | Should -Be 'Red'
            $params['BackGroundColor'] | Should -Be 'Blue'
        }

        It 'Includes Bold when set to true' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Bold = $true
            $params = $style.ToWriteColorParams()

            $params.ContainsKey('Bold') | Should -Be $true
            $params['Bold'] | Should -Be $true
        }

        It 'Excludes Bold when set to false' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Bold = $false
            $params = $style.ToWriteColorParams()

            $params.ContainsKey('Bold') | Should -Be $false
        }

        It 'Includes multiple style properties' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Bold = $true
            $style.Italic = $true
            $style.Underline = $true
            $params = $style.ToWriteColorParams()

            $params['Bold'] | Should -Be $true
            $params['Italic'] | Should -Be $true
            $params['Underline'] | Should -Be $true
        }

        It 'Includes formatting properties when set' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.StartTab = 2
            $style.LinesBefore = 1
            $style.LinesAfter = 1
            $params = $style.ToWriteColorParams()

            $params['StartTab'] | Should -Be 2
            $params['LinesBefore'] | Should -Be 1
            $params['LinesAfter'] | Should -Be 1
        }

        It 'Excludes formatting properties when zero' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $params = $style.ToWriteColorParams()

            $params.ContainsKey('StartTab') | Should -Be $false
            $params.ContainsKey('LinesBefore') | Should -Be $false
            $params.ContainsKey('LinesAfter') | Should -Be $false
        }

        It 'Includes Gradient when set' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Gradient = @('Red', 'Blue')
            $params = $style.ToWriteColorParams()

            $params.ContainsKey('Gradient') | Should -Be $true
            $params['Gradient'].Count | Should -Be 2
        }

        It 'Excludes Gradient when less than 2 colors' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Gradient = @('Red')
            $params = $style.ToWriteColorParams()

            $params.ContainsKey('Gradient') | Should -Be $false
        }

        It 'Caches parameters for performance' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $style.Bold = $true

            $params1 = $style.ToWriteColorParams()
            $params2 = $style.ToWriteColorParams()

            # Both should have the same values but be different instances (cloned)
            $params1['Bold'] | Should -Be $params2['Bold']
            $params1 -eq $params2 | Should -Be $false
        }

        It 'Returns clone to prevent external modification' {
            $style = [PSColorStyle]::new('TestStyle', 'Red', $null)
            $params = $style.ToWriteColorParams()

            # Modify returned params
            $params['Color'] = 'Blue'

            # Original should still be Red
            $params2 = $style.ToWriteColorParams()
            $params2['Color'] | Should -Be 'Red'
        }
    }

    Context 'Clone Method' {
        It 'Creates a copy of the style' {
            $original = [PSColorStyle]::new('Original', 'Red', 'Blue')
            $original.Bold = $true
            $original.StartTab = 2

            $clone = $original.Clone()

            $clone.Name | Should -Be 'Original_Copy'
            $clone.ForegroundColor | Should -Be 'Red'
            $clone.BackgroundColor | Should -Be 'Blue'
            $clone.Bold | Should -Be $true
            $clone.StartTab | Should -Be 2
        }

        It 'Creates independent copy that can be modified' {
            $original = [PSColorStyle]::new('Original', 'Red', $null)
            $clone = $original.Clone()

            $clone.ForegroundColor = 'Blue'

            $original.ForegroundColor | Should -Be 'Red'
            $clone.ForegroundColor | Should -Be 'Blue'
        }

        It 'Clone does not share cached parameters' {
            $original = [PSColorStyle]::new('Original', 'Red', $null)
            $original.Bold = $true

            # Warm cache
            $null = $original.ToWriteColorParams()

            $clone = $original.Clone()

            # Clone should have null cache initially
            $clone.Bold = $false
            $params = $clone.ToWriteColorParams()
            $params.ContainsKey('Bold') | Should -Be $false
        }

        It 'Copies all style properties' {
            $original = [PSColorStyle]::new('Original', 'Red', 'Blue')
            $original.Bold = $true
            $original.Italic = $true
            $original.Underline = $true
            $original.Blink = $true
            $original.Faint = $true
            $original.CrossedOut = $true
            $original.DoubleUnderline = $true
            $original.Overline = $true

            $clone = $original.Clone()

            $clone.Bold | Should -Be $true
            $clone.Italic | Should -Be $true
            $clone.Underline | Should -Be $true
            $clone.Blink | Should -Be $true
            $clone.Faint | Should -Be $true
            $clone.CrossedOut | Should -Be $true
            $clone.DoubleUnderline | Should -Be $true
            $clone.Overline | Should -Be $true
        }

        It 'Copies Gradient array' {
            $original = [PSColorStyle]::new('Original', 'Red', $null)
            $original.Gradient = @('Red', 'Blue', 'Green')

            $clone = $original.Clone()

            $clone.Gradient.Count | Should -Be 3
            $clone.Gradient[0] | Should -Be 'Red'
            $clone.Gradient[1] | Should -Be 'Blue'
            $clone.Gradient[2] | Should -Be 'Green'
        }
    }

    Context 'InitializeDefaultProfiles Static Method' {
        It 'Initializes all default profiles' {
            # This is called during module load, so profiles should exist
            [PSColorStyle]::Profiles.Count | Should -BeGreaterOrEqual 7
        }

        It 'Error profile has correct properties' {
            $errorProfile = [PSColorStyle]::GetProfile('Error')

            $errorProfile.Name | Should -Be 'Error'
            $errorProfile.ForegroundColor | Should -Be 'Red'
            $errorProfile.Bold | Should -Be $true
        }

        It 'Warning profile has correct properties' {
            $warningProfile = [PSColorStyle]::GetProfile('Warning')

            $warningProfile.Name | Should -Be 'Warning'
            $warningProfile.ForegroundColor | Should -Be 'Yellow'
        }

        It 'Info profile has correct properties' {
            $infoProfile = [PSColorStyle]::GetProfile('Info')

            $infoProfile.Name | Should -Be 'Info'
            $infoProfile.ForegroundColor | Should -Be 'Cyan'
        }

        It 'Success profile has correct properties' {
            $successProfile = [PSColorStyle]::GetProfile('Success')

            $successProfile.Name | Should -Be 'Success'
            $successProfile.ForegroundColor | Should -Be 'Green'
        }

        It 'Critical profile has correct properties' {
            $criticalProfile = [PSColorStyle]::GetProfile('Critical')

            $criticalProfile.Name | Should -Be 'Critical'
            $criticalProfile.ForegroundColor | Should -Be 'White'
            $criticalProfile.BackgroundColor | Should -Be 'DarkRed'
            $criticalProfile.Bold | Should -Be $true
            $criticalProfile.Blink | Should -Be $true
        }

        It 'Debug profile has correct properties' {
            $debugProfile = [PSColorStyle]::GetProfile('Debug')

            $debugProfile.Name | Should -Be 'Debug'
            $debugProfile.ForegroundColor | Should -Be 'DarkGray'
            $debugProfile.Italic | Should -Be $true
        }

        It 'Default profile cache is pre-warmed' {
            # Default profiles should have cached params
            $defaultProfile = [PSColorStyle]::GetProfile('Default')
            $params = $defaultProfile.ToWriteColorParams()

            $params | Should -Not -BeNullOrEmpty
        }
    }

    # NEW TESTS: InvalidateCache Method
    Context 'Cache Invalidation' {
        It 'InvalidateCache clears cached parameters' {
            $style = [PSColorStyle]::new('Test', 'Red', $null)

            # Force cache population by calling ToWriteColorParams
            $null = $style.ToWriteColorParams()

            # Verify cache was populated (internal property)
            $style._cachedParams | Should -Not -BeNullOrEmpty

            # Invalidate cache
            $style.InvalidateCache()

            # Verify cache is cleared
            $style._cachedParams | Should -BeNullOrEmpty
        }

        It 'Cache regenerates after invalidation' {
            $style = [PSColorStyle]::new('Test', 'Blue', 'Yellow')

            # Populate cache
            $params1 = $style.ToWriteColorParams()
            $params1 | Should -Not -BeNullOrEmpty

            # Invalidate cache
            $style.InvalidateCache()

            # Cache should regenerate on next call
            $params2 = $style.ToWriteColorParams()
            $params2 | Should -Not -BeNullOrEmpty
            $params2.Count | Should -BeGreaterThan 0
        }

        It 'Changing property and invalidating cache allows regeneration' {
            $style = [PSColorStyle]::new('Test', 'Red', $null)

            # Populate cache
            $params1 = $style.ToWriteColorParams()
            $params1.ContainsKey('Bold') | Should -Be $false

            # Change a property and invalidate
            $style.Bold = $true
            $style.InvalidateCache()

            # Cache should regenerate with new value
            $params2 = $style.ToWriteColorParams()
            $params2.ContainsKey('Bold') | Should -Be $true
            $params2.Bold | Should -Be $true
        }

        It 'Changing ForegroundColor and invalidating cache works' {
            $style = [PSColorStyle]::new('Test', 'Red', $null)

            # Populate cache
            $params1 = $style.ToWriteColorParams()
            $params1.Color | Should -Be 'Red'

            # Change ForegroundColor and invalidate
            $style.ForegroundColor = 'Blue'
            $style.InvalidateCache()

            # Cache should regenerate
            $params2 = $style.ToWriteColorParams()
            $params2.Color | Should -Be 'Blue'
        }

        It 'Changing BackgroundColor and invalidating cache works' {
            $style = [PSColorStyle]::new('Test', 'Red', 'Yellow')

            # Populate cache
            $params1 = $style.ToWriteColorParams()
            $params1.BackGroundColor | Should -Be 'Yellow'

            # Change BackgroundColor and invalidate
            $style.BackgroundColor = 'Green'
            $style.InvalidateCache()

            # Cache should regenerate
            $params2 = $style.ToWriteColorParams()
            $params2.BackGroundColor | Should -Be 'Green'
        }

        It 'Changing multiple properties and invalidating cache works' {
            $style = [PSColorStyle]::new('Test', 'Red', $null)

            # Populate cache
            $params1 = $style.ToWriteColorParams()

            # Change multiple properties and invalidate
            $style.Bold = $true
            $style.Italic = $true
            $style.Underline = $true
            $style.StartTab = 2
            $style.InvalidateCache()

            # Cache should regenerate with all new values
            $params2 = $style.ToWriteColorParams()
            $params2.Bold | Should -Be $true
            $params2.Italic | Should -Be $true
            $params2.Underline | Should -Be $true
            $params2.StartTab | Should -Be 2
        }

        It 'SetAsDefault does not break cache' {
            $style = [PSColorStyle]::new('Test', 'Cyan', $null)
            $style.Bold = $true

            # Populate cache
            $params1 = $style.ToWriteColorParams()

            # Set as default
            $style.SetAsDefault()

            # Cache should still work
            $params2 = $style.ToWriteColorParams()
            $params2 | Should -Not -BeNullOrEmpty
            $params2.Bold | Should -Be $true
        }

        It 'AddToProfiles does not break cache' {
            $style = [PSColorStyle]::new('CacheTest', 'Magenta', $null)
            $style.Italic = $true

            # Populate cache
            $params1 = $style.ToWriteColorParams()

            # Add to profiles
            $style.AddToProfiles()

            # Cache should still work
            $params2 = $style.ToWriteColorParams()
            $params2 | Should -Not -BeNullOrEmpty
            $params2.Italic | Should -Be $true

            # Cleanup
            [PSColorStyle]::Profiles.Remove('CacheTest')
        }

        It 'Clone creates new instance without shared cache' {
            $original = [PSColorStyle]::new('Original', 'Red', $null)
            $original.Bold = $true

            # Populate original cache
            $null = $original.ToWriteColorParams()
            $original._cachedParams | Should -Not -BeNullOrEmpty

            # Clone should have null cache initially
            $clone = $original.Clone()
            $clone._cachedParams | Should -BeNullOrEmpty

            # Changing clone and invalidating should not affect original cache
            $clone.Bold = $false
            $clone.InvalidateCache()
            $cloneParams = $clone.ToWriteColorParams()

            # Original cache should still have Bold = true
            $originalParams = $original.ToWriteColorParams()
            $originalParams.Bold | Should -Be $true
            $cloneParams.ContainsKey('Bold') | Should -Be $false  # Bold = $false means key not included
        }
    }
}