```powershell
<#
.SYNOPSIS
    Validates readiness and optionally creates a Windows Server 2025 failover cluster.

.DESCRIPTION
    This script provides a practical, non-destructive workflow for preparing a Windows Server 2025
    failover cluster. It can:
      - Check basic node connectivity and feature presence
      - Run cluster validation tests
      - Create a new cluster when explicitly requested
      - Configure quorum when explicitly requested
      - Review cluster and node state after creation

    By default, the script performs discovery and validation only. It does not create or modify the
    cluster unless you pass -CreateCluster and/or -ConfigureQuorum.

    Intended use:
      - Existing VectraOps content page: Configure Windows Server 2025 Failover Clustering for High Availability
      - Vendor-neutral operational baseline

.NOTES
    Run from an elevated PowerShell session on a management host or one of the cluster nodes.
    Requires the Failover Clustering PowerShell module.
#>

[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
param(
    [Parameter(Mandatory = $true)]
    [string[]]$Node,

    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$ClusterName = 'CLUSTER01',

    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$StaticAddress,

    [Parameter(Mandatory = $false)]
    [ValidateSet('System Configuration', 'Network', 'Inventory', 'Storage')]
    [string[]]$ValidationCategory = @('System Configuration', 'Network', 'Inventory', 'Storage'),

    [Parameter(Mandatory = $false)]
    [switch]$SkipStorageValidation,

    [Parameter(Mandatory = $false)]
    [switch]$CreateCluster,

    [Parameter(Mandatory = $false)]
    [switch]$ConfigureQuorum,

    [Parameter(Mandatory = $false)]
    [ValidateSet('FileShareWitness', 'CloudWitness', 'NodeMajority')]
    [string]$QuorumType = 'FileShareWitness',

    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$FileShareWitnessPath,

    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$LogPath = "$env:TEMP\ClusterReadiness-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Write-Section {
    param([string]$Text)
    Write-Host ''
    Write-Host ('=' * 72)
    Write-Host $Text
    Write-Host ('=' * 72)
}

function Test-RequiredModule {
    $module = Get-Module -ListAvailable -Name FailoverClusters
    if (-not $module) {
        throw 'The Failover Clustering PowerShell module is not installed on this system.'
    }
}

function Test-NodeReachability {
    param([string[]]$ComputerName)

    foreach ($n in $ComputerName) {
        $ping = Test-Connection -ComputerName $n -Count 2 -Quiet -ErrorAction SilentlyContinue
        [pscustomobject]@{
            Node        = $n
            Ping        = [bool]$ping
            DnsResolved = [bool](Resolve-DnsName -Name $n -ErrorAction SilentlyContinue)
        }
    }
}

function Get-FeatureState {
    param([string[]]$ComputerName)

    foreach ($n in $ComputerName) {
        $feature = Get-WindowsFeature -ComputerName $n -Name Failover-Clustering -ErrorAction Stop
        [pscustomobject]@{
            Node    = $n
            Feature = $feature.DisplayName
            Installed = [bool]$feature.Installed
        }
    }
}

function Invoke-ClusterValidation {
    param(
        [string[]]$ComputerName,
        [string[]]$Include
    )

    $args = @{ Node = $ComputerName; ErrorAction = 'Stop' }
    if ($Include.Count -gt 0) { $args.Include = $Include }

    Test-Cluster @args
}

function New-ClusterIfRequested {
    param(
        [string[]]$ComputerName,
        [string]$Name,
        [string]$Address
    )

    if (-not $CreateCluster) { return }

    $newClusterArgs = @{ Name = $Name; Node = $ComputerName; ErrorAction = 'Stop' }
    if ($Address) { $newClusterArgs.StaticAddress = $Address }

    if ($PSCmdlet.ShouldProcess("Cluster '$Name'", 'Create new failover cluster')) {
        New-Cluster @newClusterArgs | Out-Null
    }
}

function Set-ClusterQuorumIfRequested {
    param(
        [string]$Name,
        [string]$WitnessPath,
        [string]$QType
    )

    if (-not $ConfigureQuorum) { return }

    switch ($QType) {
        'FileShareWitness' {
            if (-not $WitnessPath) {
                throw 'FileShareWitnessPath is required when -QuorumType FileShareWitness is selected.'
            }
            if ($PSCmdlet.ShouldProcess("Cluster '$Name'", "Set quorum to file share witness: $WitnessPath")) {
                Set-ClusterQuorum -NodeAndFileShareMajority $WitnessPath -ErrorAction Stop
            }
        }
        'CloudWitness' {
            throw 'Cloud witness configuration is environment-specific and intentionally not automated by this script.'
        }
        'NodeMajority' {
            if ($PSCmdlet.ShouldProcess("Cluster '$Name'", 'Set quorum to node majority')) {
                Set-ClusterQuorum -NodeMajority -ErrorAction Stop
            }
        }
    }
}

function Get-ClusterHealthSummary {
    param([string]$Name)

    $cluster = Get-Cluster -Name $Name -ErrorAction Stop
    $nodes   = Get-ClusterNode -Cluster $cluster.Name -ErrorAction Stop

    [pscustomobject]@{
        ClusterName = $cluster.Name
        QuorumType  = (Get-ClusterQuorum -Cluster $cluster.Name -ErrorAction SilentlyContinue | ForEach-Object { $_.QuorumResource })
        NodeCount   = ($nodes | Measure-Object).Count
        Nodes       = ($nodes | Select-Object -ExpandProperty Name) -join ', '
    }
}

try {
    Write-Section 'Cluster readiness script started'
    Test-RequiredModule

    if (-not (Test-Path -Path $LogPath)) {
        New-Item -Path $LogPath -ItemType Directory -Force | Out-Null
    }

    Write-Host "Log path: $LogPath"
    Write-Host "Target nodes: $($Node -join ', ')"

    Write-Section 'Basic node connectivity'
    $reachability = Test-NodeReachability -ComputerName $Node
    $reachability | Format-Table -AutoSize

    if ($reachability.Ping -contains $false) {
        Write-Warning 'One or more nodes did not respond to ICMP. Verify network paths and firewall rules.'
    }

    if ($reachability.DnsResolved -contains $false) {
        Write-Warning 'One or more nodes did not resolve by DNS name. Verify DNS registration and name resolution.'
    }

    Write-Section 'Failover Clustering feature state'
    $features = Get-FeatureState -ComputerName $Node
    $features | Format-Table -AutoSize

    if ($features.Installed -contains $false) {
        Write-Warning 'The Failover Clustering feature is missing on one or more nodes.'
    }

    Write-Section 'Cluster validation'
    $includeList = @($ValidationCategory)
    if ($SkipStorageValidation) {
        $includeList = $includeList | Where-Object { $_ -ne 'Storage' }
    }

    if ($includeList.Count -eq 0) {
        throw 'No validation categories selected. Choose at least one validation category.'
    }

    Write-Host "Running Test-Cluster with categories: $($includeList -join ', ')"
    $validationReport = Invoke-ClusterValidation -ComputerName $Node -Include $includeList
    $validationReport

    if ($CreateCluster) {
        Write-Section 'Cluster creation'
        if (-not $StaticAddress) {
            Write-Warning 'No static address was provided. If your environment requires a static cluster IP, supply -StaticAddress.'
        }
        New-ClusterIfRequested -ComputerName $Node -Name $ClusterName -Address $StaticAddress
    }

    if ($ConfigureQuorum) {
        Write-Section 'Quorum configuration'
        Set-ClusterQuorumIfRequested -Name $ClusterName -WitnessPath $FileShareWitnessPath -QType $QuorumType
    }

    Write-Section 'Post-build health summary'
    try {
        $summary = Get-ClusterHealthSummary -Name $ClusterName
        $summary | Format-List
    }
    catch {
        Write-Warning "Cluster health summary could not be retrieved yet: $($_.Exception.Message)"
    }

    Write-Section 'Completed'
    Write-Host 'Review the validation output and cluster state before placing production workloads online.'
}
catch {
    Write-Error $_.Exception.Message
    throw
}
```