FilterBox/Write-SRxFilterBranch.debug.ps1

[CmdletBinding()]
param(
    [Parameter(Mandatory)] [string]$RootPath,
    [Parameter(Mandatory)] [string]$PackPath,
    [Parameter(Mandatory)] [string]$ResultPath,
    [Parameter(Mandatory=$false)] [switch]$Force,
    [Parameter(Mandatory=$false)] [ValidateSet('JSON','XML')] [string]$Format = 'JSON'
)

$ErrorActionPreference = 'Stop'

$script:DbgLogPaths = @()
try {
    $mirrorDir = Join-Path $env:TEMP 'SRxFilterLogs'
    if (-not (Test-Path $mirrorDir)) { New-Item -ItemType Directory -Path $mirrorDir -Force | Out-Null }
    $stamp = (Get-Date).ToString('yyyyMMdd-HHmmss-fff')
    $script:DbgLogPaths += (Join-Path $mirrorDir ("filter-$stamp-pid$PID.log"))
} catch { }
try { if ($ResultPath) { $script:DbgLogPaths += ($ResultPath + '.log') } } catch { }

function Write-Dbg {
    param([string]$Message, [string]$Level = 'INFO')
    $line = "{0} [{1}] (pid {2}) {3}" -f ((Get-Date).ToString('o')), $Level, $PID, $Message
    foreach ($p in $script:DbgLogPaths) { try { Add-Content -LiteralPath $p -Value $line -Encoding UTF8 } catch { } }
    try { Write-Host $line } catch { }
}
function Write-DbgException {
    param($ErrorRecord, [string]$Where = '')
    try {
        $ex = $ErrorRecord.Exception
        Write-Dbg "==================== EXCEPTION $Where ====================" 'ERROR'
        Write-Dbg ("Type : " + $ex.GetType().FullName) 'ERROR'
        Write-Dbg ("Message : " + $ex.Message) 'ERROR'
        Write-Dbg ("CategoryInfo : " + $ErrorRecord.CategoryInfo) 'ERROR'
        Write-Dbg ("FullyQualifiedId: " + $ErrorRecord.FullyQualifiedErrorId) 'ERROR'
        if ($ErrorRecord.InvocationInfo) {
            Write-Dbg ("PositionMessage : " + $ErrorRecord.InvocationInfo.PositionMessage) 'ERROR'
            Write-Dbg ("ScriptName:Line : {0}:{1}" -f $ErrorRecord.InvocationInfo.ScriptName, $ErrorRecord.InvocationInfo.ScriptLineNumber) 'ERROR'
        }
        Write-Dbg ("ScriptStackTrace:`n" + $ErrorRecord.ScriptStackTrace) 'ERROR'
        Write-Dbg (".NET StackTrace :`n" + $ex.StackTrace) 'ERROR'
        $inner = $ex.InnerException; $depth = 0
        while ($inner -and $depth -lt 6) {
            Write-Dbg ("--- Inner[$depth] : " + $inner.GetType().FullName) 'ERROR'
            Write-Dbg (" Message : " + $inner.Message) 'ERROR'
            Write-Dbg (" StackTrace :`n" + $inner.StackTrace) 'ERROR'
            $inner = $inner.InnerException; $depth++
        }
        Write-Dbg "=========================================================" 'ERROR'
    } catch { try { Write-Dbg ("Write-DbgException failed: " + $_.Exception.Message) 'ERROR' } catch {} }
}

try {
    if ($ResultPath) { $script:TranscriptPath = $ResultPath + '.transcript'; Start-Transcript -LiteralPath $script:TranscriptPath -Force | Out-Null }
} catch { Write-Dbg ("Start-Transcript failed (non-fatal): " + $_.Exception.Message) 'WARN' }

Write-Dbg "########## FILTER WORKER START ##########"
Write-Dbg ("PSVersion : " + $PSVersionTable.PSVersion.ToString() + " Edition: " + $PSVersionTable.PSEdition)
Write-Dbg ("RootPath : " + $RootPath)
Write-Dbg ("PackPath : " + $PackPath + " exists=" + (Test-Path $PackPath))
Write-Dbg ("ResultPath : " + $ResultPath)
Write-Dbg ("Force : " + [bool]$Force)

function Write-SRxFilterResult {
    param([bool]$Success, [bool]$Conflict, $Data, [string]$ErrorText)
    $payload = [pscustomobject]@{
        Success = $Success; Conflict = $Conflict; Data = $Data; Error = $ErrorText
        Pid = $PID; Ended = (Get-Date).ToString('o'); LogPath = ($script:DbgLogPaths -join ';')
    }
    $json = $payload | ConvertTo-Json -Depth 25 -Compress
    try {
        $dir = Split-Path -Parent $ResultPath
        if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
        Set-Content -LiteralPath $ResultPath -Value $json -Encoding UTF8
        Write-Dbg ("Result written: Success=$Success Conflict=$Conflict Error='$ErrorText'")
    } catch { Write-Dbg ("Failed to write result file: " + $_.Exception.Message) 'ERROR' }
    Write-Host ("__JSON__" + $json)
}

function ConvertFrom-SRxJsonFile {
    param([string]$Path)
    if (-not (Test-Path $Path)) { throw "Input file not found: $Path" }
    $raw = Get-Content -LiteralPath $Path -Raw -Encoding UTF8
    if ([string]::IsNullOrWhiteSpace($raw)) { throw "Input file empty: $Path" }
    return ($raw | ConvertFrom-Json)
}

function ConvertTo-SRxNodesStack {
    param($Branch)
    $stack = [System.Collections.Stack]::new()
    foreach ($d in $Branch) {
        $attrs = @{}
        if ($d.Attributes) { foreach ($p in $d.Attributes.PSObject.Properties) { $attrs[$p.Name] = [string]$p.Value } }
        if ($d.NodeName)            { $attrs['ts_NodeName']            = [string]$d.NodeName }
        if ($d.KeyAttribute)        { $attrs['ts_KeyAttribute']        = [string]$d.KeyAttribute }
        if ($d.KeyAttributeValue)   { $attrs['ts_KeyAttributeValue']   = [string]$d.KeyAttributeValue }
        if ($d.ProvisioningActivity){ $attrs['ts_ProvisioningActivity']= [string]$d.ProvisioningActivity }
        if ($d.CustomizationTarget) { $attrs['ts_CustomizationTarget'] = $true }
        if ($d.Deprecated)          { $attrs['ts_Deprecated']          = $true }
        $termNode = [pscustomobject]@{
            Name = [string]$d.Name; Attributes = $attrs; Scope = [int]$d.Scope
            xPath = [string]$d.XPath; Operation = [string]$d.Operation; optype = [int]$d.Optype
        }
        $stack.Push($termNode)
    }
    return ,$stack
}

try {
    Write-Dbg "STAGE 1: bootstrap begin"
    Set-Location -Path $RootPath
    $externalScript = Join-Path $RootPath 'loadmodule.ps1'
    Write-Dbg ("loadmodule : " + $externalScript + " exists=" + (Test-Path $externalScript))
    if (-not (Test-Path $externalScript)) { throw "loadmodule.ps1 not found at '$externalScript'." }
    . $externalScript
    Write-Dbg ("after dot-source: `$LoadModule is " + $(if ($null -eq $LoadModule) {'NULL'} else {$LoadModule.GetType().Name}))
    if ($null -ne $LoadModule) { $LoadModule.Invoke("SRxCore") | Out-Null }
    Write-Dbg "STAGE 1a: Initialize-SRxEnv -LoadModule2"
    Initialize-SRxEnv -LoadModule2 $LoadModule -RootPath $RootPath | Out-Null
    Connect-SRxSPOService
    if ($null -eq $global:SRxEnv) {
        if (Get-Command Start-SRxShell -ErrorAction SilentlyContinue) { Write-Dbg "STAGE 1b: Start-SRxShell -isJob"; Start-SRxShell -RootPath $RootPath -isJob | Out-Null }
        elseif (Get-Command Initialize-SRxEnv -ErrorAction SilentlyContinue) { Write-Dbg "STAGE 1b: Initialize-SRxEnv -RootPath"; Initialize-SRxEnv -RootPath $RootPath | Out-Null }
        else { throw "SRx environment could not be initialised from '$RootPath'." }
    }
    if ($null -eq $global:SRxEnv -or $null -eq $global:SRxEnv.Tenancy) { throw "global:SRxEnv or .Tenancy is null after bootstrap." }
    Write-Dbg ("STAGE 1 done. Tenancy.AdminUrl = '" + $global:SRxEnv.Tenancy.AdminUrl + "'")

    try { Import-Module (Join-Path $PSScriptRoot 'SRxFilterBox.psm1') -DisableNameChecking -Force -ErrorAction SilentlyContinue } catch { Write-Dbg ("SRxFilterBox import warn: " + $_.Exception.Message) 'WARN' }
    try { Import-Module (Join-Path $PSScriptRoot 'SRxCustomizationJson.psm1') -DisableNameChecking -Force -ErrorAction SilentlyContinue } catch { Write-Dbg ("SRxCustomizationJson import warn: " + $_.Exception.Message) 'WARN'  }
    $jsonWriterPath = Join-Path $PSScriptRoot 'Write-SRxJsonCustomization.ps1'
    Write-Dbg ("Write-SRxJsonCustomization.ps1 path: $jsonWriterPath exists=" + (Test-Path $jsonWriterPath))
    try { . $jsonWriterPath; Write-Dbg ("Write-SRxJsonCustomization loaded: " + [bool](Get-Command Write-SRxJsonCustomization -Errn SilentlyContinue)) }
    catch { Write-Dbg ("dot-source FAILED: " + $_.Exception.Message) 'ERROR' }
    Write-Dbg ("Sync-SRxLocalFilterBranch available: " + [bool](Get-Command Sync-SRxLocalFilterBranch -ErrorAction SilentlyContinue))
    Write-Dbg ("Get-SRxCustomizationWriter available: " + [bool](Get-Command Get-SRxCustomizationWriter -ErrorAction SilentlyContinue))

    Write-Dbg "STAGE 2: read pack"
    $pack = ConvertFrom-SRxJsonFile -Path $PackPath
    $operation = [string]$pack.Operation
    $scope     = [int]$pack.Scope
    Write-Dbg (" Operation=$operation Scope=$scope Optype=$($pack.Optype)")
    Write-Dbg (" Environment='$($pack.Environment)' Design='$($pack.Design)' TermID='$($pack.TermID)'")
    Write-Dbg (" BaselineModified='$($pack.BaselineModified)'")
    $bcount = 0; if ($pack.Branch) { $bcount = @($pack.Branch).Count }
    Write-Dbg (" Branch descriptors: $bcount")
    $i = 0
    foreach ($d in @($pack.Branch)) {
        $i++
        $ac = 0; if ($d.Attributes) { $ac = @($d.Attributes.PSObject.Properties).Count }
        Write-Dbg (" [$i] Name='$($d.Name)' NodeName='$($d.NodeName)' Key='$($d.KeyAttribute)'='$($d.KeyAttributeValue)' attrs=$ac Deprecated=$($d.Deprecated) Include=$($d.CustomizationTarget)")
    }

    Write-Dbg "STAGE 3: concurrency check skipped (Iteration 1 / -Force=$([bool]$Force))"

    Write-Dbg "STAGE 4: build writer + nodesStack"
    $effFormat = $Format
    if ([int]$pack.Scope -le 0) { $effFormat = 'XML' }

    if (-not (Get-Command Get-SRxCustomizationWriter -ErrorAction SilentlyContinue)) { throw "Get-SRxCustomizationWriter not available (SRxCore not loaded)." }
    

    if ($effFormat -eq 'JSON') {
        if (-not (Get-Command New-SRxJsonContainerTerm -ErrorAction SilentlyContinue)) { throw "New-SRxJsonContainerTerm not available (SRxCustomizationJson not loaded)." }
        if (-not (Get-Command Write-SRxJsonCustomization -ErrorAction SilentlyContinue)) { throw "Write-SRxJsonCustomization not available (dot-source it in STAGE 1)." }

        $container = New-SRxJsonContainerTerm -Pack $pack -Operation $operation

        $conn = Get-SRxConnection -siteUrl $global:SRxEnv.Tenancy.AdminUrl

        $props = @{}
        foreach ($k in $container.Attributes.Keys) { $props[[string]$k] = [string]$container.Attributes[$k] }

        $jw = Write-SRxJsonCustomization -Connection $conn `
                -Scope       ([int]$pack.Scope) `
                -Optype      ([int]$pack.Optype) `
                -Environment ([string]$pack.Environment) `
                -Design      ([string]$pack.Design) `
                -TermID      ([string]$pack.TermID) `
                -ContainerName ([string]$container.Name) `
                -Operation   ([string]$operation) `
                -Props       $props
        if (-not $jw.Success) { throw "Direct JSON write failed: $($jw.Error)" }
    }
    else {
        $writer = Get-SRxCustomizationWriter
        $nodesStack = ConvertTo-SRxNodesStack -Branch $pack.Branch
        $writerPack = [pscustomobject]@{
            nodesStack     = ,$nodesStack
            ts_Environment = [string]$pack.Environment
            ts_Design      = [string]$pack.Design
            ts_TermID      = [string]$pack.TermID
        }
        $writer.Start($writerPack)
    }
    Write-Dbg "STAGE 4 done: branch written to termstore"

    Write-Dbg "STAGE 5: local sync (termSet.xml + JSON)"
    $syncData = $null; $syncWarning = $null
    try {
        if (Get-Command Sync-SRxLocalFilterBranch -ErrorAction SilentlyContinue) {
            if ($effFormat -eq 'JSON') {
                $sync = Sync-SRxLocalJsonCustomization -Pack $pack -Operation $operation
            } else {
                $sync = Sync-SRxLocalFilterBranch -Pack $pack
            }
            if ($sync.Success) { $syncData = $sync; Write-Dbg " local sync OK" }
            else { $syncWarning = "Local sync failed: $($sync.Error)"; Write-Dbg (" " + $syncWarning) 'WARN' }
        } else { $syncWarning = "Sync-SRxLocalFilterBranch not available; local cache not updated."; Write-Dbg (" " + $syncWarning) 'WARN' }
    } catch { $syncWarning = "Local sync threw: $($_.Exception.Message)"; Write-DbgException -ErrorRecord $_ -Where "(local sync)" }

    $data = [pscustomobject]@{
        Operation   = $operation
        Scope       = $scope
        FilterNodes = if ($syncData) { $syncData.FilterNodes } else { $null }
        OuterXml    = if ($syncData) { $syncData.OuterXml }    else { $null }
        SyncWarning = $syncWarning
    }
    Write-SRxFilterResult -Success $true -Conflict $false -Data $data -ErrorText $null
    Write-Dbg "########## FILTER WORKER SUCCESS ##########"
    try { Stop-Transcript | Out-Null } catch {}
    exit 0
}
catch {
    Write-DbgException -ErrorRecord $_ -Where "(main try)"
    $msg = $_.Exception.Message
    if ($_.Exception.InnerException) { $msg += " | inner: " + $_.Exception.InnerException.Message }
    $msg2 = $msg + " [log: " + ($script:DbgLogPaths | Select-Object -First 1) + "]"
    Write-SRxFilterResult -Success $false -Conflict $false -Data $null -ErrorText $msg2
    Write-Dbg "########## FILTER WORKER FAILED ##########"
    try { Stop-Transcript | Out-Null } catch {}
    exit 1
}