Functions/Invoke-PgSchema.ps1

function Invoke-PgSchema {
    <#
    .SYNOPSIS
        Ejecuta acciones de pgschema para despliegue declarativo de esquemas PostgreSQL.
 
    .DESCRIPTION
        Cmdlet que envuelve pgschema CLI (ejecutado vía WSL) con configuración declarativa
        via pgschema.yaml. Soporta las acciones: Init, Dump, Plan, Apply y Script.
 
        pgschema trabaja schema-por-schema. El archivo pgschema.yaml define una lista
        de schemas, y el cmdlet itera sobre todos ellos (o sobre uno si se usa -Schema).
 
        Las credenciales se leen del archivo .env (gitignored), usando variables PG* estándar.
        Los parámetros de esquema se leen de pgschema.yaml (versionado).
 
        Debe ejecutarse desde el directorio db/ del proyecto (donde está pgschema.yaml).
 
        Referencia: https://www.pgschema.com
 
    .PARAMETER Init
        Genera los archivos de configuración (pgschema.yaml y .env) en el directorio actual.
 
    .PARAMETER Plan
        Genera un plan de migración comparando el estado deseado (main.sql) contra la BD.
        No modifica la base de datos. Equivalente a DeployReport en SqlPackage.
 
    .PARAMETER Apply
        Aplica el plan de migración a la base de datos.
        Usa el plan.json generado por -Plan, o genera uno sobre la marcha con --file.
 
    .PARAMETER Dump
        Captura el esquema actual de la BD y lo muestra por stdout (o a archivo).
        Equivalente a Extract en SqlPackage.
 
    .PARAMETER Script
        Genera el script SQL exacto que Apply ejecutaría, sin modificar la BD.
        Equivalente a Script en SqlPackage.
 
    .PARAMETER Schema
        Nombre del schema a procesar. Si se omite, procesa todos los schemas
        definidos en pgschema.yaml. Válido para Plan, Apply, Dump y Script.
 
    .EXAMPLE
        Invoke-PgSchema -Init
 
        Genera pgschema.yaml y .env en el directorio actual.
 
    .EXAMPLE
        Invoke-PgSchema -Plan
 
        Genera plan de migración para TODOS los schemas definidos en pgschema.yaml.
 
    .EXAMPLE
        Invoke-PgSchema -Plan -Schema auth
 
        Genera plan de migración solo para el schema 'auth'.
 
    .EXAMPLE
        Invoke-PgSchema -Apply -Schema auth
 
        Aplica el plan de migración solo para el schema 'auth'.
 
    .EXAMPLE
        Invoke-PgSchema -Dump
 
        Muestra el esquema actual de la BD para todos los schemas.
 
    .EXAMPLE
        Invoke-PgSchema -Script -Schema auth
 
        Genera el SQL que se ejecutaría para el schema 'auth'.
 
    .NOTES
        Requiere:
        - WSL con Ubuntu (pgschema instalado en /usr/local/bin/)
        - PostgreSQL accesible desde WSL
        - Archivos pgschema.yaml y .env configurados (usar -Init para generarlos)
 
        Author: @ccisnedev
        Version: 1.1.0
    #>

    [CmdletBinding(DefaultParameterSetName = 'Plan')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Init',
            HelpMessage = "Genera archivos de configuración (pgschema.yaml y .env)")]
        [switch]$Init,

        [Parameter(Mandatory, ParameterSetName = 'Plan',
            HelpMessage = "Genera plan de migración (dry-run, no modifica la BD)")]
        [switch]$Plan,

        [Parameter(Mandatory, ParameterSetName = 'Apply',
            HelpMessage = "Aplica el plan de migración a la BD")]
        [switch]$Apply,

        [Parameter(Mandatory, ParameterSetName = 'Dump',
            HelpMessage = "Captura el esquema actual de la BD")]
        [switch]$Dump,

        [Parameter(Mandatory, ParameterSetName = 'Script',
            HelpMessage = "Genera script SQL de los cambios sin aplicarlos")]
        [switch]$Script,

        [Parameter(ParameterSetName = 'Plan')]
        [Parameter(ParameterSetName = 'Apply')]
        [Parameter(ParameterSetName = 'Dump')]
        [Parameter(ParameterSetName = 'Script')]
        [string]$Schema
    )

    begin {
        $ErrorActionPreference = 'Stop'
    }

    process {
        # Banner
        Write-Host ""
        Write-Host "╔══════════════════════════════════════════════════╗" -ForegroundColor Cyan
        Write-Host "║ pgschema — macss-devops ║" -ForegroundColor Cyan
        Write-Host "╚══════════════════════════════════════════════════╝" -ForegroundColor Cyan
        Write-Host ""

        switch ($PSCmdlet.ParameterSetName) {
            'Init' {
                Write-Host " Inicializando configuración pgschema..." -ForegroundColor Cyan
                Write-Host ""
                New-PgSchemaConfig
                Write-Host ""
                Write-Host " Configuración creada. Edite los archivos según su entorno:" -ForegroundColor Green
                Write-Host " pgschema.yaml → schemas y opciones de migración (versionar)" -ForegroundColor DarkGray
                Write-Host " .env → credenciales PostgreSQL PG* (NO versionar)" -ForegroundColor DarkGray
                Write-Host ""
            }

            'Plan' {
                $resolved = Resolve-PgSchemaTargets -Schema $Schema
                $config   = $resolved.Config
                $schemas  = $resolved.Schemas
                $envVars  = $resolved.EnvVars

                try {
                    Write-Host " Host: $($envVars['PGHOST']):$($envVars['PGPORT'])" -ForegroundColor Cyan
                    Write-Host " Database: $($envVars['PGDATABASE'])" -ForegroundColor Cyan
                    Write-Host " Schemas: $($schemas.name -join ', ')" -ForegroundColor Cyan
                    Write-Host " Modo: PLAN (dry-run)" -ForegroundColor Cyan
                    Write-Host ""

                    foreach ($s in $schemas) {
                        Write-Host " ─── Schema: $($s.name) ───" -ForegroundColor Magenta
                        Write-Host " File: $($s.file)" -ForegroundColor DarkGray
                        Write-Host ""

                        $extraArgs = @()
                        if ($config.plan_options) {
                            if ($config.plan_options.output_human) {
                                $extraArgs += '--output-human', $config.plan_options.output_human
                            }
                        }
                        # JSON plan siempre va al archivo del schema
                        $extraArgs += '--output-json', $s.plan

                        $pgArgs = Build-PgSchemaArgs -Action 'plan' -SchemaEntry $s -EnvVars $envVars -ExtraArgs $extraArgs

                        Invoke-PgSchemaWSL -Arguments $pgArgs -Password $envVars['PGPASSWORD']

                        Write-Host ""
                        Write-Host " Plan: $($s.plan)" -ForegroundColor DarkGray
                        Write-Host ""
                    }

                    Write-Host " Plan generado exitosamente" -ForegroundColor Green
                    Write-Host ""
                }
                finally {
                    if ($envVars) { $envVars['PGPASSWORD'] = $null }
                    [System.GC]::Collect()
                }
            }

            'Apply' {
                $resolved = Resolve-PgSchemaTargets -Schema $Schema
                $config   = $resolved.Config
                $schemas  = $resolved.Schemas
                $envVars  = $resolved.EnvVars

                try {
                    Write-Host " Host: $($envVars['PGHOST']):$($envVars['PGPORT'])" -ForegroundColor Cyan
                    Write-Host " Database: $($envVars['PGDATABASE'])" -ForegroundColor Cyan
                    Write-Host " Schemas: $($schemas.name -join ', ')" -ForegroundColor Cyan
                    Write-Host " Modo: APPLY" -ForegroundColor Yellow
                    Write-Host ""

                    # Confirmación global (salvo auto_approve)
                    if (-not ($config.apply_options -and $config.apply_options.auto_approve)) {
                        $confirm = Read-Host " ¿Aplicar cambios a la BD para [$($schemas.name -join ', ')]? (S/N)"
                        if ($confirm -notmatch '^[Ss]$') {
                            Write-Host " Aplicación cancelada." -ForegroundColor Yellow
                            return
                        }
                    }

                    foreach ($s in $schemas) {
                        Write-Host " ─── Schema: $($s.name) ───" -ForegroundColor Magenta

                        $extraArgs = @('--auto-approve')

                        # Determinar si usar --plan o --file
                        if ($s.plan -and (Test-Path $s.plan)) {
                            Write-Host " Usando plan: $($s.plan)" -ForegroundColor Cyan
                            $extraArgs += '--plan', $s.plan
                        }
                        else {
                            Write-Host " Sin plan previo, generando sobre la marcha..." -ForegroundColor Yellow
                        }

                        # Lock timeout
                        if ($config.apply_options -and $config.apply_options.lock_timeout) {
                            $extraArgs += '--lock-timeout', $config.apply_options.lock_timeout
                        }

                        $pgArgs = Build-PgSchemaArgs -Action 'apply' -SchemaEntry $s -EnvVars $envVars -ExtraArgs $extraArgs

                        Invoke-PgSchemaWSL -Arguments $pgArgs -Password $envVars['PGPASSWORD']

                        Write-Host ""
                    }

                    Write-Host " Migración aplicada exitosamente" -ForegroundColor Green
                    Write-Host ""
                }
                finally {
                    if ($envVars) { $envVars['PGPASSWORD'] = $null }
                    [System.GC]::Collect()
                }
            }

            'Dump' {
                $resolved = Resolve-PgSchemaTargets -Schema $Schema
                $config   = $resolved.Config
                $schemas  = $resolved.Schemas
                $envVars  = $resolved.EnvVars

                try {
                    Write-Host " Host: $($envVars['PGHOST']):$($envVars['PGPORT'])" -ForegroundColor Cyan
                    Write-Host " Database: $($envVars['PGDATABASE'])" -ForegroundColor Cyan
                    Write-Host " Schemas: $($schemas.name -join ', ')" -ForegroundColor Cyan
                    Write-Host " Modo: DUMP (snapshot)" -ForegroundColor Cyan
                    Write-Host ""

                    foreach ($s in $schemas) {
                        Write-Host " ─── Schema: $($s.name) ───" -ForegroundColor Magenta
                        Write-Host ""

                        $extraArgs = @()
                        if ($config.dump_options) {
                            if ($config.dump_options.multi_file) {
                                $extraArgs += '--multi-file'
                                $extraArgs += '--file', $s.file
                            }
                            if ($config.dump_options.no_comments) {
                                $extraArgs += '--no-comments'
                            }
                        }

                        $pgArgs = Build-PgSchemaArgs -Action 'dump' -SchemaEntry $s -EnvVars $envVars -ExtraArgs $extraArgs

                        Invoke-PgSchemaWSL -Arguments $pgArgs -Password $envVars['PGPASSWORD']

                        Write-Host ""
                    }

                    Write-Host " Dump completado" -ForegroundColor Green
                    Write-Host ""
                }
                finally {
                    if ($envVars) { $envVars['PGPASSWORD'] = $null }
                    [System.GC]::Collect()
                }
            }

            'Script' {
                $resolved = Resolve-PgSchemaTargets -Schema $Schema
                $config   = $resolved.Config
                $schemas  = $resolved.Schemas
                $envVars  = $resolved.EnvVars

                try {
                    Write-Host " Host: $($envVars['PGHOST']):$($envVars['PGPORT'])" -ForegroundColor Cyan
                    Write-Host " Database: $($envVars['PGDATABASE'])" -ForegroundColor Cyan
                    Write-Host " Schemas: $($schemas.name -join ', ')" -ForegroundColor Cyan
                    Write-Host " Modo: SCRIPT SQL" -ForegroundColor Cyan
                    Write-Host ""

                    foreach ($s in $schemas) {
                        Write-Host " ─── Schema: $($s.name) ───" -ForegroundColor Magenta

                        $scriptPath = "$($s.name)/deploy_script_$(Get-Date -Format 'yyyyMMdd_HHmmss').sql"

                        $extraArgs = @('--output-sql', $scriptPath)

                        $pgArgs = Build-PgSchemaArgs -Action 'plan' -SchemaEntry $s -EnvVars $envVars -ExtraArgs $extraArgs

                        Invoke-PgSchemaWSL -Arguments $pgArgs -Password $envVars['PGPASSWORD']

                        Write-Host ""
                        Write-Host " Script: $scriptPath" -ForegroundColor Green
                        Write-Host ""
                    }
                }
                finally {
                    if ($envVars) { $envVars['PGPASSWORD'] = $null }
                    [System.GC]::Collect()
                }
            }
        }
    }
}

<#
.SYNOPSIS
    Resuelve la lista de schemas a procesar y carga config + env.
 
.DESCRIPTION
    Helper interno que centraliza la validación de prerequisitos,
    lectura de config, .env y filtrado por -Schema.
 
.PARAMETER Schema
    Nombre del schema a filtrar. Si es vacío, retorna todos.
#>

function Resolve-PgSchemaTargets {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Schema
    )

    if (-not (Test-Path ".\pgschema.yaml")) { throw "No se encontró pgschema.yaml. Ejecute 'Invoke-PgSchema -Init'." }
    if (-not (Test-Path ".\.env")) { throw "No se encontró .env. Ejecute 'Invoke-PgSchema -Init'." }

    $config = Read-PgSchemaConfig
    $envConfig = Read-DotEnv -Path ".\.env"
    $envVars = $envConfig.Env

    # Filtrar schemas
    if ($Schema) {
        $schemas = @($config.schemas | Where-Object { $_.name -eq $Schema })
        if ($schemas.Count -eq 0) {
            $available = ($config.schemas | ForEach-Object { $_.name }) -join ', '
            throw "Schema '$Schema' no encontrado en pgschema.yaml. Disponibles: $available"
        }
    }
    else {
        $schemas = @($config.schemas)
    }

    return @{
        Config  = $config
        Schemas = $schemas
        EnvVars = $envVars
    }
}