[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
param(
    [Parameter(Mandatory = $true)]
    [string]$Path,

    [switch]$Recurse
)

<#
用法示例

1. 批量修复当前目录下所有 XML 路径文件
powershell -ExecutionPolicy Bypass -File "C:\Users\Tellme\apps\NavisworksTransport-rail-mount-modes\scripts\Fix-RailPreferredNormals.ps1" -Path "C:\Users\Tellme\Documents\NavisworksTransport\模型"

2. 递归修复目录及其子目录中的所有 XML 路径文件
powershell -ExecutionPolicy Bypass -File "C:\Users\Tellme\apps\NavisworksTransport-rail-mount-modes\scripts\Fix-RailPreferredNormals.ps1" -Path "C:\Users\Tellme\Documents\NavisworksTransport\模型" -Recurse

3. 只修复单个 XML 路径文件
powershell -ExecutionPolicy Bypass -File "C:\Users\Tellme\apps\NavisworksTransport-rail-mount-modes\scripts\Fix-RailPreferredNormals.ps1" -Path "C:\Users\Tellme\Documents\NavisworksTransport\模型\副本_人工_0331_133243.xml"

脚本规则

- 只处理 pathType="Rail" 且 railMountMode="OverRail" 的路径
- 只修复 railPreferredNormalY < 0 的路径
- 修复方式为将 railPreferredNormalX / Y / Z 三个分量一起取反
- 原文件会被直接覆盖，同时自动保留一个同名 .bak 备份文件
#>

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Format-NormalValue {
    param(
        [Parameter(Mandatory = $true)]
        [double]$Value
    )

    return $Value.ToString("0.###", [System.Globalization.CultureInfo]::InvariantCulture)
}

function Test-ParseableDoubleText {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Text,

        [ref]$Value
    )

    return [double]::TryParse(
        $Text,
        [System.Globalization.NumberStyles]::Float -bor [System.Globalization.NumberStyles]::AllowLeadingSign,
        [System.Globalization.CultureInfo]::InvariantCulture,
        $Value)
}

function Update-RouteNode {
    param(
        [Parameter(Mandatory = $true)]
        [System.Xml.XmlElement]$RouteNode,

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

    if ($RouteNode.GetAttribute("pathType") -ne "Rail") {
        return $false
    }

    if ($RouteNode.GetAttribute("railMountMode") -ne "OverRail") {
        return $false
    }

    $xText = $RouteNode.GetAttribute("railPreferredNormalX")
    $yText = $RouteNode.GetAttribute("railPreferredNormalY")
    $zText = $RouteNode.GetAttribute("railPreferredNormalZ")

    if ([string]::IsNullOrWhiteSpace($xText) -or
        [string]::IsNullOrWhiteSpace($yText) -or
        [string]::IsNullOrWhiteSpace($zText)) {
        return $false
    }

    $x = 0.0
    $y = 0.0
    $z = 0.0
    if (-not (Test-ParseableDoubleText -Text $xText -Value ([ref]$x)) -or
        -not (Test-ParseableDoubleText -Text $yText -Value ([ref]$y)) -or
        -not (Test-ParseableDoubleText -Text $zText -Value ([ref]$z))) {
        throw "文件 '$SourcePath' 中存在无法解析的 railPreferredNormal 值。"
    }

    if ($y -ge 0) {
        return $false
    }

    $RouteNode.SetAttribute("railPreferredNormalX", (Format-NormalValue -Value (-$x)))
    $RouteNode.SetAttribute("railPreferredNormalY", (Format-NormalValue -Value (-$y)))
    $RouteNode.SetAttribute("railPreferredNormalZ", (Format-NormalValue -Value (-$z)))
    return $true
}

function Save-XmlDocument {
    param(
        [Parameter(Mandatory = $true)]
        [xml]$Document,

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

    $settings = New-Object System.Xml.XmlWriterSettings
    $settings.Indent = $true
    $settings.IndentChars = "  "
    $settings.Encoding = New-Object System.Text.UTF8Encoding($false)
    $settings.NewLineChars = "`r`n"
    $settings.NewLineHandling = [System.Xml.NewLineHandling]::Replace

    $writer = [System.Xml.XmlWriter]::Create($TargetPath, $settings)
    try {
        $Document.Save($writer)
    }
    finally {
        $writer.Dispose()
    }
}

function Update-XmlFile {
    param(
        [Parameter(Mandatory = $true)]
        [string]$FilePath
    )

    [xml]$xml = Get-Content -LiteralPath $FilePath -Raw
    $routeNodes = @($xml.SelectNodes("//*[local-name()='Route']"))
    if ($null -eq $routeNodes -or $routeNodes.Count -eq 0) {
        return [pscustomobject]@{
            FilePath = $FilePath
            Modified = $false
            RouteCount = 0
        }
    }

    $modifiedRouteCount = 0
    foreach ($routeNode in $routeNodes) {
        if (Update-RouteNode -RouteNode $routeNode -SourcePath $FilePath) {
            $modifiedRouteCount++
        }
    }

    if ($modifiedRouteCount -gt 0) {
        $backupPath = "$FilePath.bak"
        if ($PSCmdlet.ShouldProcess($FilePath, "修复 railPreferredNormal 并写回 XML")) {
            if (-not (Test-Path -LiteralPath $backupPath)) {
                Copy-Item -LiteralPath $FilePath -Destination $backupPath
            }

            Save-XmlDocument -Document $xml -TargetPath $FilePath
        }
    }

    return [pscustomobject]@{
        FilePath = $FilePath
        Modified = ($modifiedRouteCount -gt 0)
        RouteCount = $modifiedRouteCount
    }
}

$resolvedPath = Resolve-Path -LiteralPath $Path
$item = Get-Item -LiteralPath $resolvedPath

$files = @()
if ($item.PSIsContainer) {
    $childParams = @{
        LiteralPath = $item.FullName
        File = $true
    }
    if ($Recurse) {
        $childParams["Recurse"] = $true
    }

    $files = @(Get-ChildItem @childParams | Where-Object { $_.Extension -ieq ".xml" })
}
else {
    $files = @($item)
}

if ($files.Count -eq 0) {
    Write-Host "未找到可处理的 XML 文件。"
    exit 0
}

$results = @()
foreach ($file in $files) {
    $results += Update-XmlFile -FilePath $file.FullName
}

$results = @($results)
$modifiedFiles = @($results | Where-Object { $_.Modified })
$modifiedRoutes = 0
if ($modifiedFiles.Count -gt 0) {
    $measure = $modifiedFiles | Measure-Object -Property RouteCount -Sum
    if ($null -ne $measure -and $null -ne $measure.Sum) {
        $modifiedRoutes = [int]$measure.Sum
    }
}

Write-Host ("扫描文件: {0}" -f $results.Count)
Write-Host ("修改文件: {0}" -f $modifiedFiles.Count)
Write-Host ("修复路径: {0}" -f $modifiedRoutes)

foreach ($result in $modifiedFiles) {
    Write-Host ("已修复: {0} (路径数={1})" -f $result.FilePath, $result.RouteCount)
}
