Public/Select-IntegrityReportFile.ps1

function Select-IntegrityReportFile {
    <#
    .SYNOPSIS
        Reads a Compare-FileShareIntegrity CSV report and outputs filtered file paths.
    .DESCRIPTION
        Parses the CSV report, filters rows by status and/or path pattern, and outputs
        a list of RelativePath strings suitable for piping to Repair-FileShareIntegrity.
 
        Supports four path-selection modes (mutually exclusive, optional):
          -FilterPath — literal substring match (no regex).
          -FilterRegex — regex pattern match.
          -IncludePaths — explicit list of paths.
          -Interactive — Out-GridView / Out-ConsoleGridView dialog.
    .PARAMETER ReportPath
        Path to the CSV integrity report.
    .PARAMETER Status
        Status values to include. Default: SizeMismatch, ByteMismatch, HashMismatch, MissingOnDestination.
    .PARAMETER FilterPath
        Literal substring to match against RelativePath (case-insensitive).
    .PARAMETER FilterRegex
        Regex pattern to match against RelativePath.
    .PARAMETER IncludePaths
        Explicit list of relative file paths to include.
    .PARAMETER Interactive
        Opens a multi-select dialog.
    .EXAMPLE
        Select-IntegrityReportFile -ReportPath '.\data_integrity-report.csv' -Status ByteMismatch
    .EXAMPLE
        Select-IntegrityReportFile -ReportPath '.\report.csv' -Status ByteMismatch -FilterRegex '\.xlsx$'
    .OUTPUTS
        [string[]] Relative file paths.
    #>

    [CmdletBinding()]
    [OutputType([string[]])]
    param(
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path $_ -PathType Leaf })]
        [string]$ReportPath,

        [Parameter()]
        [ValidateSet('SizeMismatch', 'ByteMismatch', 'HashMismatch', 'MissingOnDestination', 'MissingOnSource', 'CheckError', 'OK')]
        [string[]]$Status = @('SizeMismatch', 'ByteMismatch', 'HashMismatch', 'MissingOnDestination'),

        [Parameter()]
        [string]$FilterPath,

        [Parameter()]
        [string]$FilterRegex,

        [Parameter()]
        [string[]]$IncludePaths,

        [Parameter()]
        [switch]$Interactive
    )

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

    # ── Validate parameter combinations ──────────────────────────────────────
    $filterModes = @(
        ($null -ne $FilterPath   -and $FilterPath   -ne ''),
        ($null -ne $FilterRegex  -and $FilterRegex  -ne ''),
        ($null -ne $IncludePaths -and $IncludePaths.Count -gt 0),
        $Interactive.IsPresent
    ) | Where-Object { $_ }

    if (($filterModes | Measure-Object).Count -gt 1) {
        throw 'Only one of -FilterPath, -FilterRegex, -IncludePaths, or -Interactive may be specified.'
    }

    # ── Parse the report ─────────────────────────────────────────────────────
    $headerLine = [System.IO.File]::ReadLines($ReportPath) | Select-Object -First 1
    $delimiter  = if ($headerLine -match ';') { ';' } else { ',' }

    $allRows = @(Import-Csv -Path $ReportPath -Delimiter $delimiter)

    if ($allRows.Count -eq 0) {
        Write-Host 'Report is empty. Nothing to output.' -ForegroundColor Yellow
        return
    }

    Write-Host "Loaded $($allRows.Count) row(s) from report." -ForegroundColor Cyan

    # ── Filter by status ─────────────────────────────────────────────────────
    $filtered = @($allRows | Where-Object { $_.Status -in $Status })

    if ($filtered.Count -eq 0) {
        Write-Host "No rows matching status: $($Status -join ', ')." -ForegroundColor Yellow
        return
    }

    Write-Host " $($filtered.Count) row(s) matching status: $($Status -join ', ')." -ForegroundColor Cyan

    # ── Apply path filter ────────────────────────────────────────────────────
    if ($FilterPath) {
        $filtered = @($filtered | Where-Object {
            $_.RelativePath.IndexOf($FilterPath, [System.StringComparison]::OrdinalIgnoreCase) -ge 0
        })
        Write-Host " $($filtered.Count) row(s) matching path '$FilterPath'." -ForegroundColor Cyan
    }
    elseif ($FilterRegex) {
        $filtered = @($filtered | Where-Object { $_.RelativePath -match $FilterRegex })
        Write-Host " $($filtered.Count) row(s) matching regex '$FilterRegex'." -ForegroundColor Cyan
    }
    elseif ($IncludePaths) {
        $includeSet = [System.Collections.Generic.HashSet[string]]::new(
            [string[]]$IncludePaths,
            [System.StringComparer]::OrdinalIgnoreCase
        )
        $filtered = @($filtered | Where-Object { $includeSet.Contains($_.RelativePath) })
        $notFound = @($IncludePaths | Where-Object { $_ -notin ($filtered | ForEach-Object { $_.RelativePath }) })
        if ($notFound.Count -gt 0) {
            Write-Warning "$($notFound.Count) path(s) not found in report (or don't match status filter)."
        }
        Write-Host " $($filtered.Count) row(s) from explicit path list." -ForegroundColor Cyan
    }
    elseif ($Interactive) {
        $displayItems = $filtered | ForEach-Object {
            [PSCustomObject]@{
                RelativePath = $_.RelativePath
                Status       = $_.Status
                SourceSize   = $_.SourceSize
                DestSize     = $_.DestSize
            }
        }

        $gridCmd = Get-Command Out-ConsoleGridView -ErrorAction SilentlyContinue
        if (-not $gridCmd) {
            $gridCmd = Get-Command Out-GridView -ErrorAction SilentlyContinue
        }
        if (-not $gridCmd) {
            throw 'Interactive mode requires Out-ConsoleGridView (Install-Module Microsoft.PowerShell.ConsoleGuiTools) or Out-GridView.'
        }

        Write-Host " Opening interactive selector ($($gridCmd.Name))..." -ForegroundColor Cyan
        $picked = $displayItems | & $gridCmd.Name -Title 'Select files (Space to toggle, Enter to confirm)' -OutputMode Multiple

        if (-not $picked -or @($picked).Count -eq 0) {
            Write-Host ' No files selected.' -ForegroundColor Yellow
            return
        }

        $pickedSet = [System.Collections.Generic.HashSet[string]]::new(
            [string[]]@($picked | ForEach-Object { $_.RelativePath }),
            [System.StringComparer]::OrdinalIgnoreCase
        )
        $filtered = @($filtered | Where-Object { $pickedSet.Contains($_.RelativePath) })
        Write-Host " $($filtered.Count) file(s) selected interactively." -ForegroundColor Cyan
    }

    # ── Output ───────────────────────────────────────────────────────────────
    if ($filtered.Count -eq 0) {
        Write-Host 'No files matched. Nothing to output.' -ForegroundColor Yellow
        return
    }

    Write-Host "$($filtered.Count) file path(s) output." -ForegroundColor Green
    $filtered | ForEach-Object { $_.RelativePath }
}