src/notifiers/NotifyToast.ps1

#Requires -Version 5.1

function Test-ToastSupported {
    <#
    .SYNOPSIS
    Check if Windows 10+ with Toast support is available.

    .OUTPUTS
    Boolean - $true if Toast API available, $false otherwise
    #>

    [CmdletBinding()]
    param()

    if ([System.Environment]::OSVersion.Version.Major -lt 10) {
        return $false
    }

    try {
        $null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
        return $true
    }
    catch {
        return $false
    }
}

function _GetAlertEmoji {
    param([string]$AlertReason)

    switch ($AlertReason) {
        'NEW' { return '[NEW]' }
        'AVAILABILITY_REDUCED' { return '[REDUCED]' }
        'SOLD_OUT' { return '[SOLD_OUT]' }
        default { return '[ALERT]' }
    }
}

function _NewToastXML {
    <#
    .SYNOPSIS
    Build Windows Toast XML with title, body, sound, and action buttons.

    .PARAMETER Title
    Toast title text

    .PARAMETER Body
    Toast body text (with course details)

    .PARAMETER ActionUrl
    URL to display in body (included as text)

    .PARAMETER SoundEnabled
    Whether to play notification sound

    .OUTPUTS
    String - XML template for Toast notification
    #>

    param(
        [string]$Title,
        [string]$Body,
        [string]$ActionUrl,
        [bool]$SoundEnabled = $true
    )

    $escapedTitle = [System.Security.SecurityElement]::Escape($Title)
    $escapedUrl = [System.Security.SecurityElement]::Escape($ActionUrl)
    $bodyWithUrl = [System.Security.SecurityElement]::Escape("$Body`n`n$ActionUrl")

    $audio = if ($SoundEnabled) {
        '<audio src="ms-winsoundevent:Notification.Default"/>'
    } else {
        '<audio silent="true"/>'
    }

    $xml = @"
<?xml version="1.0" encoding="utf-8"?>
<toast>
  <visual>
    <binding template="ToastText02">
      <text id="1">$escapedTitle</text>
      <text id="2">$bodyWithUrl</text>
    </binding>
  </visual>
  $audio
  <actions>
    <action activationType="protocol" arguments="$escapedUrl" content="View Course"/>
    <action activationType="system" arguments="dismiss" content="Dismiss"/>
  </actions>
</toast>
"@


    return $xml
}


function _SendToastViaWinRT {
    <#
    .SYNOPSIS
    Send Toast via Windows.UI.Notifications WinRT API.

    .PARAMETER ToastXml
    XML template for Toast

    .OUTPUTS
    Boolean - $true if successful, $false if failed
    #>

    param([string]$ToastXml)

    try {
        [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
        [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null

        $xml = New-Object Windows.Data.Xml.Dom.XmlDocument
        $xml.LoadXml($ToastXml)

        $toast = New-Object Windows.UI.Notifications.ToastNotification $xml

        $appId = 'IPSC.KursMonitor'

        [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId).Show($toast)

        return $true
    }
    catch {
        Write-Log -Level WARN -Message "Failed to display Toast via WinRT" `
            -Context @{ error = $_.Exception.Message } -Exception $_
        return $false
    }
}

function Send-ToastNotification {
    <#
    .SYNOPSIS
    Send Windows Toast notifications for course alerts (one Toast per course).

    .DESCRIPTION
    Each alert (course) gets its own Toast notification with course-specific URL.
    Toast title includes alert emoji and course name. Body shows time, availability, price.

    .PARAMETER Alerts
    Array of alert objects with alert_reason, name, time, availability, price, url properties

    .PARAMETER Config
    Toast notification configuration object with properties:
      - enabled: Boolean
      - sound_enabled: Boolean
      - auto_dismiss_seconds: Integer

    .EXAMPLE
    $alerts = @(
        @{ alert_reason = 'NEW_COURSE'; name = 'Basic 2.0'; time = '09:30-13:00'; availability = 2; price = 'CHF 280.00'; url = 'https://...' }
    )
    $config = @{ enabled = $true; sound_enabled = $true }
    Send-ToastNotification -Alerts $alerts -Config $config
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][ValidateNotNull()][object[]]$Alerts,
        [Parameter(Mandatory)][ValidateNotNull()][object]$Config
    )

    if (-not $Config.enabled) { return }
    if (-not $Alerts -or $Alerts.Count -eq 0) { return }

    if (-not (Test-ToastSupported)) {
        Write-Log -Level WARN -Message "Toast notifications skipped (not supported)" `
            -Context @{ os = [System.Environment]::OSVersion.VersionString }
        return
    }

    if ($PSCmdlet.ShouldProcess("Toast notifications", "Send $($Alerts.Count) alerts")) {
        try {
            $toastCount = 0
            $failureCount = 0

            foreach ($alert in $Alerts) {
                try {
                    $emoji = _GetAlertEmoji -AlertReason $alert.alert_reason
                    $title = "$emoji $($alert.name) | $($alert.availability) Slots"
                    $body = "$($alert.date) | $($alert.time) | $($alert.price)"

                    $toastXml = _NewToastXML -Title $title `
                                           -Body $body `
                                           -ActionUrl $alert.url `
                                           -SoundEnabled $Config.sound_enabled

                    $sendSuccess = _SendToastViaWinRT -ToastXml $toastXml

                    if ($sendSuccess) {
                        Write-Log -Level INFO -Message "Toast notification sent" `
                            -Context @{
                                alert_type = $alert.alert_reason
                                course_name = $alert.name
                                title = $title
                            }
                        $toastCount++
                    }
                    else {
                        $failureCount++
                    }
                }
                catch {
                    Write-Log -Level WARN -Message "Failed to create Toast for $($alert.name)" `
                        -Context @{ alert_type = $alert.alert_reason } -Exception $_
                    $failureCount++
                }
            }

            Write-Log -Level INFO -Message "Toast notifications completed" `
                -Context @{
                    total_alerts = $Alerts.Count
                    sent = $toastCount
                    failed = $failureCount
                }
        }
        catch {
            Write-Log -Level ERROR -Message "Toast notification pipeline failed" `
                -Context @{ alert_count = $Alerts.Count } -Exception $_
        }
    }
}