Private/Invoke-LMPaginatedGet.ps1

<#
.SYNOPSIS
Centralized pagination runner for LogicMonitor GET requests.

.DESCRIPTION
Invoke-LMPaginatedGet centralizes common pagination behavior used by many
Get-LM cmdlets. It executes a caller-supplied request scriptblock for each
offset, safely handles null responses, and aggregates paged items.
Callers should capture `$PSCmdlet` before invoking this helper and pass that
captured reference to `Invoke-LMRestMethod -CallerPSCmdlet` inside InvokeRequest.

.PARAMETER InvokeRequest
A scriptblock that accepts two arguments: offset and batch size, and returns
the API response for that page.

.PARAMETER BatchSize
The page size to request. Defaults to 1000.

.PARAMETER SingleObjectWhenNotPaged
When set, a non-paged response is returned directly as a single object.
Otherwise, a non-paged response is returned as a single-item array.

.PARAMETER NormalizeEmptyToArray
When set, paged responses with no items return @() instead of $null.

.PARAMETER ExtractResponse
Optional scriptblock that can transform nested response shapes before pagination
logic is evaluated. The scriptblock receives one argument: the raw response.

.PARAMETER MaxItems
Optional cap on total items to retrieve. When reached, pagination stops and
$MaxItemsWarningMessage is written as a warning if supplied.

.PARAMETER MaxItemsWarningMessage
Warning message to emit when MaxItems cap is reached. Ignored if MaxItems is not set.

.OUTPUTS
Object
#>

function Invoke-LMPaginatedGet {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ScriptBlock]$InvokeRequest,

        [ValidateRange(1, 1000)]
        [Int]$BatchSize = 1000,

        [Switch]$SingleObjectWhenNotPaged,

        [Switch]$NormalizeEmptyToArray,

        [ScriptBlock]$ExtractResponse,

        [Int]$MaxItems = 0,

        [String]$MaxItemsWarningMessage
    )

    $offset = 0
    $results = @()

    while ($true) {
        $response = & $InvokeRequest $offset $BatchSize
        if ($ExtractResponse) {
            $response = & $ExtractResponse $response
        }

        if ($null -eq $response) {
            return $null
        }

        if (!(Test-LMResponseHasPagination -Response $response)) {
            if ($SingleObjectWhenNotPaged) {
                return $response
            }
            return @($response)
        }

        $items = @($response.Items)
        if ($items.Count -eq 0 -and $NormalizeEmptyToArray) {
            return @()
        }
        if ($items.Count -eq 0) {
            # Avoid infinite loops when an endpoint reports pagination but no items.
            return $results
        }

        if ($MaxItems -gt 0) {
            $remaining = $MaxItems - $results.Count
            if ($remaining -le 0) {
                if ($MaxItemsWarningMessage) {
                    Write-Warning $MaxItemsWarningMessage
                }
                break
            }

            if ($items.Count -ge $remaining) {
                $results += $items[0..($remaining - 1)]
                if ($MaxItemsWarningMessage) {
                    Write-Warning $MaxItemsWarningMessage
                }
                break
            }
        }

        $results += $items
        $offset += $items.Count

        if ($offset -ge [int]$response.Total) {
            break
        }
    }

    return $results
}