Public/Clear-ColorScriptCache.ps1

function Clear-ColorScriptCache {
    [OutputType([System.Object[]])]
    [CmdletBinding(DefaultParameterSetName = 'Selection', SupportsShouldProcess = $true, ConfirmImpact = 'Medium', HelpUri = 'https://nick2bad4u.github.io/PS-Color-Scripts-Enhanced/docs/help-redirect.html?cmdlet=Clear-ColorScriptCache')]
    param(
        [Parameter(ParameterSetName = 'Help')]
        [Alias('help')]
        [switch]$h,

        [Parameter(ParameterSetName = 'Selection', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Parameter(ParameterSetName = 'All', ValueFromPipelineByPropertyName = $true)]
        [SupportsWildcards()]
        [ValidateScript({ Test-ColorScriptNameValue $_ -AllowWildcard })]
        [string[]]$Name,

        [Parameter(ParameterSetName = 'Selection')]
        [Parameter(ParameterSetName = 'All')]
        [string[]]$Category,

        [Parameter(ParameterSetName = 'Selection')]
        [Parameter(ParameterSetName = 'All')]
        [string[]]$Tag,

        [Parameter(ParameterSetName = 'Selection')]
        [Parameter(ParameterSetName = 'All')]
        [ValidateScript({ Test-ColorScriptPathValue $_ })]
        [string]$Path,

        [Parameter(ParameterSetName = 'All')]
        [switch]$All,

        [Parameter(ParameterSetName = 'Selection')]
        [Parameter(ParameterSetName = 'All')]
        [switch]$DryRun,

        [Parameter(ParameterSetName = 'Selection')]
        [Parameter(ParameterSetName = 'All')]
        [switch]$PassThru
    )

    begin {
        if ($h) {
            Show-ColorScriptHelp -CommandName 'Clear-ColorScriptCache'
            return
        }

        $nameSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
        $collectedNames = [System.Collections.Generic.List[string]]::new()

        $addName = {
            param([string]$Value)

            if ([string]::IsNullOrWhiteSpace($Value)) {
                return
            }

            if ($nameSet.Add($Value)) {
                [void]$collectedNames.Add($Value)
            }
        }

        if ($PSBoundParameters.ContainsKey('Name') -and $Name) {
            foreach ($value in $Name) {
                & $addName $value
            }
        }
    }

    process {
        if ($h) {
            return
        }

        if ($MyInvocation.ExpectingInput) {
            if ($PSBoundParameters.ContainsKey('Name') -and $Name) {
                foreach ($value in $Name) {
                    & $addName $value
                }
            }
            elseif ($null -ne $_) {
                $candidate = $null

                if ($_ -is [string]) {
                    $candidate = $_
                }
                elseif ($_ -is [System.Management.Automation.PSObject]) {
                    if ($_.PSObject.Properties['Name']) {
                        $candidate = [string]$_.PSObject.Properties['Name'].Value
                    }
                    elseif ($_.PSObject.Properties['ScriptName']) {
                        $candidate = [string]$_.PSObject.Properties['ScriptName'].Value
                    }
                }

                if ($candidate) {
                    & $addName $candidate
                }
            }
        }
    }

    end {
        if ($h) {
            return
        }

        $requestedNames = $collectedNames.ToArray()

        if (-not $All -and -not $Category -and -not $Tag -and $requestedNames.Count -eq 0) {
            Invoke-ColorScriptError -Message $script:Messages.SpecifyAllOrNameToClearCache -ErrorId 'ColorScriptsEnhanced.CacheClearSelectionMissing' -Category ([System.Management.Automation.ErrorCategory]::InvalidOperation) -Cmdlet $PSCmdlet
        }

        $cacheRoot = $null

        if ($PSBoundParameters.ContainsKey('Path') -and $Path) {
            $resolvedPath = Resolve-CachePath -Path $Path

            if (-not $resolvedPath) {
                Write-Warning ($script:Messages.CachePathNotFound -f $Path)
                return @()
            }

            $cacheRoot = $resolvedPath
        }
        else {
            Initialize-CacheDirectory
            $cacheRoot = $script:CacheDir

            if (-not $cacheRoot) {
                Write-Warning ($script:Messages.CachePathNotFound -f '<not set>')
                return @()
            }
        }

        if (-not (Test-Path -LiteralPath $cacheRoot -PathType Container)) {
            Write-Warning ($script:Messages.CachePathNotFound -f $cacheRoot)
            return @()
        }

        $cacheInventory = @(Get-ChildItem -Path $cacheRoot -Filter '*.cache' -File -ErrorAction SilentlyContinue)
        $cacheLookup = @{}

        foreach ($file in $cacheInventory) {
            $key = $file.BaseName.ToLowerInvariant()
            if (-not $cacheLookup.ContainsKey($key)) {
                $cacheLookup[$key] = $file
            }
        }

        $metadataRecords = @()
        $needMetadata = $All -or $requestedNames.Count -gt 0 -or $Category -or $Tag

        if ($needMetadata) {
            try {
                $metadataRecords = @(Get-ColorScriptEntry -Category $Category -Tag $Tag)
            }
            catch {
                Write-Verbose ("Get-ColorScriptEntry failed: {0}" -f $_.Exception.Message)
                $metadataRecords = @()
            }

            if (($Category -or $Tag) -and -not $metadataRecords) {
                Write-Warning $script:Messages.NoScriptsMatchedSpecifiedFilters
                return @()
            }
        }

        $metadataLookup = @{}
        foreach ($record in $metadataRecords) {
            $key = ([string]$record.Name).ToLowerInvariant()
            if (-not [string]::IsNullOrWhiteSpace($key) -and -not $metadataLookup.ContainsKey($key)) {
                $metadataLookup[$key] = $record
            }
        }

        $missingEntries = [System.Collections.Generic.List[psobject]]::new()
        $selectedRecords = $metadataRecords
        $selection = $null

        if ($requestedNames.Count -gt 0) {
            $selection = Select-RecordsByName -Records $metadataRecords -Name $requestedNames
            $selectedRecords = if ($selection.Records) { $selection.Records } else { @() }

            if ($selection.MatchMap) {
                foreach ($map in $selection.MatchMap) {
                    if (-not $map.Matched) {
                        $pattern = [string]$map.Pattern

                        if ($Category -or $Tag) {
                            Write-Warning ($script:Messages.ScriptSkippedByFilter -f $pattern)
                        }
                        elseif (-not [string]::IsNullOrWhiteSpace($pattern)) {
                            [void]$missingEntries.Add([pscustomobject]@{ Name = $pattern })
                        }
                    }
                }
            }
        }

        $patternMatchMap = @{}
        if ($selection -and $selection.MatchMap) {
            foreach ($entry in $selection.MatchMap) {
                $pattern = [string]$entry.Pattern
                if (-not [string]::IsNullOrWhiteSpace($pattern) -and -not $patternMatchMap.ContainsKey($pattern)) {
                    $patternMatchMap[$pattern] = $entry
                }
            }
        }

        $targetNameSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
        $targetNames = [System.Collections.Generic.List[string]]::new()

        $addTargetName = {
            param([string]$Value)

            if ([string]::IsNullOrWhiteSpace($Value)) {
                return
            }

            if ($targetNameSet.Add($Value)) {
                [void]$targetNames.Add($Value)
            }
        }

        if ($All) {
            if ($cacheInventory.Count -eq 0) {
                Write-Warning ($script:Messages.NoCacheFilesFound -f $cacheRoot)
                return @()
            }

            $selectedFiles = $cacheInventory

            if ($requestedNames.Count -gt 0) {
                $matchedFiles = [System.Collections.Generic.List[System.IO.FileInfo]]::new()

                foreach ($pattern in $requestedNames) {
                    $patternMatches = $cacheInventory | Where-Object { $_.BaseName -like $pattern }

                    if ($patternMatches) {
                        foreach ($match in $patternMatches) {
                            [void]$matchedFiles.Add($match)
                        }
                    }
                    else {
                        [void]$missingEntries.Add([pscustomobject]@{ Name = $pattern })
                    }
                }

                $selectedFiles = if ($matchedFiles.Count -gt 0) { $matchedFiles.ToArray() } else { @() }
            }

            if ($Category -or $Tag) {
                $allowedNames = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)

                foreach ($record in $metadataRecords) {
                    $recordName = [string]$record.Name

                    if (-not [string]::IsNullOrWhiteSpace($recordName)) {
                        [void]$allowedNames.Add($recordName)
                    }
                }

                $selectedFiles = @($selectedFiles | Where-Object { $allowedNames.Contains($_.BaseName) })
            }

            if (-not $selectedFiles -or $selectedFiles.Count -eq 0) {
                Write-Warning ($script:Messages.NoCacheFilesFound -f $cacheRoot)
                return @()
            }

            foreach ($file in ($selectedFiles | Sort-Object -Property BaseName -Unique)) {
                & $addTargetName $file.BaseName
            }
        }
        else {
            if ($requestedNames.Count -gt 0) {
                foreach ($value in $requestedNames) {
                    $patternEntry = if ($patternMatchMap.ContainsKey($value)) { $patternMatchMap[$value] } else { $null }

                    if ($patternEntry -and $patternEntry.Matched -and $patternEntry.Matches) {
                        foreach ($matchedName in $patternEntry.Matches) {
                            $matchKey = $matchedName.ToLowerInvariant()
                            if ($metadataLookup.ContainsKey($matchKey)) {
                                & $addTargetName ([string]$metadataLookup[$matchKey].Name)
                            }
                            else {
                                & $addTargetName $matchedName
                            }
                        }
                        continue
                    }

                    $key = $value.ToLowerInvariant()

                    if ($metadataLookup.ContainsKey($key)) {
                        & $addTargetName ([string]$metadataLookup[$key].Name)
                        continue
                    }

                    if ($Category -or $Tag) {
                        continue
                    }

                    if ($missingEntries | Where-Object { $_.Name -eq $value }) {
                        & $addTargetName $value
                        continue
                    }

                    & $addTargetName $value
                }
            }
            elseif ($Category -or $Tag) {
                foreach ($record in $metadataRecords) {
                    & $addTargetName ([string]$record.Name)
                }
            }

            if ($targetNames.Count -eq 0 -and $missingEntries.Count -gt 0) {
                foreach ($entry in $missingEntries) {
                    & $addTargetName ([string]$entry.Name)
                }
            }
        }

        if ($targetNames.Count -eq 0) {
            return @()
        }

        $results = [System.Collections.Generic.List[psobject]]::new()
        $summary = [ordered]@{
            Removed = 0
            Missing = 0
            Errors  = 0
            DryRun  = 0
            Skipped = 0
        }

        $overallAllowed = $true
        if (-not $DryRun) {
            $overallAllowed = Invoke-ShouldProcess -Cmdlet $PSCmdlet -Target $cacheRoot -Action 'Clear cache'
        }

        if (-not $overallAllowed) {
            if ($All) {
                return @()
            }

            foreach ($name in $targetNames) {
                $cachePath = Join-Path -Path $cacheRoot -ChildPath ("{0}.cache" -f $name)
                $exists = Test-Path -LiteralPath $cachePath

                if ($exists) {
                    $summary.Skipped++
                    [void]$results.Add([pscustomobject]@{
                            Name      = $name
                            CacheFile = $cachePath
                            Status    = 'SkippedByUser'
                            Message   = ''
                        })
                }
                else {
                    $summary.Missing++
                    [void]$results.Add([pscustomobject]@{
                            Name      = $name
                            CacheFile = $cachePath
                            Status    = 'Missing'
                            Message   = $script:Messages.CacheFileNotFound
                        })
                }
            }

            return $results.ToArray()
        }

        foreach ($name in $targetNames) {
            $key = $name.ToLowerInvariant()
            $cacheInfo = if ($cacheLookup.ContainsKey($key)) { $cacheLookup[$key] } else { $null }
            $cachePath = if ($cacheInfo) { $cacheInfo.FullName } else { Join-Path -Path $cacheRoot -ChildPath ("{0}.cache" -f $name) }
            $exists = $false

            if ($cacheInfo) {
                $exists = Test-Path -LiteralPath $cacheInfo.FullName

                if (-not $exists) {
                    $cacheInfo = $null
                    $cachePath = Join-Path -Path $cacheRoot -ChildPath ("{0}.cache" -f $name)
                }
            }

            if (-not $cacheInfo) {
                $exists = Test-Path -LiteralPath $cachePath
            }

            if (-not $exists) {
                $summary.Missing++
                [void]$results.Add([pscustomobject]@{
                        Name      = $name
                        CacheFile = $cachePath
                        Status    = 'Missing'
                        Message   = $script:Messages.CacheFileNotFound
                    })
                continue
            }

            if ($DryRun) {
                $summary.DryRun++
                [void]$results.Add([pscustomobject]@{
                        Name      = $name
                        CacheFile = $cachePath
                        Status    = 'DryRun'
                        Message   = $script:Messages.NoChangesApplied
                    })
                continue
            }

            if (-not (Invoke-ShouldProcess -Cmdlet $PSCmdlet -Target $cachePath -Action 'Clear colorscript cache file')) {
                $summary.Skipped++
                [void]$results.Add([pscustomobject]@{
                        Name      = $name
                        CacheFile = $cachePath
                        Status    = 'SkippedByUser'
                        Message   = ''
                    })
                continue
            }

            try {
                Remove-Item -LiteralPath $cachePath -Force -ErrorAction Stop
                $summary.Removed++
                [void]$results.Add([pscustomobject]@{
                        Name      = $name
                        CacheFile = $cachePath
                        Status    = 'Removed'
                        Message   = ''
                    })
            }
            catch {
                $summary.Errors++
                Write-Warning ("Failed to remove cache file '{0}': {1}" -f $name, $_.Exception.Message)
                [void]$results.Add([pscustomobject]@{
                        Name      = $name
                        CacheFile = $cachePath
                        Status    = 'Error'
                        Message   = $_.Exception.Message
                    })
            }
        }

        $output = $results.ToArray()

        if (-not $PassThru) {
            $summaryMessage = [string]::Format('Cache clear summary – Removed: {0}, Missing: {1}, Skipped: {2}, DryRun: {3}, Errors: {4}', $summary.Removed, $summary.Missing, $summary.Skipped, $summary.DryRun, $summary.Errors)
            Write-ColorScriptInformation -Message $summaryMessage -Quiet:$false
        }

        return $output
    }
}