param(
    [Parameter(Mandatory = $true)]
    [string]$SourceRoot,

    [Parameter(Mandatory = $true)]
    [string]$StagingRoot,

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

$ErrorActionPreference = 'Stop'

$source = (Resolve-Path -LiteralPath $SourceRoot).Path
$stage = [System.IO.Path]::GetFullPath($StagingRoot)
$archive = [System.IO.Path]::GetFullPath($ArchivePath)

if (-not (Test-Path -LiteralPath $source -PathType Container)) {
    throw 'SourceRoot debe ser un directorio existente.'
}
if ($stage.StartsWith($source, [System.StringComparison]::OrdinalIgnoreCase)) {
    throw 'StagingRoot no puede estar dentro del arbol fuente.'
}
if (Test-Path -LiteralPath $stage) {
    if (Get-ChildItem -LiteralPath $stage -Force | Select-Object -First 1) {
        throw 'StagingRoot ya existe y no esta vacio; no se sobrescribira.'
    }
}
if (Test-Path -LiteralPath $archive) {
    throw 'ArchivePath ya existe; no se sobrescribira.'
}

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

$excluded = New-Object System.Collections.Generic.List[string]
$copied = 0

Get-ChildItem -LiteralPath $source -Recurse -Force -File | ForEach-Object {
    $relative = $_.FullName.Substring($source.Length).TrimStart('\', '/').Replace('\', '/')
    $leaf = $_.Name
    $extension = $_.Extension.ToLowerInvariant()
    $skip = $false

    if ($relative -eq 'core/private_config.php' -or $relative -eq 'auditor.php') {
        $skip = $true
    } elseif ($relative -match '(^|/)\.env($|\.)') {
        $skip = $true
    } elseif ($relative -like 'uploads/*' -and $leaf -ne '.htaccess') {
        $skip = $true
    } elseif ($relative -like 'assets/uploads/*' -and $leaf -ne '.htaccess') {
        $skip = $true
    } elseif ($relative -like 'storage/*' -and $leaf -ne '.htaccess') {
        $skip = $true
    } elseif ($extension -in @('.dump', '.bak', '.old', '.zip', '.tar', '.gz', '.tgz')) {
        $skip = $true
    } elseif ($extension -eq '.sql' -and $relative -notlike 'database/migrations/*' -and $relative -notlike 'database/preflight/*') {
        $skip = $true
    }

    if ($skip) {
        $excluded.Add($relative)
        return
    }

    $destination = Join-Path $stage $relative
    $destinationDirectory = Split-Path -Parent $destination
    if (-not (Test-Path -LiteralPath $destinationDirectory)) {
        New-Item -ItemType Directory -Path $destinationDirectory -Force | Out-Null
    }
    Copy-Item -LiteralPath $_.FullName -Destination $destination
    $copied++
}

$forbidden = Get-ChildItem -LiteralPath $stage -Recurse -Force -File | Where-Object {
    $relative = $_.FullName.Substring($stage.Length).TrimStart('\', '/').Replace('\', '/')
    $leaf = $_.Name
    return (
        $relative -eq 'core/private_config.php' -or
        $relative -eq 'auditor.php' -or
        ($relative -like 'uploads/*' -and $leaf -ne '.htaccess') -or
        ($relative -like 'assets/uploads/*' -and $leaf -ne '.htaccess') -or
        ($relative -like 'storage/*' -and $leaf -ne '.htaccess') -or
        $relative -match '(^|/)\.env($|\.)'
    )
}

if ($forbidden) {
    throw 'La verificacion del staging encontro archivos privados u operativos.'
}

$archiveDirectory = Split-Path -Parent $archive
if (-not (Test-Path -LiteralPath $archiveDirectory)) {
    New-Item -ItemType Directory -Path $archiveDirectory -Force | Out-Null
}

Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem

# Se controla el nombre de cada entrada para que el ZIP siempre exponga el
# overlay bajo app/, tal como lo exige deploy_billing_readiness.sh.
$zip = [System.IO.Compression.ZipFile]::Open(
    $archive,
    [System.IO.Compression.ZipArchiveMode]::Create
)
try {
    Get-ChildItem -LiteralPath $stage -Recurse -Force -File | ForEach-Object {
        $relative = $_.FullName.Substring($stage.Length).TrimStart('\', '/').Replace('\', '/')
        $entryName = 'app/' + $relative
        [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
            $zip,
            $_.FullName,
            $entryName,
            [System.IO.Compression.CompressionLevel]::Optimal
        ) | Out-Null
    }
} finally {
    $zip.Dispose()
}

[pscustomobject]@{
    Source = $source
    Staging = $stage
    Archive = $archive
    CopiedFiles = $copied
    ExcludedFiles = $excluded.Count
    ArchiveBytes = (Get-Item -LiteralPath $archive).Length
    Sha256 = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash
}
