functions/ValidationHelpers.ps1

# Validation Helpers for EmojiTools Module

<#
.SYNOPSIS
    Centralized validation and common operation helpers for EmojiTools.

.DESCRIPTION
    Provides reusable validation functions and common patterns to reduce code
    duplication and improve consistency across the module.
#>


function Test-EmojiDataLoaded {
    <#
    .SYNOPSIS
        Validates that emoji data is loaded and available.

    .DESCRIPTION
        Checks if the Script:EmojiData variable is populated. Throws a
        DataNotFoundException if data is not available.

    .PARAMETER ThrowOnError
        If true, throws an exception. If false, returns a boolean.

    .OUTPUTS
        [bool] Returns true if data is loaded, false otherwise (when ThrowOnError is false)

    .EXAMPLE
        Test-EmojiDataLoaded -ThrowOnError
        Throws an exception if data is not loaded

    .EXAMPLE
        if (Test-EmojiDataLoaded) { # Data is loaded }
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [switch]$ThrowOnError
    )

    $isLoaded = ($null -ne $Script:EmojiData -and $Script:EmojiData.Count -gt 0)

    if (-not $isLoaded -and $ThrowOnError) {
        $exception = [DataNotFoundException]::new(
            "No emoji data loaded. Run Update-EmojiDataset to download the emoji data."
        )
        Write-EmojiError -Exception $exception -Category ResourceUnavailable
        throw $exception
    }

    return $isLoaded
}

function Get-CollectionsFilePath {
    <#
    .SYNOPSIS
        Returns the standardized path to the collections.json file.

    .DESCRIPTION
        Provides a centralized function to get the collections file path,
        reducing path construction duplication across functions.

    .OUTPUTS
        [string] The full path to collections.json

    .EXAMPLE
        $path = Get-CollectionsFilePath
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param()

    return Join-Path $PSScriptRoot "..\data\collections.json"
}

function Get-CollectionData {
    <#
    .SYNOPSIS
        Loads collection data with caching support and validation.

    .DESCRIPTION
        Centralized function to load collection data from disk or cache.
        Handles file existence checks and error handling consistently.

    .PARAMETER CollectionName
        Optional. If provided, validates that the specified collection exists.

    .PARAMETER ThrowOnNotFound
        If true, throws an exception when collections file or specific collection is not found.

    .OUTPUTS
        [hashtable] The collections data, or $null if not found and ThrowOnNotFound is false

    .EXAMPLE
        $collections = Get-CollectionData -ThrowOnNotFound

    .EXAMPLE
        $collections = Get-CollectionData -CollectionName "Work" -ThrowOnNotFound
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $false)]
        [string]$CollectionName,

        [Parameter(Mandatory = $false)]
        [switch]$ThrowOnNotFound
    )

    # Try to use cached collections first
    if (Get-Command Get-CachedCollections -ErrorAction SilentlyContinue) {
        $collections = Get-CachedCollections
    }
    else {
        $collectionsPath = Get-CollectionsFilePath

        if (-not (Test-Path $collectionsPath)) {
            if ($ThrowOnNotFound) {
                $exception = [DataNotFoundException]::new(
                    "No collections found. Run Initialize-EmojiCollections to create default collections."
                )
                Write-EmojiError -Exception $exception -Category ObjectNotFound
                throw $exception
            }
            return $null
        }

        $collections = Get-Content $collectionsPath -Encoding UTF8 | ConvertFrom-Json -AsHashtable
    }

    # Validate specific collection if requested
    if ($CollectionName -and -not $collections.ContainsKey($CollectionName)) {
        if ($ThrowOnNotFound) {
            $exception = [CollectionNotFoundException]::new($CollectionName)
            Write-EmojiError -Exception $exception -Category ObjectNotFound
            throw $exception
        }
        return $null
    }

    return $collections
}

function Save-CollectionData {
    <#
    .SYNOPSIS
        Saves collection data to disk with proper encoding and error handling.

    .DESCRIPTION
        Centralized function to save collection data, ensuring consistent
        encoding and depth settings.

    .PARAMETER Collections
        The hashtable of collections to save.

    .PARAMETER UpdateTimestamp
        If provided with a collection name, updates that collection's modified timestamp.

    .EXAMPLE
        Save-CollectionData -Collections $collections

    .EXAMPLE
        Save-CollectionData -Collections $collections -UpdateTimestamp "Work"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]$Collections,

        [Parameter(Mandatory = $false)]
        [string]$UpdateTimestamp
    )

    # Update timestamp if requested
    if ($UpdateTimestamp -and $Collections.ContainsKey($UpdateTimestamp)) {
        $Collections[$UpdateTimestamp].modified = (Get-Date).ToString("yyyy-MM-dd")
    }

    # Save to file
    $collectionsPath = Get-CollectionsFilePath
    $Collections | ConvertTo-Json -Depth 10 | Set-Content $collectionsPath -Encoding UTF8
}

function Get-DataFilePath {
    <#
    .SYNOPSIS
        Returns standardized paths to data files.

    .DESCRIPTION
        Provides centralized path construction for various data files used by the module.

    .PARAMETER FileName
        The name of the data file (without path).

    .OUTPUTS
        [string] The full path to the specified data file

    .EXAMPLE
        $historyPath = Get-DataFilePath -FileName "history.json"

    .EXAMPLE
        $statsPath = Get-DataFilePath -FileName "stats.json"
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$FileName
    )

    return Join-Path $PSScriptRoot "..\data\$FileName"
}

function Test-ValidCollectionName {
    <#
    .SYNOPSIS
        Validates a collection name format.

    .DESCRIPTION
        Ensures collection names follow allowed patterns (alphanumeric, spaces, hyphens, underscores).

    .PARAMETER Name
        The collection name to validate.

    .PARAMETER ThrowOnError
        If true, throws a DataValidationException on invalid names.

    .OUTPUTS
        [bool] True if valid, false otherwise (when ThrowOnError is false)

    .EXAMPLE
        Test-ValidCollectionName -Name "My Work" -ThrowOnError
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$Name,

        [Parameter(Mandatory = $false)]
        [switch]$ThrowOnError
    )

    # Validate not empty
    if ([string]::IsNullOrWhiteSpace($Name)) {
        if ($ThrowOnError) {
            $exception = [DataValidationException]::new(
                "Collection name cannot be empty or whitespace.",
                @{ Name = $Name }
            )
            Write-EmojiError -Exception $exception -Category InvalidArgument
            throw $exception
        }
        return $false
    }

    # Validate length (1-50 characters)
    if ($Name.Length -gt 50) {
        if ($ThrowOnError) {
            $exception = [DataValidationException]::new(
                "Collection name must be 50 characters or less. Provided: $($Name.Length) characters.",
                @{ Name = $Name; Length = $Name.Length }
            )
            Write-EmojiError -Exception $exception -Category InvalidArgument
            throw $exception
        }
        return $false
    }

    # Validate allowed characters (alphanumeric, spaces, hyphens, underscores)
    if ($Name -notmatch '^[\w\s\-]+$') {
        if ($ThrowOnError) {
            $exception = [DataValidationException]::new(
                "Collection name contains invalid characters. Only alphanumeric, spaces, hyphens, and underscores are allowed.",
                @{ Name = $Name }
            )
            Write-EmojiError -Exception $exception -Category InvalidArgument
            throw $exception
        }
        return $false
    }

    return $true
}

function Invoke-WithRetry {
    <#
    .SYNOPSIS
        Executes a script block with retry logic.

    .DESCRIPTION
        Generic retry wrapper for operations that may fail transiently.
        Provides exponential backoff between retries.

    .PARAMETER ScriptBlock
        The script block to execute.

    .PARAMETER MaxAttempts
        Maximum number of retry attempts (default: 3).

    .PARAMETER InitialDelaySeconds
        Initial delay in seconds before first retry (default: 1).

    .PARAMETER ExponentialBackoff
        If true, doubles delay after each retry.

    .EXAMPLE
        Invoke-WithRetry -ScriptBlock { Get-Content $path } -MaxAttempts 5
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [scriptblock]$ScriptBlock,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 10)]
        [int]$MaxAttempts = 3,

        [Parameter(Mandatory = $false)]
        [ValidateRange(0, 60)]
        [int]$InitialDelaySeconds = 1,

        [Parameter(Mandatory = $false)]
        [switch]$ExponentialBackoff
    )

    $attempt = 1
    $delay = $InitialDelaySeconds
    $lastException = $null

    while ($attempt -le $MaxAttempts) {
        try {
            Write-Verbose "Attempt $attempt of $MaxAttempts"
            return & $ScriptBlock
        }
        catch {
            $lastException = $_
            Write-Verbose "Attempt $attempt failed: $($_.Exception.Message)"

            if ($attempt -lt $MaxAttempts) {
                Write-Verbose "Waiting $delay seconds before retry..."
                Start-Sleep -Seconds $delay

                if ($ExponentialBackoff) {
                    $delay *= 2
                }
            }

            $attempt++
        }
    }

    # All retries failed
    throw $lastException
}

function Format-EmojiOutput {
    <#
    .SYNOPSIS
        Formats emoji results consistently across commands.

    .DESCRIPTION
        Provides standardized formatting for emoji output with proper spacing
        and column selection.

    .PARAMETER Emojis
        Array of emoji objects to format.

    .PARAMETER IncludeKeywords
        If true, includes the keywords column.

    .OUTPUTS
        Formatted table output

    .EXAMPLE
        Format-EmojiOutput -Emojis $results

    .EXAMPLE
        Format-EmojiOutput -Emojis $results -IncludeKeywords
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [AllowEmptyCollection()]
        [array]$Emojis,

        [Parameter(Mandatory = $false)]
        [switch]$IncludeKeywords
    )

    process {
        if ($null -eq $Emojis -or $Emojis.Count -eq 0) {
            return
        }

        $selectProps = @(
            @{Name = 'Emoji'; Expression = { "$($_.emoji) " } }
            @{Name = 'Name'; Expression = { $_.name.Trim() } }
            @{Name = 'Category'; Expression = { $_.category } }
        )

        if ($IncludeKeywords) {
            $selectProps += @{Name = 'Keywords'; Expression = { $_.keywords } }
        }

        $Emojis | Select-Object $selectProps | Format-Table -AutoSize
    }
}