#!/usr/bin/env pwsh
<#
.SYNOPSIS
    Converts C# files from block-scoped to file-scoped namespaces and fixes System namespace conflicts.

.DESCRIPTION
    This script converts all C# files in MediaBrowser.Model from block-scoped namespaces
    to file-scoped namespaces, and replaces System using directives with global::System
    to resolve conflicts with MediaBrowser.Model.System namespace.

.PARAMETER Path
    The root path to search for C# files. Defaults to "MediaBrowser.Model"

.PARAMETER DryRun
    If specified, shows what would be changed without actually modifying files

.PARAMETER Verbose
    Shows detailed processing information

.PARAMETER BackupFiles
    Creates .bak files before conversion

.EXAMPLE
    .\ConvertToFileScopedNamespaces.ps1 -DryRun

.EXAMPLE
    .\ConvertToFileScopedNamespaces.ps1 -BackupFiles

.EXAMPLE
    .\ConvertToFileScopedNamespaces.ps1 -Path "MediaBrowser.Model\Dlna" -Verbose
#>

param(
    [string]$Path = "MediaBrowser.Model",
    [switch]$DryRun,
    [switch]$BackupFiles
)

$ErrorActionPreference = "Stop"
$VerbosePreference = if ($PSBoundParameters.ContainsKey('Verbose')) { 'Continue' } else { 'SilentlyContinue' }

function Get-IndentString {
    param([string]$Line)

    if ($Line -match '^(\s+)') {
        return $Matches[1]
    }
    return ""
}

function Get-IndentLevel {
    param([string]$IndentString)

    # Count spaces (4 spaces = 1 level) or tabs (1 tab = 1 level)
    $spaces = ($IndentString.ToCharArray() | Where-Object { $_ -eq ' ' }).Count
    $tabs = ($IndentString.ToCharArray() | Where-Object { $_ -eq "`t" }).Count

    return [Math]::Max($tabs, [Math]::Floor($spaces / 4))
}

function Remove-OneIndentLevel {
    param([string]$Line, [ref]$IndentChar, [ref]$IndentSize)

    if ([string]::IsNullOrWhiteSpace($Line)) {
        return $Line
    }

    $indent = Get-IndentString -Line $Line

    if ($indent.Length -eq 0) {
        return $Line
    }

    # Detect indent character and size on first indented line
    if ($IndentChar.Value -eq $null) {
        if ($indent[0] -eq "`t") {
            $IndentChar.Value = "`t"
            $IndentSize.Value = 1
        } else {
            $IndentChar.Value = ' '
            # Detect indent size (usually 4 spaces)
            $IndentSize.Value = 4
            if ($indent.Length -ge 2) {
                # Try to detect actual indent size
                for ($i = 1; $i -le 8; $i++) {
                    if ($indent.Length % $i -eq 0) {
                        $IndentSize.Value = $i
                        break
                    }
                }
            }
        }
        Write-Verbose "Detected indent: '$($IndentChar.Value)' x $($IndentSize.Value)"
    }

    # Remove one level of indentation
    if ($IndentChar.Value -eq "`t") {
        if ($Line -match '^\t(.*)') {
            return $Matches[1]
        }
    } else {
        $pattern = "^" + (' ' * $IndentSize.Value) + '(.*)'
        if ($Line -match $pattern) {
            return $Matches[1]
        }
    }

    return $Line
}

function Test-FileScopedNamespace {
    param([string]$Content)

    # Check if already using file-scoped namespace (semicolon after namespace declaration)
    return $Content -match 'namespace\s+[\w\.]+\s*;'
}

function Test-BlockScopedNamespace {
    param([string]$Content)

    # Check for block-scoped namespace
    return $Content -match 'namespace\s+[\w\.]+\s*\r?\n?\s*\{'
}

function Add-GlobalPrefix {
    param([string]$Content)

    # System namespaces to prefix (ordered from most specific to least)
    $systemNamespaces = @(
        'System\.Xml\.Serialization',
        'System\.ComponentModel\.DataAnnotations',
        'System\.ComponentModel',
        'System\.Collections\.Generic',
        'System\.Collections\.Concurrent',
        'System\.Collections',
        'System\.Diagnostics\.CodeAnalysis',
        'System\.Diagnostics',
        'System\.Globalization',
        'System\.Linq\.Expressions',
        'System\.Linq',
        'System\.Text\.Json\.Serialization',
        'System\.Text\.Json',
        'System\.Text\.RegularExpressions',
        'System\.Text',
        'System\.Threading\.Tasks',
        'System\.Threading',
        'System\.Runtime\.Serialization',
        'System\.Runtime\.CompilerServices',
        'System\.Runtime',
        'System\.Net\.Http',
        'System\.Net\.Sockets',
        'System\.Net\.WebSockets',
        'System\.Net\.Mime',
        'System\.Net',
        'System\.IO\.Compression',
        'System\.IO',
        'System\.Security',
        'System'  # Must be last to avoid partial matches
    )

    foreach ($ns in $systemNamespaces) {
        # Don't add global:: if it's already there
        # Match: "using System.X" but not "using global::System.X"
        $pattern = '(\s+using\s+)(?!global::)(' + $ns + ')(\s|;)'
        $replacement = '${1}global::${2}${3}'
        $Content = $Content -replace $pattern, $replacement
    }

    return $Content
}

function Convert-ToFileScopedNamespace {
    param(
        [string]$FilePath,
        [switch]$DryRun,
        [switch]$CreateBackup
    )

    $content = Get-Content -Path $FilePath -Raw
    $originalContent = $content
    $modified = $false

    # Check current state
    $hasFileScopedNS = Test-FileScopedNamespace -Content $content
    $hasBlockScopedNS = Test-BlockScopedNamespace -Content $content

    if ($hasFileScopedNS) {
        Write-Verbose "  Already using file-scoped namespace"

        # But still add global:: prefixes if needed
        $contentWithGlobal = Add-GlobalPrefix -Content $content
        if ($contentWithGlobal -ne $content) {
            $content = $contentWithGlobal
            $modified = $true
            Write-Host "  [UPDATED] Added global:: prefixes" -ForegroundColor Yellow
        } else {
            Write-Host "  [SKIP] Already correct" -ForegroundColor Gray
            return $false
        }
    }
    elseif (-not $hasBlockScopedNS) {
        Write-Host "  [SKIP] No namespace found" -ForegroundColor Gray
        return $false
    }
    else {
        Write-Verbose "  Converting block-scoped to file-scoped namespace"

        # Extract namespace name and line
        if ($content -match 'namespace\s+([\w\.]+)\s*\r?\n?\s*\{') {
            $namespaceName = $Matches[1]
            Write-Verbose "  Namespace: $namespaceName"
        } else {
            Write-Warning "  Could not parse namespace"
            return $false
        }

        # Step 1: Convert namespace declaration to file-scoped
        $content = $content -replace '(namespace\s+[\w\.]+)\s*\r?\n?\s*\{', '$1;'
        $modified = $true

        # Step 2: Add global:: prefixes to System using directives
        $content = Add-GlobalPrefix -Content $content

        # Step 3: Parse into lines for indentation removal
        $lines = $content -split '\r?\n'
        $hasWindowsLineEndings = $originalContent -match '\r\n'

        # Find namespace declaration line
        $namespaceLineIndex = -1
        for ($i = 0; $i -lt $lines.Length; $i++) {
            if ($lines[$i] -match '^namespace\s+[\w\.]+\s*;') {
                $namespaceLineIndex = $i
                break
            }
        }

        if ($namespaceLineIndex -lt 0) {
            Write-Warning "  Could not find namespace declaration after conversion"
            return $false
        }

        # Step 4: Remove one level of indentation from all lines after namespace
        $indentChar = $null
        $indentSize = 4
        $indentCharRef = [ref]$indentChar
        $indentSizeRef = [ref]$indentSize

        for ($i = $namespaceLineIndex + 1; $i -lt $lines.Length; $i++) {
            $lines[$i] = Remove-OneIndentLevel -Line $lines[$i] -IndentChar $indentCharRef -IndentSize $indentSizeRef
        }

        # Step 5: Remove the closing brace of the namespace
        # Find the last non-empty, non-whitespace line that's just a closing brace
        $closingBraceIndex = -1
        for ($i = $lines.Length - 1; $i -gt $namespaceLineIndex; $i--) {
            $trimmed = $lines[$i].Trim()
            if ($trimmed -eq '}') {
                $closingBraceIndex = $i
                break
            }
            elseif ($trimmed -ne '' -and $trimmed -ne '}') {
                # Found non-brace content, stop looking
                break
            }
        }

        if ($closingBraceIndex -ge 0) {
            Write-Verbose "  Removing closing brace at line $closingBraceIndex"
            $lines = $lines[0..($closingBraceIndex-1)] + $lines[($closingBraceIndex+1)..($lines.Length-1)]
        }

        # Step 6: Rejoin lines
        $lineEnding = if ($hasWindowsLineEndings) { "`r`n" } else { "`n" }
        $content = ($lines | Where-Object { $_ -ne $null }) -join $lineEnding

        # Step 7: Clean up trailing whitespace and ensure single newline at end
        $content = $content.TrimEnd()
        $content += $lineEnding

        Write-Host "  [CONVERTED] Block-scoped → File-scoped" -ForegroundColor Green
    }

    # Apply changes
    if ($modified -and $content -ne $originalContent) {
        if ($DryRun) {
            Write-Host "  [DRY-RUN] Would save changes" -ForegroundColor Cyan
            return $true
        }

        if ($CreateBackup) {
            $backupPath = "$FilePath.bak"
            Copy-Item -Path $FilePath -Destination $backupPath -Force
            Write-Verbose "  Created backup: $backupPath"
        }

        # Write with correct encoding (UTF-8 without BOM for .cs files)
        [System.IO.File]::WriteAllText($FilePath, $content, [System.Text.UTF8Encoding]::new($false))
        return $true
    }

    return $false
}

# Main script
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "C# File-Scoped Namespace Converter" -ForegroundColor Cyan
Write-Host "Fixes MediaBrowser.Model.System namespace conflicts" -ForegroundColor Cyan
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host

if ($DryRun) {
    Write-Host "DRY RUN MODE - No files will be modified" -ForegroundColor Yellow
    Write-Host
}

if ($BackupFiles) {
    Write-Host "Backup mode enabled - .bak files will be created" -ForegroundColor Yellow
    Write-Host
}

if (-not (Test-Path $Path)) {
    Write-Error "❌ Path not found: $Path"
    exit 1
}

# Find all C# files
$files = Get-ChildItem -Path $Path -Filter "*.cs" -Recurse | Where-Object { 
    $_.FullName -notmatch "\\obj\\" -and 
    $_.FullName -notmatch "\\bin\\" -and
    $_.FullName -notmatch "\\Properties\\AssemblyInfo\.cs$"
}

Write-Host "Found $($files.Count) C# files in $Path" -ForegroundColor Cyan
Write-Host

$convertedCount = 0
$skippedCount = 0
$errorCount = 0
$updatedCount = 0

foreach ($file in $files) {
    $relativePath = $file.FullName.Replace((Get-Location).Path, "").TrimStart([char]92, [char]47)
    Write-Host "[Processing] $relativePath" -ForegroundColor White

    try {
        $result = Convert-ToFileScopedNamespace -FilePath $file.FullName -DryRun:$DryRun -CreateBackup:$BackupFiles

        if ($result) {
            if ($result -eq "updated") {
                $updatedCount++
            } else {
                $convertedCount++
            }
        } else {
            $skippedCount++
        }
    }
    catch {
        Write-Host "  [ERROR] $_" -ForegroundColor Red
        Write-Verbose $_.ScriptStackTrace
        $errorCount++
    }
}

Write-Host
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "Summary:" -ForegroundColor Cyan
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "  Total files:          $($files.Count)" -ForegroundColor White
Write-Host "  Converted:            $convertedCount" -ForegroundColor Green
Write-Host "  Updated (global::):   $updatedCount" -ForegroundColor Yellow
Write-Host "  Skipped:              $skippedCount" -ForegroundColor Gray
Write-Host "  Errors:               $errorCount" -ForegroundColor Red

if ($DryRun) {
    Write-Host
    Write-Host "This was a DRY RUN. Run without -DryRun to apply changes." -ForegroundColor Yellow
} elseif ($convertedCount -gt 0 -or $updatedCount -gt 0) {
    Write-Host
    Write-Host "Conversion complete! Next steps:" -ForegroundColor Green
    Write-Host "  1. Review changes with: git diff" -ForegroundColor White
    Write-Host "  2. Build project: dotnet build MediaBrowser.Model\MediaBrowser.Model.csproj" -ForegroundColor White
    Write-Host "  3. If successful: git add . && git commit -m `"Convert to file-scoped namespaces`"" -ForegroundColor White
}

Write-Host ("=" * 80) -ForegroundColor Cyan

exit $errorCount
