Modules/Public/Get-S2DCapacityWaterfall.ps1

function Get-S2DCapacityWaterfall {
    <#
    .SYNOPSIS
        Computes the 7-stage theoretical capacity waterfall from raw physical to final usable capacity.

    .DESCRIPTION
        Thin wrapper around Invoke-S2DWaterfallCalculation. Extracts inputs from the live
        cluster session (physical disks, pool, volumes) and calls the pure function. The
        waterfall is entirely theoretical — no live provisioned-volume state influences
        the pipeline.

        Requires an active session established with Connect-S2DCluster.

    .EXAMPLE
        Get-S2DCapacityWaterfall

    .EXAMPLE
        Get-S2DCapacityWaterfall | Select-Object -ExpandProperty Stages | Format-Table

    .OUTPUTS
        S2DCapacityWaterfall
    #>

    [CmdletBinding()]
    [OutputType([S2DCapacityWaterfall])]
    param()

    # ── Gather prerequisite data from cache or live queries ───────────────────
    $physDisks = @($Script:S2DSession.CollectedData['PhysicalDisks'])
    if (-not $physDisks) {
        Write-Verbose "Collecting physical disk data for waterfall."
        $physDisks = @(Get-S2DPhysicalDiskInventory)
    }

    $pool = $Script:S2DSession.CollectedData['StoragePool']
    if (-not $pool) {
        Write-Verbose "Collecting storage pool data for waterfall."
        $pool = Get-S2DStoragePoolInfo
    }

    $volumes = @($Script:S2DSession.CollectedData['Volumes'])
    if (-not $volumes) {
        Write-Verbose "Collecting volume data for waterfall."
        $volumes = @(Get-S2DVolumeMap)
    }

    $nodeCount = if ($Script:S2DSession.Nodes.Count -gt 0) { $Script:S2DSession.Nodes.Count } else {
        $perNode = @($physDisks | Group-Object NodeName)
        if ($perNode.Count -gt 0) { $perNode.Count } else { 4 }
    }

    # ── Extract inputs ────────────────────────────────────────────────────────
    $capacityDisks = @($physDisks | Where-Object { $_.IsPoolMember -ne $false -and $_.Role -eq 'Capacity' })
    if (-not $capacityDisks) {
        $capacityDisks = @($physDisks | Where-Object {
            $_.IsPoolMember -ne $false -and
            $_.Usage -ne 'Journal' -and
            $_.Usage -ne 'Retired'
        })
    }

    $rawDiskBytes      = [int64]($capacityDisks | Measure-Object -Property SizeBytes -Sum).Sum
    $largestDriveBytes = [int64]($capacityDisks | Measure-Object -Property SizeBytes -Maximum).Maximum
    $poolTotalBytes    = if ($pool -and $pool.TotalSize) { $pool.TotalSize.Bytes } else { [int64]0 }
    $poolFreeBytes     = if ($pool -and $pool.RemainingSize) { $pool.RemainingSize.Bytes } else { [int64]0 }

    $infraVolumes  = @($volumes | Where-Object IsInfrastructureVolume)
    $infraBytes    = [int64]0
    foreach ($iv in $infraVolumes) {
        if ($iv.FootprintOnPool) { $infraBytes += $iv.FootprintOnPool.Bytes }
        elseif ($iv.Size)        { $infraBytes += $iv.Size.Bytes }
    }

    # Resiliency factor from pool settings; default 3-way mirror
    $resiliencyFactor = 3.0
    $resiliencyName   = '3-way mirror'
    if ($pool -and $pool.ResiliencySettings) {
        $mirrorSetting = @($pool.ResiliencySettings | Where-Object { $_.Name -eq 'Mirror' }) | Select-Object -First 1
        if ($mirrorSetting -and $mirrorSetting.NumberOfDataCopies -gt 0) {
            $resiliencyFactor = [double]$mirrorSetting.NumberOfDataCopies
            $resiliencyName   = "$($mirrorSetting.NumberOfDataCopies)-way mirror"
        }
    }

    # ── Compute waterfall via pure function ───────────────────────────────────
    $waterfall = Invoke-S2DWaterfallCalculation `
        -RawDiskBytes        $rawDiskBytes `
        -NodeCount           $nodeCount `
        -LargestDiskSizeBytes $largestDriveBytes `
        -PoolTotalBytes      $poolTotalBytes `
        -PoolFreeBytes       $poolFreeBytes `
        -InfraVolumeBytes    $infraBytes `
        -ResiliencyFactor    $resiliencyFactor `
        -ResiliencyName      $resiliencyName

    # Overcommit state is live-cluster context — set it here, not in the pure function
    $waterfall.IsOvercommitted = $pool -and $pool.OvercommitRatio -gt 1.0
    $waterfall.OvercommitRatio = if ($pool) { $pool.OvercommitRatio } else { 0.0 }

    $Script:S2DSession.CollectedData['CapacityWaterfall'] = $waterfall
    $waterfall
}