param(
    [string]$SourceDir = (Get-Location).Path,
    [string]$LibraryRoot,
    [switch]$Move,
    [switch]$Recurse,
    [switch]$WhatIf
)

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

function Get-NormalizedTitle {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Title
    )

    $clean = $Title.Trim()
    $clean = $clean -replace '\s+', ' '
    $clean = $clean -replace '(^| )Dont( |$)', "`$1Don't`$2"
    return $clean.Trim()
}

function Get-DestinationInfo {
    param(
        [Parameter(Mandatory = $true)]
        [System.IO.FileInfo]$File,

        [Parameter(Mandatory = $true)]
        [string]$LibraryRootPath
    )

    $pattern = '^(?<show>.+?) Series (?<series>\d+) - (?<episode>\d+)\. (?<title>.+?)(?: [a-z0-9]{7,})?(?: audiodescribed)?(?<ext>\.[^.]+)$'
    $match = [regex]::Match($File.Name, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)

    if (-not $match.Success) {
        return $null
    }

    $showName = $match.Groups['show'].Value.Trim()
    $seriesNumber = [int]$match.Groups['series'].Value
    $episodeNumber = [int]$match.Groups['episode'].Value
    $title = Get-NormalizedTitle -Title $match.Groups['title'].Value
    $extension = $match.Groups['ext'].Value

    $seriesFolder = Join-Path -Path (Join-Path -Path $LibraryRootPath -ChildPath $showName) -ChildPath ("Series {0}" -f $seriesNumber)
    $destinationName = "{0:D2} - {1}{2}" -f $episodeNumber, $title, $extension
    $destinationPath = Join-Path -Path $seriesFolder -ChildPath $destinationName

    [pscustomobject]@{
        ShowName        = $showName
        SeriesNumber    = $seriesNumber
        EpisodeNumber   = $episodeNumber
        Title           = $title
        DestinationDir  = $seriesFolder
        DestinationName = $destinationName
        DestinationPath = $destinationPath
    }
}

$resolvedSource = (Resolve-Path -LiteralPath $SourceDir).Path

if ([string]::IsNullOrWhiteSpace($LibraryRoot)) {
    $LibraryRoot = $resolvedSource
}

if (-not (Test-Path -LiteralPath $LibraryRoot)) {
    throw "Library root does not exist: $LibraryRoot"
}

$resolvedLibraryRoot = (Resolve-Path -LiteralPath $LibraryRoot).Path

$getChildItemParams = @{
    LiteralPath = $resolvedSource
    File = $true
}

if ($Recurse) {
    $getChildItemParams.Recurse = $true
}

$files = Get-ChildItem @getChildItemParams | Sort-Object FullName

if (-not $files) {
    Write-Host "No files found in $resolvedSource"
    exit 0
}

foreach ($file in $files) {
    $destination = Get-DestinationInfo -File $file -LibraryRootPath $resolvedLibraryRoot

    if ($null -eq $destination) {
        Write-Warning "Skipped unmatched file: $($file.FullName)"
        continue
    }

    Write-Host ("{0} -> {1}" -f $file.Name, $destination.DestinationPath)

    if ($WhatIf) {
        continue
    }

    if (-not (Test-Path -LiteralPath $destination.DestinationDir)) {
        New-Item -ItemType Directory -Path $destination.DestinationDir | Out-Null
    }

    if (Test-Path -LiteralPath $destination.DestinationPath) {
        Write-Warning "Skipped existing destination: $($destination.DestinationPath)"
        continue
    }

    if ($Move) {
        Move-Item -LiteralPath $file.FullName -Destination $destination.DestinationPath
    }
    else {
        Copy-Item -LiteralPath $file.FullName -Destination $destination.DestinationPath
    }
}
