Public/Invoke-SrrRestore.ps1

function Invoke-SrrRestore {
    <#
    .SYNOPSIS
        Complete SRR restoration - extracts stored files, reconstructs archives, validates, and cleans up.

    .DESCRIPTION
        This is the main entry point for SRR restoration. It performs:
        - Auto-detection of SRR file if not specified
        - Auto-detection of source files
        - Extraction of all stored files (NFO, SFV, etc.)
        - Reconstruction of RAR volumes
        - CRC validation against SFV
        - Cleanup of temporary and source files (with confirmation)

    .PARAMETER SrrFile
        Path to SRR file. If not specified, searches current directory for a single .srr file.

    .PARAMETER SourcePath
        Directory containing source files. Defaults to current directory.

    .PARAMETER OutputPath
        Directory for reconstructed release. Defaults to current directory.

    .PARAMETER KeepSrr
        If specified, do not delete SRR file after successful restoration.

    .PARAMETER KeepSources
        If specified, do not delete source files (e.g., .mkv) after successful restoration.

    .PARAMETER SkipValidation
        Skip CRC validation against embedded SFV. Use when source files differ from original
        scene release (e.g., when using Plex or other media server sources for testing).

    .EXAMPLE
        Invoke-SrrRestore

        Auto-detects the SRR file and source files in the current directory and performs a complete restoration.

    .EXAMPLE
        Invoke-SrrRestore -SrrFile "Release.srr" -KeepSrr

        Specifies the SRR file explicitly and preserves it after successful restoration.
    #>

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

        [Parameter()]
        [string]$SourcePath = ".",

        [Parameter()]
        [string]$OutputPath = ".",

        [Parameter()]
        [switch]$KeepSrr,

        [Parameter()]
        [switch]$KeepSources,

        [Parameter()]
        [switch]$SkipValidation
    )

    Write-Host ""
    Write-Host "===========================================================" -ForegroundColor Cyan
    Write-Host " SRR Release Restoration" -ForegroundColor Cyan
    Write-Host "===========================================================" -ForegroundColor Cyan
    Write-Host ""

    # Track files we create for potential cleanup
    $script:createdFiles = @()
    $script:validationPassed = $false

    try {
        # Step 1: Auto-detect or validate SRR file
        Write-Host "[1/6] Locating SRR file..." -ForegroundColor Yellow

        if ([string]::IsNullOrWhiteSpace($SrrFile)) {
            # Auto-detect SRR in current directory
            $srrFiles = Get-ChildItem -Path $SourcePath -Filter "*.srr" -File -ErrorAction SilentlyContinue

            if ($srrFiles.Count -eq 0) {
                throw "No SRR file found in current directory. Specify -SrrFile parameter or place .srr file in current directory."
            }
            elseif ($srrFiles.Count -gt 1) {
                Write-Host " Multiple SRR files found:" -ForegroundColor Red
                foreach ($f in $srrFiles) {
                    Write-Host " - $($f.Name)" -ForegroundColor Red
                }
                throw "Multiple SRR files found. Please specify which SRR to process using -SrrFile parameter."
            }
            else {
                $SrrFile = $srrFiles[0].FullName
                Write-Host " [OK] Auto-detected: $($srrFiles[0].Name)" -ForegroundColor Green
            }
        }
        else {
            # Resolve provided path
            $SrrFile = (Resolve-Path -Path $SrrFile -ErrorAction Stop).Path
            Write-Host " [OK] Using: $(Split-Path $SrrFile -Leaf)" -ForegroundColor Green
        }

        # Resolve other paths
        $SourcePath = (Resolve-Path -Path $SourcePath -ErrorAction Stop).Path

        # Normalize and ensure OutputPath exists (respect -WhatIf)
        $OutputPath = [System.IO.Path]::GetFullPath($OutputPath)
        if (-not [System.IO.Directory]::Exists($OutputPath)) {
            if ($PSCmdlet.ShouldProcess($OutputPath, "Create directory")) {
                [System.IO.Directory]::CreateDirectory($OutputPath) | Out-Null
            }
        }

        Write-Host ""

        # Step 2: Parse SRR and discover source files
        Write-Host "[2/6] Parsing SRR and discovering source files..." -ForegroundColor Yellow

        $reader = [BlockReader]::new($SrrFile)
        $blocks = $reader.ReadAllBlocks()
        $reader.Close()

        Write-Host " Parsed $($blocks.Count) blocks" -ForegroundColor Gray

        # Find all unique source files referenced in RAR packed file blocks
        $sourceFiles = @{}
        $packedBlocks = $blocks | Where-Object { $_ -is [RarPackedFileBlock] }

        foreach ($block in $packedBlocks) {
            if (-not $sourceFiles.ContainsKey($block.FileName)) {
                $sourceFiles[$block.FileName] = @{
                    Name = $block.FileName
                    Size = $block.FullUnpackedSize
                    Path = $null
                }
            }
        }

        if ($sourceFiles.Count -eq 0) {
            throw "No source files found in SRR metadata"
        }

        Write-Host " Required source files: $($sourceFiles.Count)" -ForegroundColor Gray

        # Auto-detect each source file
        $allFound = $true
        foreach ($fileName in $sourceFiles.Keys) {
            $fileInfo = $sourceFiles[$fileName]
            $foundPath = Find-SourceFile -FileName $fileName -SearchPath $SourcePath -ExpectedSize $fileInfo.Size

            if ($foundPath) {
                $sourceFiles[$fileName].Path = $foundPath
                Write-Host " [OK] Found: $fileName" -ForegroundColor Green
            }
            else {
                $sizeGB = [Math]::Round($fileInfo.Size / 1GB, 2)
                Write-Host (" [X] Missing: {0} ({1} GB)" -f $fileName, $sizeGB) -ForegroundColor Red
                $allFound = $false
            }
        }

        if (-not $allFound) {
            throw "Required source file(s) not found. Searched in: $SourcePath"
        }

        Write-Host ""

        # Step 3: Extract all stored files
        Write-Host "[3/6] Extracting stored files..." -ForegroundColor Yellow

        $storedBlocks = $blocks | Where-Object { $_ -is [SrrStoredFileBlock] }

        if ($storedBlocks.Count -eq 0) {
            Write-Host " No stored files found in SRR" -ForegroundColor Gray
        }
        else {
            $fs = [System.IO.File]::OpenRead($SrrFile)
            try {
                $br = [System.IO.BinaryReader]::new($fs)
                $currentPos = 0

                foreach ($block in $blocks) {
                    $blockSize = $block.HeadSize + $block.AddSize

                    if ($block -is [SrrStoredFileBlock]) {
                        # Skip SRR container itself if present in stored list
                        if ($block.FileName -match '\.srr$') {
                            $currentPos += $blockSize
                            continue
                        }

                        # Guard against rooted paths and preserve relative names
                        $relativePath = $block.FileName.TrimStart('\', '/')
                        $targetPath = Join-Path $OutputPath $relativePath
                        $targetDir = Split-Path $targetPath -Parent

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

                        $dataStart = $currentPos + $block.HeadSize
                        $fs.Seek($dataStart, [System.IO.SeekOrigin]::Begin) | Out-Null

                        $fileData = $br.ReadBytes([int]$block.FileSize)
                        if ($PSCmdlet.ShouldProcess($targetPath, "Write stored file")) {
                            [System.IO.File]::WriteAllBytes($targetPath, $fileData)
                            $script:createdFiles += $targetPath
                            if ($targetPath.ToLower().EndsWith('.srs')) {
                                $info = Get-SrsInfo -FilePath $targetPath
                                Write-Host (" [OK] Extracted SRS: {0} [{1}]" -f $block.FileName, $info.Type) -ForegroundColor Green
                            }
                            else {
                                Write-Host " [OK] Extracted: $($block.FileName)" -ForegroundColor Green
                            }
                        }
                    }

                    $currentPos += $blockSize
                }
            }
            finally {
                $br.Dispose()
                $fs.Close()
            }
        }

        Write-Host ""

        # Step 3.5: Reconstruct video samples from SRS (if present and not under -WhatIf)
        $srsFiles = $storedBlocks | Where-Object { $_.FileName -match '\.srs$' }
        if ($srsFiles.Count -gt 0 -and -not $WhatIfPreference) {
            Write-Host "[3b/6] Reconstructing video samples from SRS..." -ForegroundColor Yellow

            foreach ($srsBlock in $srsFiles) {
                $srsPath = Join-Path $OutputPath $srsBlock.FileName

                if (Test-Path $srsPath) {
                    # Determine output sample filename (replace .srs with .mkv or .mp4)
                    $sampleBaseName = $srsPath -replace '\.(srs)$', '.mkv'

                    # Find source file for this sample (usually in source metadata or named similarly)
                    # For now, use the primary source file
                    $sourcePath = $sourceFiles.Values | Select-Object -First 1 | Select-Object -ExpandProperty Path

                    if ($sourcePath -and (Test-Path $sourcePath)) {
                        $reconstructed = Restore-SrsVideo -SrsFilePath $srsPath -SourceMkvPath $sourcePath -OutputMkvPath $sampleBaseName

                        if ($reconstructed) {
                            $script:createdFiles += $sampleBaseName
                        }
                    }
                    else {
                        Write-Warning " Source file not available for SRS reconstruction"
                    }
                }
            }

            Write-Host ""
        }

        # Step 4: Reconstruct RAR volumes
        Write-Host "[4/6] Reconstructing RAR volumes..." -ForegroundColor Yellow

        # Group blocks by RAR volume
        $rarVolumes = @{}
        $currentVolume = $null

        foreach ($block in $blocks) {
            if ($block -is [SrrRarFileBlock]) {
                $currentVolume = $block.FileName
                $rarVolumes[$currentVolume] = @{
                    RarFileBlock = $block
                    Blocks = [System.Collections.Generic.List[Object]]::new()
                }
            }
            elseif ($currentVolume -and (
                $block -is [RarMarkerBlock] -or
                $block -is [RarVolumeHeaderBlock] -or
                $block -is [RarPackedFileBlock] -or
                $block -is [RarEndArchiveBlock]
            )) {
                $rarVolumes[$currentVolume].Blocks.Add($block)
            }
        }

        Write-Host " RAR volumes to reconstruct: $($rarVolumes.Count)" -ForegroundColor Gray

        # Sort volumes: .rar first, then .r00, .r01, etc
        $sortedVolumes = $rarVolumes.Keys | Sort-Object {
            if ($_ -match '\.rar$') { 0 }
            elseif ($_ -match '\.r(\d+)$') { [int]$matches[1] + 1 }
            else { 999 }
        }

        # If -WhatIf: preview sources and target output files without writing
        if ($WhatIfPreference) {
            Write-Host " Preview sources:" -ForegroundColor Gray
            foreach ($sf in $sourceFiles.Values) {
                Write-Host (" - {0} <= {1}" -f $sf.Name, $sf.Path) -ForegroundColor Gray
            }
            Write-Host " Preview outputs:" -ForegroundColor Gray
            foreach ($v in $sortedVolumes) {
                $previewPath = Join-Path $OutputPath $v
                Write-Host (" - {0}" -f $previewPath) -ForegroundColor Gray
            }
        }

        $sourceFileHandle = $null
        $currentSourceFile = $null
        $sourceFileOffset = [long]0

        try {
            foreach ($volumeName in $sortedVolumes) {
                $volumeData = $rarVolumes[$volumeName]
                $outputFile = Join-Path $OutputPath $volumeName

                $proceed = $PSCmdlet.ShouldProcess($outputFile, "Create RAR volume")
                if (-not $proceed) { continue }

                $rarStream = [System.IO.FileStream]::new($outputFile, [System.IO.FileMode]::Create)

                try {
                    foreach ($block in $volumeData.Blocks) {
                        if ($block -is [RarMarkerBlock]) {
                            $markerBytes = [byte[]]@(0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00)
                            $rarStream.Write($markerBytes, 0, $markerBytes.Length)
                        }
                        elseif ($block -is [RarVolumeHeaderBlock]) {
                            $blockBytes = $block.GetBlockBytes()
                            $rarStream.Write($blockBytes, 0, $blockBytes.Length)
                        }
                        elseif ($block -is [RarPackedFileBlock]) {
                            # Open source file if needed
                            if ($currentSourceFile -ne $block.FileName) {
                                if ($sourceFileHandle) {
                                    $sourceFileHandle.Close()
                                    $sourceFileHandle = $null
                                }

                                $currentSourceFile = $block.FileName
                                $sourceFileOffset = 0

                                # Get source path from our discovered list
                                $sourcePath = $sourceFiles[$block.FileName].Path

                                if (-not $sourcePath) {
                                    throw "Source file not found: $($block.FileName)"
                                }

                                $sourceFileHandle = [System.IO.File]::OpenRead($sourcePath)
                            }

                            # Write file header
                            $blockBytes = $block.GetBlockBytes()
                            $rarStream.Write($blockBytes, 0, $blockBytes.Length)

                            # Copy chunk data from source
                            $chunkSize = $block.FullPackedSize
                            $buffer = New-Object byte[] ([Math]::Min($chunkSize, 1MB))
                            $remaining = $chunkSize

                            $sourceFileHandle.Seek($sourceFileOffset, [System.IO.SeekOrigin]::Begin) | Out-Null

                            while ($remaining -gt 0) {
                                $toRead = [Math]::Min($remaining, $buffer.Length)
                                $bytesRead = $sourceFileHandle.Read($buffer, 0, $toRead)

                                if ($bytesRead -eq 0) {
                                    throw "Unexpected end of source file"
                                }

                                $rarStream.Write($buffer, 0, $bytesRead)
                                $remaining -= $bytesRead
                            }

                            $sourceFileOffset += $chunkSize
                        }
                        elseif ($block -is [RarEndArchiveBlock]) {
                            $blockBytes = $block.GetBlockBytes()
                            $rarStream.Write($blockBytes, 0, $blockBytes.Length)
                        }
                    }
                }
                finally {
                    $rarStream.Close()
                }

                $script:createdFiles += $outputFile
                $fileSize = (Get-Item $outputFile).Length
                Write-Host " [OK] Created: $volumeName ($fileSize bytes)" -ForegroundColor Green
            }
        }
        finally {
            if ($sourceFileHandle) {
                $sourceFileHandle.Close()
            }
        }

        Write-Host ""

        # Step 5: Validate reconstructed archives
        Write-Host "[5/6] Validating reconstructed archives..." -ForegroundColor Yellow

        # Respect -WhatIf: skip validation to avoid temp file writes, but allow cleanup preview
        if ($WhatIfPreference) {
            Write-Host " Skipping validation under -WhatIf (no temp files written)" -ForegroundColor Gray
            $script:validationPassed = $true
        }
        elseif ($SkipValidation) {
            Write-Host " Skipping validation (-SkipValidation specified)" -ForegroundColor Yellow
            $script:validationPassed = $true
        }
        else {
            # Extract and parse SFV
            $tempSfv = [System.IO.Path]::GetTempFileName() + ".sfv"
            $sfvFound = $false

            try {
                # Try to extract SFV from SRR
                $storedSfv = $storedBlocks | Where-Object { $_.FileName -match '\.sfv$' } | Select-Object -First 1

                if ($storedSfv) {
                    $fs = [System.IO.File]::OpenRead($SrrFile)
                    try {
                        $br = [System.IO.BinaryReader]::new($fs)
                        $currentPos = 0

                        foreach ($block in $blocks) {
                            $blockSize = $block.HeadSize + $block.AddSize

                            if ($block -eq $storedSfv) {
                                $dataStart = $currentPos + $block.HeadSize
                                $fs.Seek($dataStart, [System.IO.SeekOrigin]::Begin) | Out-Null
                                $fileData = $br.ReadBytes([int]$block.FileSize)
                                [System.IO.File]::WriteAllBytes($tempSfv, $fileData)
                                $sfvFound = $true
                                break
                            }

                            $currentPos += $blockSize
                        }
                    }
                    finally {
                        $br.Dispose()
                        $fs.Close()
                    }
                }

                if (-not $sfvFound) {
                    Write-Warning " SFV file not found in SRR, skipping CRC validation"
                }
                else {
                    # Parse SFV
                    $sfvData = ConvertFrom-SfvFile -FilePath $tempSfv
                    Write-Host " SFV entries: $($sfvData.Count)" -ForegroundColor Gray

                    # Validate each RAR file
                    $allValid = $true
                    $validCount = 0
                    $failCount = 0

                    foreach ($rarFile in $sfvData.Keys | Sort-Object) {
                        $rarPath = Join-Path $OutputPath $rarFile

                        if (-not (Test-Path $rarPath)) {
                            Write-Host " [X] $rarFile - NOT FOUND" -ForegroundColor Red
                            $allValid = $false
                            $failCount++
                            continue
                        }

                        $expectedCrc = $sfvData[$rarFile]
                        $actualCrc = Get-Crc32 -FilePath $rarPath

                        if ($actualCrc -eq $expectedCrc) {
                            Write-Host " [OK] $rarFile" -ForegroundColor Green
                            $validCount++
                        }
                        else {
                            Write-Host (" [X] $rarFile - CRC mismatch: Expected 0x{0:X8}, got 0x{1:X8}" -f $expectedCrc, $actualCrc) -ForegroundColor Red
                            $allValid = $false
                            $failCount++
                        }
                    }

                    if (-not $allValid) {
                        throw "Validation failed! $validCount valid, $failCount failed. Files not cleaned up for inspection."
                    }

                    $script:validationPassed = $true
                    Write-Host " All $validCount RAR files validated successfully!" -ForegroundColor Green
                }
            }
            finally {
                Remove-Item $tempSfv -Force -ErrorAction SilentlyContinue
            }
        }

        Write-Host ""

        # Step 6: Cleanup (only if validation passed)
            if ($script:validationPassed) {
            Write-Host "[6/6] Cleanup..." -ForegroundColor Yellow

            # SRR deletion via ShouldProcess/-Confirm
            if (-not $KeepSrr) {
                if ($PSCmdlet.ShouldProcess($SrrFile, "Delete SRR")) {
                    if (Test-Path $SrrFile) {
                        Remove-Item $SrrFile -Force -ErrorAction SilentlyContinue
                        Write-Host " [OK] Deleted SRR: $(Split-Path $SrrFile -Leaf)" -ForegroundColor Gray
                    }
                }
                else {
                    Write-Host " Keeping SRR (confirmation declined)" -ForegroundColor Gray
                }
            }
            else {
                Write-Host " Keeping SRR (KeepSrr specified)" -ForegroundColor Gray
            }

            # Source deletions via ShouldProcess/-Confirm
            if (-not $KeepSources) {
                foreach ($fileName in $sourceFiles.Keys) {
                    $srcPath = $sourceFiles[$fileName].Path
                    if ($PSCmdlet.ShouldProcess($srcPath, "Delete source")) {
                        if (Test-Path $srcPath) {
                            Remove-Item $srcPath -Force -ErrorAction SilentlyContinue
                            Write-Host " [OK] Deleted source: $(Split-Path $srcPath -Leaf)" -ForegroundColor Gray
                        }
                    }
                }
            }
            else {
                Write-Host " Keeping source files (KeepSources specified)" -ForegroundColor Gray
            }

            # SRS deletions via ShouldProcess/-Confirm
            $srsBlocks = $storedBlocks | Where-Object { $_.FileName -match '\.srs$' }
            foreach ($srsBlock in $srsBlocks) {
                $srsPath = Join-Path $OutputPath $srsBlock.FileName
                if (Test-Path $srsPath) {
                    if ($PSCmdlet.ShouldProcess($srsPath, "Delete SRS")) {
                        Remove-Item $srsPath -Force -ErrorAction SilentlyContinue
                        Write-Host " [OK] Deleted SRS: $(Split-Path $srsPath -Leaf)" -ForegroundColor Gray
                    }
                }
            }

            Write-Host ""
            Write-Host "===========================================================" -ForegroundColor Green
            Write-Host " Restoration Complete & Validated!" -ForegroundColor Green
            Write-Host "===========================================================" -ForegroundColor Green
            Write-Host ""
            Write-Host "Output directory: $OutputPath" -ForegroundColor Cyan
            Write-Host " - RAR volumes: $($rarVolumes.Count)" -ForegroundColor Gray
            Write-Host " - Stored files: $($storedBlocks.Count)" -ForegroundColor Gray
            Write-Host ""
        }
        else {
            Write-Host ""
            Write-Host "===========================================================" -ForegroundColor Yellow
            Write-Host " Restoration Complete (Validation Skipped)" -ForegroundColor Yellow
            Write-Host "===========================================================" -ForegroundColor Yellow
            Write-Host ""
            Write-Warning "Validation was skipped or failed. No cleanup performed."
            Write-Host "Output directory: $OutputPath" -ForegroundColor Cyan
            Write-Host ""
        }

    }
    catch {
        Write-Host ""
        Write-Host "[X] Restoration failed: $_" -ForegroundColor Red
        Write-Host ""
        throw
    }
}