Private/Initialize-CacheDirectory.ps1

function Write-InitializedCacheMetadataFile {
    param(
        [switch]$RefreshMetadata
    )

    if (-not $RefreshMetadata) {
        return
    }

    $metadataFileName = 'cache-metadata-v{0}.json' -f $script:CacheFormatVersion
    Write-CacheMetadataFile -CacheDirectory $script:CacheDir -MetadataFileName $metadataFileName
    $script:CacheValidationPerformed = $true
    $script:CacheValidationManualOverride = $false
}

function Get-EnvironmentCacheDirectoryCandidate {
    $overrideCacheRoot = $env:COLOR_SCRIPTS_ENHANCED_CACHE_PATH
    if (-not $overrideCacheRoot) {
        return $null
    }

    $resolvedOverride = Resolve-CachePath -Path $overrideCacheRoot
    if ($resolvedOverride) {
        return $resolvedOverride
    }

    Write-Verbose "Ignoring COLOR_SCRIPTS_ENHANCED_CACHE_PATH override '$overrideCacheRoot' because the path could not be resolved."
    return $null
}

function Get-ConfiguredCacheDirectoryCandidate {
    $configData = $script:ConfigurationData
    if (-not $configData -or -not $configData.Cache -or -not $configData.Cache.Path) {
        return $null
    }

    $configuredPath = Resolve-CachePath -Path $configData.Cache.Path
    if ($configuredPath) {
        return $configuredPath
    }

    Write-Warning ($script:Messages.ConfiguredCachePathInvalid -f $configData.Cache.Path)
    return $null
}

function Get-PlatformCacheDirectoryCandidate {
    if ($script:IsWindows -or $PSVersionTable.PSVersion.Major -le 5) {
        if (-not $env:APPDATA) {
            return $null
        }
        $windowsBase = Join-Path -Path $env:APPDATA -ChildPath 'ColorScripts-Enhanced'
        return Join-Path -Path $windowsBase -ChildPath 'cache'
    }

    if ($script:IsMacOS) {
        $macBase = Join-Path -Path $HOME -ChildPath 'Library'
        $macBase = Join-Path -Path $macBase -ChildPath 'Application Support'
        $macBase = Join-Path -Path $macBase -ChildPath 'ColorScripts-Enhanced'
        return Join-Path -Path $macBase -ChildPath 'cache'
    }

    $xdgCache = if ($env:XDG_CACHE_HOME) { $env:XDG_CACHE_HOME } else { Join-Path -Path $HOME -ChildPath '.cache' }
    if (-not $xdgCache) {
        return $null
    }
    return Join-Path -Path $xdgCache -ChildPath 'ColorScripts-Enhanced'
}

function Get-CacheDirectoryCandidate {
    $candidatePaths = @(
        Get-EnvironmentCacheDirectoryCandidate
        Get-ConfiguredCacheDirectoryCandidate
        Get-PlatformCacheDirectoryCandidate
    )
    return @($candidatePaths | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
}

function Resolve-ReadOnlyCacheDirectory {
    param(
        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [string[]]$CandidatePath
    )

    foreach ($candidate in $CandidatePath) {
        $resolvedCandidate = Resolve-CachePath -Path $candidate
        if (-not $resolvedCandidate -or (Test-Path -LiteralPath $resolvedCandidate -PathType Leaf)) {
            continue
        }

        if (Test-Path -LiteralPath $resolvedCandidate -PathType Container) {
            try {
                $resolvedCandidate = (Resolve-Path -LiteralPath $resolvedCandidate -ErrorAction Stop).ProviderPath
            }
            catch {
                Write-ModuleTrace ("Read-only cache path normalization failed for '{0}': {1}" -f $resolvedCandidate, $_.Exception.Message)
            }
        }

        return $resolvedCandidate
    }

    return Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'ColorScripts-Enhanced'
}

function Write-CacheDirectoryCreationFailure {
    param(
        [Parameter(Mandatory)][string]$Target,
        [AllowNull()][object]$ErrorRecord
    )

    $message = if ($ErrorRecord -and $ErrorRecord.Exception) {
        $ErrorRecord.Exception.Message
    }
    elseif ($ErrorRecord) {
        $ErrorRecord.ToString()
    }
    else {
        'unknown reason'
    }
    Write-Warning ($script:Messages.UnableToPrepareCacheDirectory -f $Target, $message)
}

function Write-CacheDirectoryResolutionFailure {
    param(
        [Parameter(Mandatory)]
        [string]$OriginalPath
    )

    Write-Verbose ("Skipping cache candidate '{0}' because it could not be resolved." -f $OriginalPath)
}

function Test-CacheMetadataRefreshRequired {
    param(
        [Parameter(Mandatory)][string]$CacheDirectory,
        [Parameter(Mandatory)][string]$MetadataFileName,
        [switch]$RefreshMetadata,
        [switch]$ForceViaEnvironment,
        [switch]$CompareDirectoryStamp
    )

    if ($RefreshMetadata -or $ForceViaEnvironment -or $script:CacheValidationManualOverride) {
        return $true
    }

    if ($script:CacheValidationPerformed) {
        return $false
    }

    $metadataPath = Join-Path -Path $CacheDirectory -ChildPath $MetadataFileName
    if (-not (Test-Path -LiteralPath $metadataPath -PathType Leaf)) {
        return $true
    }

    if (-not $CompareDirectoryStamp) {
        return $false
    }

    try {
        $directoryStamp = & $script:DirectoryGetLastWriteTimeUtcDelegate $CacheDirectory
        $metadataStamp = & $script:FileGetLastWriteTimeUtcDelegate $metadataPath
        return $directoryStamp -and $metadataStamp -and ($directoryStamp -gt $metadataStamp)
    }
    catch {
        Write-ModuleTrace ("Cache metadata stamp comparison failed: {0}" -f $_.Exception.Message)
        return $false
    }
}

function Initialize-ResolvedCacheDirectory {
    param(
        [Parameter(Mandatory)][string]$CacheDirectory,
        [switch]$RefreshMetadata,
        [switch]$ForceViaEnvironment,
        [switch]$CompareDirectoryStamp
    )

    $script:CacheDir = $CacheDirectory
    $script:CacheInitialized = $true
    $metadataFileName = 'cache-metadata-v{0}.json' -f $script:CacheFormatVersion
    $shouldValidate = Test-CacheMetadataRefreshRequired -CacheDirectory $CacheDirectory -MetadataFileName $metadataFileName -RefreshMetadata:$RefreshMetadata -ForceViaEnvironment:$ForceViaEnvironment -CompareDirectoryStamp:$CompareDirectoryStamp
    if ($shouldValidate) {
        Write-CacheMetadataFile -CacheDirectory $CacheDirectory -MetadataFileName $metadataFileName
        $script:CacheValidationPerformed = $true
    }

    $script:CacheValidationManualOverride = $false
}

function Initialize-FallbackCacheDirectory {
    $fallback = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'ColorScripts-Enhanced'
    try {
        New-Item -ItemType Directory -Path $fallback -Force -ErrorAction Stop | Out-Null
    }
    catch {
        Write-CacheDirectoryCreationFailure -Target $fallback -ErrorRecord $_
        throw
    }

    try {
        return (Resolve-Path -LiteralPath $fallback -ErrorAction Stop).ProviderPath
    }
    catch {
        return $fallback
    }
}

function Initialize-CacheDirectoryCore {
    param(
        [switch]$ReadOnly,
        [switch]$RefreshMetadata
    )

    if ($script:CacheInitialized -and $script:CacheDir) {
        Write-InitializedCacheMetadataFile -RefreshMetadata:$RefreshMetadata
        return
    }

    Initialize-Configuration
    $candidatePaths = @(Get-CacheDirectoryCandidate)
    if ($ReadOnly) {
        $script:CacheDir = Resolve-ReadOnlyCacheDirectory -CandidatePath $candidatePaths
        return
    }

    $onCreateFailure = {
        param($target, $errorRecord)
        Write-CacheDirectoryCreationFailure -Target $target -ErrorRecord $errorRecord
    }
    $onResolutionFailure = {
        param($originalPath)
        Write-CacheDirectoryResolutionFailure -OriginalPath $originalPath
    }
    $resolvedCacheDir = Resolve-PreferredDirectoryCandidate -CandidatePaths $candidatePaths -OnCreateFailure $onCreateFailure -OnResolutionFailure $onResolutionFailure
    $forceViaEnvironment = $env:COLOR_SCRIPTS_ENHANCED_VALIDATE_CACHE -match '^(1|true|yes)$'

    $usingFallback = -not $resolvedCacheDir
    if ($usingFallback) {
        $resolvedCacheDir = Initialize-FallbackCacheDirectory
    }

    Initialize-ResolvedCacheDirectory -CacheDirectory $resolvedCacheDir -RefreshMetadata:$RefreshMetadata -ForceViaEnvironment:$forceViaEnvironment -CompareDirectoryStamp:(-not $usingFallback)
}

function Initialize-CacheDirectory {
    param(
        [switch]$ReadOnly,
        [switch]$RefreshMetadata
    )

    if ($script:CacheInitialized -and $script:CacheDir -and -not $RefreshMetadata) {
        return
    }

    $shouldRemainReadOnly = $ReadOnly.IsPresent
    Invoke-ModuleSynchronized $script:CacheSyncRoot {
        Initialize-CacheDirectoryCore -ReadOnly:$shouldRemainReadOnly -RefreshMetadata:$RefreshMetadata
    }
}