Private/Get-LastCheckTime.ps1

function Get-LastCheckTime {
    <#
    .SYNOPSIS
        Manages the state file for tracking between RepoWatch runs.
    .DESCRIPTION
        Reads or writes the JSON state file that stores the last check timestamp,
        star/fork counts per repo, and PSGallery download counts. This enables
        delta tracking between runs so the digest shows only changes.
    .PARAMETER StatePath
        Path to the state JSON file.
    .PARAMETER Owner
        GitHub owner/username for namespacing state data.
    .PARAMETER Write
        Switch to enable write mode. When set, updates the state file with Data.
    .PARAMETER Data
        Hashtable of data to write to state. Used with -Write switch.
    .EXAMPLE
        $state = Get-LastCheckTime -StatePath $path -Owner "larro1991"
    .EXAMPLE
        Get-LastCheckTime -StatePath $path -Owner "larro1991" -Write -Data $newState
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$StatePath = (Join-Path $env:USERPROFILE '.repowatch\state.json'),

        [Parameter()]
        [string]$Owner,

        [Parameter()]
        [switch]$Write,

        [Parameter()]
        [hashtable]$Data
    )

    # Ensure the directory exists
    $stateDir = Split-Path -Path $StatePath -Parent
    if (-not (Test-Path -Path $stateDir)) {
        New-Item -Path $stateDir -ItemType Directory -Force | Out-Null
    }

    if ($Write) {
        # Write mode: update state file with provided data
        if (-not $Data) {
            Write-Error 'Data parameter is required when using -Write switch.'
            return
        }

        $state = @{}

        # Load existing state if file exists
        if (Test-Path -Path $StatePath) {
            try {
                $existingContent = Get-Content -Path $StatePath -Raw -Encoding UTF8
                if ($existingContent) {
                    $existingState = $existingContent | ConvertFrom-Json
                    # Convert PSObject to hashtable
                    foreach ($prop in $existingState.PSObject.Properties) {
                        $state[$prop.Name] = $prop.Value
                    }
                }
            }
            catch {
                Write-Warning "Could not parse existing state file. Creating new one."
                $state = @{}
            }
        }

        # Merge in new data
        foreach ($key in $Data.Keys) {
            $state[$key] = $Data[$key]
        }

        # Write state file with UTF-8 BOM encoding
        $jsonContent = $state | ConvertTo-Json -Depth 10
        [System.IO.File]::WriteAllText(
            $StatePath,
            $jsonContent,
            [System.Text.UTF8Encoding]::new($true)
        )

        Write-Verbose "State file updated: $StatePath"
        return
    }
    else {
        # Read mode: return current state
        if (-not (Test-Path -Path $StatePath)) {
            Write-Verbose "State file not found at $StatePath. Returning defaults."
            return [PSCustomObject]@{
                last_check = $null
                repos      = @{}
                psgallery  = @{}
            }
        }

        try {
            $content = Get-Content -Path $StatePath -Raw -Encoding UTF8
            if (-not $content) {
                return [PSCustomObject]@{
                    last_check = $null
                    repos      = @{}
                    psgallery  = @{}
                }
            }

            $stateObj = $content | ConvertFrom-Json

            # Ensure expected properties exist
            if (-not ($stateObj.PSObject.Properties.Name -contains 'last_check')) {
                $stateObj | Add-Member -NotePropertyName 'last_check' -NotePropertyValue $null
            }
            if (-not ($stateObj.PSObject.Properties.Name -contains 'repos')) {
                $stateObj | Add-Member -NotePropertyName 'repos' -NotePropertyValue ([PSCustomObject]@{})
            }
            if (-not ($stateObj.PSObject.Properties.Name -contains 'psgallery')) {
                $stateObj | Add-Member -NotePropertyName 'psgallery' -NotePropertyValue ([PSCustomObject]@{})
            }

            return $stateObj
        }
        catch {
            Write-Warning "Failed to parse state file: $_"
            return [PSCustomObject]@{
                last_check = $null
                repos      = @{}
                psgallery  = @{}
            }
        }
    }
}