Private/Invoke-SonarrRequest.ps1

function Invoke-SonarrRequest
{
    <#
        .SYNOPSIS
            Centralized function for making HTTP requests to the Sonarr API.

        .DESCRIPTION
            This function consolidates all HTTP request logic for the Sonarr API, providing consistent
            error handling, URL encoding, header generation, and support for common parameters like -WhatIf.

        .PARAMETER Path
            The API endpoint path (e.g., '/series', '/qualityprofile'). Leading slash is optional.

        .PARAMETER Method
            The HTTP method to use (GET, POST, PUT, DELETE). Defaults to GET.

        .PARAMETER Params
            A hashtable of query string parameters to append to the URL. Values will be properly URL-encoded.

        .PARAMETER Body
            The request body for POST/PUT requests. Can be a hashtable, PSCustomObject, or JSON string.
            If not a string, it will be converted to JSON automatically.

        .PARAMETER SuppressWhatIf
            If specified, bypasses WhatIf support for read-only operations (GET requests).

        .EXAMPLE
            Invoke-SonarrRequest -Path '/series' -Method GET

        .EXAMPLE
            Invoke-SonarrRequest -Path '/series/lookup' -Params @{ term = 'imdb:tt0944947' }

        .EXAMPLE
            $Body = @{ qualityProfileId = 1; monitored = $true }
            Invoke-SonarrRequest -Path '/series' -Method POST -Body $Body

        .NOTES
            This function requires the configuration to be loaded via Import-Configuration before use.
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory = $true)]
        [String]$Path,

        [Parameter(Mandatory = $false)]
        [ValidateSet('GET', 'POST', 'PUT', 'DELETE')]
        [String]$Method = 'GET',

        [Parameter(Mandatory = $false)]
        [System.Collections.IDictionary]$Params,

        [Parameter(Mandatory = $false)]
        [Object]$Body,

        [Parameter(Mandatory = $false)]
        [Switch]$SuppressWhatIf
    )

    ####################################################################################################
    # Strip leading slash from path if present
    if($Path.StartsWith('/'))
    {
        $Path = $Path.Substring(1)
    }

    ####################################################################################################
    # Build query string with proper URL encoding
    [String]$QueryString = ''
    if($Params -and $Params.Count -gt 0)
    {
        $EncodedParams = @()
        foreach($Key in $Params.Keys)
        {
            $EncodedKey = [System.Web.HttpUtility]::UrlEncode($Key)
            $EncodedValue = [System.Web.HttpUtility]::UrlEncode($Params[$Key])
            $EncodedParams += "$EncodedKey=$EncodedValue"
        }
        $QueryString = '?' + ($EncodedParams -join '&')
    }

    ####################################################################################################
    # Construct the full URI
    $Uri = "$($Config.Protocol)://$($Config.Server):$($Config.Port)/api/v$($Config.APIVersion)/$Path$QueryString"

    ####################################################################################################
    # Build headers
    $Headers = @{
        'X-Api-Key' = $Config.APIKey
    }

    ####################################################################################################
    # Prepare body if provided
    $RequestParams = @{
        Uri     = $Uri
        Headers = $Headers
        Method  = $Method
    }

    if($Body)
    {
        if($Body -is [String])
        {
            $BodyBytes = [System.Text.Encoding]::UTF8.GetBytes($Body)
        }
        else
        {
            $BodyJson = $Body | ConvertTo-Json -Depth 10 -Compress
            $BodyBytes = [System.Text.Encoding]::UTF8.GetBytes($BodyJson)
        }

        $RequestParams['Body'] = $BodyBytes
        $RequestParams['ContentType'] = 'application/json'
    }

    ####################################################################################################
    # Handle WhatIf for state-changing operations
    $IsReadOnly = ($Method -eq 'GET')

    if(-not $IsReadOnly -or -not $SuppressWhatIf)
    {
        $OperationDescription = "$Method $Uri"
        if($Body)
        {
            $OperationDescription += " (with body)"
        }

        if($PSCmdlet.ShouldProcess($OperationDescription, "Invoke Sonarr API"))
        {
            # Proceed with the request
        }
        else
        {
            # WhatIf or Confirm prevented execution
            return
        }
    }

    ####################################################################################################
    # Make the request with consistent error handling
    try
    {
        Write-Verbose -Message "[$Method] $Uri"
        if($Body)
        {
            Write-Debug -Message "Request Body: $($Body | ConvertTo-Json -Depth 10 -Compress)"
        }
        $Response = Invoke-RestMethod @RequestParams -ErrorAction Stop
        return $Response
    }
    catch
    {
        throw $_
    }
}