Providers/LocalFileSystem.ps1

function Clear-LocalFileSystem-Cache {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$ProviderName,
        [switch]$Force
    )

    $prov = Get-ExpressionCacheProvider -ProviderName $ProviderName
    $folder = $prov.Config.CacheFolder

    if ($folder -and (Test-Path -LiteralPath $folder)) {
        Remove-Item -LiteralPath $folder -Recurse -Force -ErrorAction SilentlyContinue
    }
  
}

function Update-LocalFileSystem-Cache {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    param(
        [Parameter(Mandatory)]
        [string]$Key,

        [Parameter(Mandatory)]
        $Data,

        [Parameter(Mandatory)]
        [string]$Query,

        [Parameter(Mandatory)]
        [string]$CacheFolder,

        [Parameter(Mandatory)]
        [string]$CacheVersion
    )

    if ($null -eq $Data -or (($Data -is [System.Collections.ICollection]) -and $Data.Count -eq 0)) { 
        return 
    }

    $cacheFile = [IO.Path]::Combine($CacheFolder, "$Key.txt")
    $cacheDir = Split-Path -Path $cacheFile -Parent

    # Ensure directory exists (gated by ShouldProcess)
    if (-not (Test-Path -LiteralPath $cacheDir)) {
        if ($PSCmdlet.ShouldProcess($cacheDir, 'Create cache directory')) {
            New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null
        }
    }

    if ($PSCmdlet.ShouldProcess($cacheFile, 'Write cache entry')) {
        $cacheResult = [pscustomobject]@{
            Version = $CacheVersion
            Query   = $Query
            Data    = $Data
        }
        $cacheResult | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $cacheFile -Force
    }
}

function Get-FromLocalFileSystem {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    param(
        [Parameter(Mandatory)][string]$Key,
        [Parameter(Mandatory)][string]$CacheFolder,
        [Parameter(Mandatory)][string]$CacheVersion,
        [Parameter(Mandatory)][CachePolicy]$Policy
    )

    $cacheFile = [IO.Path]::Combine($CacheFolder, "$Key.txt")
    if (-not (Test-Path -LiteralPath $cacheFile)) { return $null }

    $item = Get-Item -LiteralPath $cacheFile -Force
    $lastWriteUtc = $item.LastWriteTimeUtc
    $nowUtc = (Get-Date).ToUniversalTime()

    $isFresh = switch ($Policy.Mode) {
        'Absolute' { $nowUtc -le $Policy.ExpireAtUtc }
        'Sliding' { ($nowUtc - $lastWriteUtc) -le [TimeSpan]::FromSeconds($Policy.TtlSeconds) }
        default { ($nowUtc - $lastWriteUtc) -le [TimeSpan]::FromSeconds($Policy.TtlSeconds) }
    }

    if (-not $isFresh) {
        if ($PSCmdlet.ShouldProcess($cacheFile, "Remove expired cache")) {
            Remove-Item -LiteralPath $cacheFile -Force -ErrorAction SilentlyContinue
        }
        Write-Verbose "ExpressionCache: $cacheFile deleted (expired)."
        return $null
    }

    try {
        $cacheContent = Get-Content -Raw -LiteralPath $cacheFile | ConvertFrom-Json
        if ($cacheContent.Version -ne $CacheVersion) {
            Write-Verbose "LocalFileSystemCache: version mismatch: expected $CacheVersion, got $($cacheContent.Version)"
            return $null
        }

        # Sliding: refresh 'last write' timestamp on hit
        if ($Policy.Sliding) {
            [IO.File]::SetLastWriteTimeUtc($cacheFile, (Get-Date).ToUniversalTime())
        }

        return $cacheContent.Data
    }
    catch {
        Write-Warning "LocalFileSystemCache: failed to read/parse cache file: $cacheFile"
        return $null
    }
}

function Get-LocalFileSystem-CachedValue {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Key,
        [Parameter(Mandatory)][string]$ProviderName,
        [Parameter(Mandatory)][scriptblock]$ScriptBlock,

        [Alias('ArgumentList')]
        [object[]]$Arguments,

        [Parameter(Mandatory)][string]$CacheFolder,
        [Parameter(Mandatory)][CachePolicy]$Policy,   # <- replaces MaximumAge

        [Parameter(Mandatory)][string]$CacheVersion
    )

    # READ (Policy handles MaxAge/Absolute/Sliding in Get-FromLocalFileSystem)
    $response = Get-FromLocalFileSystem -Key $Key -CacheFolder $CacheFolder -CacheVersion $CacheVersion -Policy $Policy

    if ($null -ne $response) {
        Write-Verbose "LocalFileSystemCache: Retrieved from cache: $Key"
        return $response
    }

    # MISS → compute
    $Arguments = if ($Arguments) { $Arguments } else { @() }
    $response = & $ScriptBlock @Arguments

    if ($null -eq $response) {
        Write-Verbose "LocalFileSystemCache: Computed null; skipping write for: $Key"
        return $null
    }

    # Persist new value (policy is enforced on future reads by Get-FromLocalFileSystem)
    $desc = ($ScriptBlock.ToString() -split "`r?`n" | ForEach-Object { $_.Trim() }) -join ' '
    Update-LocalFileSystem-Cache -Key $Key -Data $response -Query $desc -CacheFolder $CacheFolder -CacheVersion $CacheVersion

    return $response
}


function Initialize-LocalFileSystem-Cache {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$CacheFolder,

        [Parameter(Mandatory)] 
        [string]$ProviderName,
        
        [Parameter(Mandatory)]
        [string]$CacheVersion
    )

    if (-not (Test-Path -LiteralPath $CacheFolder)) {
        New-Item -ItemType Directory -Path $CacheFolder -Force | Out-Null
    }

    $provider = Get-ExpressionCacheProvider -ProviderName $ProviderName

    $state = $provider.State
    if (-not $state) {
        $state = [PSCustomObject]@{
            Initialized = $true
        }
    } 

    $null = $provider | Set-ECProperty -Name 'State' -Value $state -DontEnforceType
}