[CmdletBinding(SupportsShouldProcess)]param(
    [string]$SourcePath=".",
    [string]$SourceSet="*.rar",
    [switch]$Recurse,
    # The extension of the output zip file, don't forget to include the dot, default is ".zip"
    [string]$ZipExtension=".zip",
    [string]$ZipDestPath="./zipped",
    [string]$ZipOptions="-mm=Deflate -mfb=258 -mpass=15",
    [string]$MoveProcessedTo="done",
    [switch]$Flatten,
    [switch]$MuteSkipped,
    [switch]$Force,
    [ScriptBlock]$GetExtractCmd
)

$local:Get7zExtractCmd = {
    param([string]$ArchivePath,[string]$TmpPath)
    $local:7zCmd = "7z "
    if( $Flatten ) {
        $7zCmd += "e "
    } else {
        $7zCmd += "x "
    }
    $7zCmd += "-o$(Get-ShellSafePath $TmpPath) $(Get-ShellSafePath $ArchivePath)"

    $7zCmd
}
$local:GetUnrarExtractCmd = {
    param([string]$ArchivePath,[string]$TmpPath)
    "unrar x $(Get-ShellSafePath $ArchivePath)"
}
if( -not $GetExtractCmd ) {
    $GetExtractCmd = $Get7zExtractCmd
    if( Get-Command unrar -ErrorAction Ignore ) {
        $GetExtractCmd = $GetUnrarExtractCmd
    }
}

function Get7zCompressCmd {
    param([string]$ArchivePath),[string]$CompressOptions
    $local:7zCmd = "7z a -sdel "
    if( -not $Flatten ) { $7zCmd += "-r " }
    $7zCmd += "$CompressOptions $(Get-ShellSafePath $ArchivePath)"

    $7zCmd
}

# Create a new temporary dir
$local:DestPathRoot = Get-Path "$ZipDestPath/"
$local:TempPath = Join-Path $DestPathRoot ([System.IO.Path]::GetRandomFileName())
$local:DonePath = [string]::Empty
if( $MoveProcessedTo ) {
    $DonePath = Get-Path $MoveProcessedTo
}

$local:runTimeSpan = [System.Diagnostics.Stopwatch]::StartNew()
$local:lastStatus = $runTimeSpan.Elapsed

$SourcePath = Get-Path $SourcePath
$local:srcSet = Get-ChildItem -Recurse:$Recurse -LiteralPath $SourcePath -Include $SourceSet

$local:progress = [ordered]@{
    Activity = "ConvertTo-Zip $SourcePath"
    ItemCount = $srcSet.Count
    ItemProgress = 0
    Elapsed = $runTimeSpan.Elapsed
}

Write-Verbose "`n`tSourcePath: $SourcePath;`n`tTempPath: $TempPath;`n`tDestPathRoot: $DestPathRoot;`n`tDonePath: $DonePath"

foreach( $local:srcFile in $srcSet ) {
    Write-Verbose "Processing $($srcFile.Name)..."
    $progress.ItemProgress++
    $progress.Elapsed = $runTimeSpan.Elapsed
    if( ($runTimeSpan.Elapsed - $lastStatus).TotalSeconds -gt 3 ) {
        Show-Progress @progress -Status "Processing $($srcFile.Name)..."
    }
    if( Test-Path -LiteralPath $TempPath ) {
        Remove-Item -Recurse -Force -LiteralPath $TempPath -ErrorAction Stop
    }
    $null = New-Item -ItemType Directory -Force -Path $TempPath -ErrorAction Stop -WhatIf:$false

    $local:relPath = $srcFile.DirectoryName.Replace($SourcePath,[string]::Empty)
    $local:destPath = $srcFile.DirectoryName.Replace($SourcePath,$DestPathRoot)
    $local:destZipPath = $(Join-Path $destPath "$($srcFile.BaseName)$ZipExtension")

#   Write-Verbose "relPath = $relPath`ndestPath = $destPath`ndestZipPath = $destZipPath"

    $local:ActionStatus = "Failed"
    Write-Verbose "Dest: $destZipPath"
    if( -not $Force -and (Test-Path -LiteralPath $destZipPath) ) {
        Write-Verbose "Skipping, dest already exists: `"$($destZipPath.Replace($DestPathRoot,[string]::Empty))`""
        $ActionStatus = "Skipped"
    } else {
        Push-Location -LiteralPath $TempPath -ErrorAction Stop
        $local:nextCmd = Invoke-Command $GetExtractCmd -ArgumentList @( $srcFile.FullName, $TempPath )
        $null = Invoke-ExpressionEx $nextCmd
        if( -not (Get-ChildItem -Recurse -File -LiteralPath $TempPath).Length ) {
            Write-Warning "Failed to convert file:`n`t$($srcFile.FullName)"
            $ActionStatus = "Failed"
        } else {
            $local:destFile = Join-Path $destPath "$([System.io.path]::GetRandomFileName()).zip"
            $nextCmd = Get7zCompressCmd -ArchivePath $destFile
            $null = Invoke-ExpressionEx $nextCmd

            if( -not (Test-Path -LiteralPath $destPath ) ) {
                $null = New-Item -Type Directory -Path $destPath
            }
            $null = Move-Item -Force:$Force -LiteralPath $destFile $destZipPath

            $ActionStatus = "Processed"
        }
        Pop-Location
    }

    if( Test-Path -PathType Container -LiteralPath $TempPath ) {
        Remove-Item -Recurse -Force -LiteralPath $TempPath -WhatIf:$false
    }

    if( $DonePath ) {
        $local:doneFinalPath = Join-Path $DonePath $relPath
        if( -not (Test-Path -LiteralPath $doneFinalPath) ) { $null = New-Item -Type Directory $doneFinalPath }
        $local:moveParams = [ordered]@{
            LiteralPath=$srcFile.FullName
            Destination=$doneFinalPath
        }
        if( Test-Path (Join-Path $doneFinalPath $($srcFile.Name))) {
            $moveParams.Confirm = $true
            $moveParams.Force = $true
        }
        Move-Item @moveParams
    }

    if( $ActionStatus -eq 'Skipped' -and $MuteSkipped ) { continue }
    [PSCustomObject]([ordered]@{
        Action = $ActionStatus
        SrcMoved = $(if($DonePath){ $true } else {$false})
        Path = $relPath
        File = $srcFile.BaseName
    })
}