param(
    [string]$OutputFile = "code_export.txt"
)

$ErrorActionPreference = "Stop"

$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path

if ([System.IO.Path]::IsPathRooted($OutputFile)) {
    $OutputPath = $OutputFile
}
else {
    $OutputPath = Join-Path $ScriptDir $OutputFile
}

$ExcludedDirectories = @("node_modules", "vendor", "dist", "build", "images")
$ExcludedExtensions = @(
    ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".svg", ".ico", ".webp",
    ".mp4", ".mov", ".avi", ".mkv", ".webm",
    ".mp3", ".wav", ".ogg", ".flac",
    ".pdf",
    ".zip", ".tar", ".gz", ".bz2", ".rar", ".7z",
    ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
    ".eot", ".ttf", ".woff", ".woff2"
)

function Get-RelativePath {
    param(
        [string]$BasePath,
        [string]$FullPath
    )

    $baseUri = [System.Uri](([System.IO.Path]::GetFullPath($BasePath)).TrimEnd("\") + "\")
    $fullUri = [System.Uri]([System.IO.Path]::GetFullPath($FullPath))
    $relativeUri = $baseUri.MakeRelativeUri($fullUri)

    return [System.Uri]::UnescapeDataString($relativeUri.ToString()).Replace("/", "\")
}

function Test-IsProbablyBinary {
    param(
        [string]$Path
    )

    try {
        $stream = [System.IO.File]::OpenRead($Path)
        try {
            $buffer = New-Object byte[] 4096
            $bytesRead = $stream.Read($buffer, 0, $buffer.Length)
        }
        finally {
            $stream.Dispose()
        }
    }
    catch {
        return $true
    }

    if ($bytesRead -ge 2) {
        if (
            ($buffer[0] -eq 0xFF -and $buffer[1] -eq 0xFE) -or
            ($buffer[0] -eq 0xFE -and $buffer[1] -eq 0xFF)
        ) {
            return $false
        }
    }

    if ($bytesRead -ge 3) {
        if ($buffer[0] -eq 0xEF -and $buffer[1] -eq 0xBB -and $buffer[2] -eq 0xBF) {
            return $false
        }
    }

    if ($bytesRead -ge 4) {
        if (
            ($buffer[0] -eq 0xFF -and $buffer[1] -eq 0xFE -and $buffer[2] -eq 0x00 -and $buffer[3] -eq 0x00) -or
            ($buffer[0] -eq 0x00 -and $buffer[1] -eq 0x00 -and $buffer[2] -eq 0xFE -and $buffer[3] -eq 0xFF)
        ) {
            return $false
        }
    }

    for ($index = 0; $index -lt $bytesRead; $index++) {
        if ($buffer[$index] -eq 0) {
            return $true
        }
    }

    return $false
}

function Test-ShouldSkipFile {
    param(
        [System.IO.FileInfo]$File
    )

    $fullPath = [System.IO.Path]::GetFullPath($File.FullName)
    if ($fullPath -eq [System.IO.Path]::GetFullPath($OutputPath)) {
        return $true
    }

    $relativePath = Get-RelativePath -BasePath $ScriptDir -FullPath $File.FullName

    if ($relativePath -eq "export_code.sh" -or $relativePath -eq "export_code.ps1") {
        return $true
    }

    $segments = $relativePath -split "[\\/]+"
    foreach ($segment in $segments) {
        if ($segment.StartsWith(".")) {
            return $true
        }

        if ($ExcludedDirectories -contains $segment) {
            return $true
        }
    }

    if ($ExcludedExtensions -contains $File.Extension) {
        return $true
    }

    return $false
}

if (Test-Path -LiteralPath $OutputPath) {
    Remove-Item -LiteralPath $OutputPath -Force
}

$builder = New-Object System.Text.StringBuilder

$files = Get-ChildItem -LiteralPath $ScriptDir -File -Recurse | Sort-Object {
    (Get-RelativePath -BasePath $ScriptDir -FullPath $_.FullName).Replace("\", "/")
}

foreach ($file in $files) {
    if (Test-ShouldSkipFile -File $file) {
        continue
    }

    if (Test-IsProbablyBinary -Path $file.FullName) {
        continue
    }

    try {
        $content = Get-Content -LiteralPath $file.FullName -Raw -ErrorAction Stop
    }
    catch {
        continue
    }

    $relativePath = (Get-RelativePath -BasePath $ScriptDir -FullPath $file.FullName).Replace("\", "/")

    [void]$builder.AppendLine(("`"./{0}`" : " -f $relativePath))
    [void]$builder.AppendLine('"""')
    [void]$builder.Append($content)

    if (-not $content.EndsWith("`n")) {
        [void]$builder.AppendLine()
    }

    [void]$builder.AppendLine('"""')
    [void]$builder.AppendLine()
    [void]$builder.AppendLine()
}

$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText($OutputPath, $builder.ToString(), $utf8NoBom)

Write-Host ("Code export finished. Output saved to {0}" -f $OutputPath)
