param(
    [string]$TemplatePath = "C:\Users\niuniu\Desktop\Report.pptx",
    [string]$OutputPath = "C:\workspace\cursor\output\vip-report\generated-from-template.pptx",
    [string]$OperationsPath = ""
)

$ErrorActionPreference = "Stop"

# 关键行注释：这里是关键处理节点。
# 函数说明：封装Get-ShapeChildren方法的核心处理流程。
function Get-ShapeChildren {
    param([Parameter(Mandatory = $true)]$Shape)
    $children = @()
    # 关键行注释：进入核心执行区，并在 finally 中释放 PowerPoint 资源。
    try {
        $count = $Shape.GroupItems.Count
        for ($i = 1; $i -le $count; $i++) {
            $child = $Shape.GroupItems.Item($i)
            $children += $child
            $children += Get-ShapeChildren -Shape $child
        }
    } catch {
    }
    return $children
}

# 关键行注释：这里是关键处理节点。
# 函数说明：封装Find-Shape方法的核心处理流程。
function Find-Shape {
    param(
        [Parameter(Mandatory = $true)]$Slide,
        [int]$ShapeId,
        [string]$ShapeName,
        [string]$ExactText,
        [string]$ContainsText
    )

    $candidates = @()
    foreach ($shape in $Slide.Shapes) {
        $candidates += $shape
        $candidates += Get-ShapeChildren -Shape $shape
    }

    foreach ($shape in $candidates) {
        if ($ShapeId -and $shape.Id -eq $ShapeId) {
            return $shape
        }

        if ($ShapeName -and $shape.Name -eq $ShapeName) {
            return $shape
        }

        $text = $null
        # 关键行注释：进入核心执行区，并在 finally 中释放 PowerPoint 资源。
        try {
            if ($shape.HasTextFrame -eq -1 -and $shape.TextFrame.HasText -eq -1) {
                $text = $shape.TextFrame.TextRange.Text
            }
        } catch {
            $text = $null
        }

        if ($ExactText -and $text -eq $ExactText) {
            return $shape
        }

        if ($ContainsText -and $text -and $text.Contains($ContainsText)) {
            return $shape
        }
    }

    return $null
}

# 关键行注释：这里是关键处理节点。
# 函数说明：封装Set-ShapeText方法的核心处理流程。
function Set-ShapeText {
    param(
        [Parameter(Mandatory = $true)]$Shape,
        [Parameter(Mandatory = $true)][string]$NewText
    )

    if ($Shape.HasTextFrame -ne -1) {
        throw "Shape '$($Shape.Name)' does not support text."
    }

    $Shape.TextFrame.TextRange.Text = $NewText
}

# 关键行注释：这里是关键处理节点。
# 函数说明：封装Replace-PictureShape方法的核心处理流程。
function Replace-PictureShape {
    param(
        [Parameter(Mandatory = $true)]$Slide,
        [Parameter(Mandatory = $true)]$Shape,
        [Parameter(Mandatory = $true)][string]$ImagePath
    )

    if (-not (Test-Path -LiteralPath $ImagePath)) {
        throw "ImagePath not found: $ImagePath"
    }

    $left = $Shape.Left
    $top = $Shape.Top
    $width = $Shape.Width
    $height = $Shape.Height
    $z = $Shape.ZOrderPosition
    $name = $Shape.Name

    $Shape.Delete()
    $newShape = $Slide.Shapes.AddPicture($ImagePath, $false, $true, $left, $top, $width, $height)
    while ($newShape.ZOrderPosition -lt $z) {
        $newShape.ZOrder(1) | Out-Null
    }
    # 关键行注释：进入核心执行区，并在 finally 中释放 PowerPoint 资源。
    try {
        $newShape.Name = $name
    } catch {
    }
}

if (-not (Test-Path -LiteralPath $TemplatePath)) {
    throw "TemplatePath not found: $TemplatePath"
}

$ops = [pscustomobject]@{
    replace_text = @()
    replace_tables = @()
    replace_charts = @()
    replace_images = @()
}

if ($OperationsPath) {
    if (-not (Test-Path -LiteralPath $OperationsPath)) {
        throw "OperationsPath not found: $OperationsPath"
    }
    $ops = Get-Content -LiteralPath $OperationsPath -Encoding UTF8 | ConvertFrom-Json
}

$replaceText = @()
$replaceTables = @()
$replaceCharts = @()
$replaceImages = @()
if ($ops.PSObject.Properties.Name -contains "replace_text") {
    $replaceText = @($ops.replace_text)
}
if ($ops.PSObject.Properties.Name -contains "replace_tables") {
    $replaceTables = @($ops.replace_tables)
}
if ($ops.PSObject.Properties.Name -contains "replace_charts") {
    $replaceCharts = @($ops.replace_charts)
}
if ($ops.PSObject.Properties.Name -contains "replace_images") {
    $replaceImages = @($ops.replace_images)
}

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

Copy-Item -LiteralPath $TemplatePath -Destination $OutputPath -Force

if ($replaceText.Count -eq 0 -and $replaceTables.Count -eq 0 -and $replaceCharts.Count -eq 0 -and $replaceImages.Count -eq 0) {
    # 无替换操作时直接复制模板，避免 PowerPoint 重新保存造成包结构差异。
    Write-Output $OutputPath
    return
}

$ppt = $null
$pres = $null

# 关键行注释：进入核心执行区，并在 finally 中释放 PowerPoint 资源。
try {
    $ppt = New-Object -ComObject PowerPoint.Application
    $pres = $ppt.Presentations.Open($OutputPath, 0, 0, 0)

    foreach ($item in $replaceText) {
        $slide = $pres.Slides.Item([int]$item.slide)
        $shape = Find-Shape -Slide $slide -ShapeId $item.shape_id -ShapeName $item.shape_name -ExactText $item.exact_text -ContainsText $item.contains_text
        if ($null -eq $shape) {
            throw "Text target not found on slide $($item.slide)"
        }
        Set-ShapeText -Shape $shape -NewText ([string]$item.new_text)
    }

    foreach ($item in $replaceImages) {
        $slide = $pres.Slides.Item([int]$item.slide)
        $shape = Find-Shape -Slide $slide -ShapeId $item.shape_id -ShapeName $item.shape_name -ExactText $item.exact_text -ContainsText $item.contains_text
        if ($null -eq $shape) {
            throw "Image target not found on slide $($item.slide)"
        }
        Replace-PictureShape -Slide $slide -Shape $shape -ImagePath ([string]$item.image_path)
    }

    foreach ($item in $replaceTables) {
        $slide = $pres.Slides.Item([int]$item.slide)
        $shape = Find-Shape -Slide $slide -ShapeId $item.shape_id -ShapeName $item.shape_name -ExactText $item.exact_text -ContainsText $item.contains_text
        if ($null -eq $shape) {
            throw "Table target not found on slide $($item.slide)"
        }
        if ($shape.HasTable -ne -1) {
            throw "Shape '$($shape.Name)' is not a table."
        }

        foreach ($cell in @($item.cells)) {
            $row = [int]$cell.row
            $col = [int]$cell.col
            $newText = [string]$cell.new_text
            $shape.Table.Cell($row, $col).Shape.TextFrame.TextRange.Text = $newText
        }
    }

    foreach ($item in $replaceCharts) {
        $slide = $pres.Slides.Item([int]$item.slide)
        $shape = Find-Shape -Slide $slide -ShapeId $item.shape_id -ShapeName $item.shape_name -ExactText $item.exact_text -ContainsText $item.contains_text
        if ($null -eq $shape) {
            throw "Chart target not found on slide $($item.slide)"
        }
        if ($shape.HasChart -ne -1) {
            throw "Shape '$($shape.Name)' is not a chart."
        }

        $chart = $shape.Chart
        foreach ($series in @($item.series)) {
            $index = [int]$series.index
            $chartSeries = $chart.SeriesCollection($index)

            if ($series.PSObject.Properties.Name -contains "name") {
                try { $chartSeries.Name = [string]$series.name } catch {}
            }
            if ($series.PSObject.Properties.Name -contains "x_values") {
                try { $chartSeries.XValues = @($series.x_values) } catch {}
            }
            if ($series.PSObject.Properties.Name -contains "values") {
                try { $chartSeries.Values = @($series.values) } catch {}
            }
        }
        try { $chart.Refresh() | Out-Null } catch {}
    }

    $pres.Save()
    Write-Output $OutputPath
} finally {
    if ($pres -ne $null) {
        try { $pres.Close() | Out-Null } catch {}
    }
    if ($ppt -ne $null) {
        try { $ppt.Quit() | Out-Null } catch {}
    }
}
