Private/Migration/Format-CgrMigrationPlan.ps1

function Format-CgrMigrationPlan {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [psobject] $Plan,

        [Parameter(Mandatory)]
        [ValidateSet('Markdown', 'Json')]
        [string] $Format
    )

    process {
        if ($Format -eq 'Json') {
            return $Plan | ConvertTo-Json -Depth 20
        }

        $lines = [System.Collections.Generic.List[string]]::new()
        $lines.Add('# GitHub Repository Copy Plan')
        $lines.Add('')
        $lines.Add('| Field | Value |')
        $lines.Add('| --- | --- |')
        $lines.Add("| Source | $($Plan.SourceRepository) |")
        $lines.Add("| Destination | $($Plan.DestinationRepository) |")
        $lines.Add("| Archive | $(if ($Plan.ArchiveRepository) { $Plan.ArchiveRepository } else { 'N/A' }) |")
        $lines.Add("| Mode | $($Plan.Mode) |")
        $lines.Add("| Content mode | $($Plan.ContentMode) |")
        $lines.Add("| Source visibility | $($Plan.SourceVisibility) |")
        $lines.Add("| Destination visibility | $($Plan.DestinationVisibility) |")
        $lines.Add("| Restore Pages | $($Plan.RestorePages) |")
        $lines.Add("| Enable Actions after copy | $($Plan.EnableActionsAfterMigration) |")
        $lines.Add("| Skip settings | $($Plan.SkipSettings) |")
        $lines.Add("| Plan only | $($Plan.PlanOnly) |")

        $sourceState = Get-CgrObjectProperty -InputObject $Plan -Name 'SourceState'
        if ($sourceState) {
            $lines.Add('')
            $lines.Add('## Approved Source State')
            $lines.Add('')
            $lines.Add('This immutable source fingerprint is the state approved by this plan. Execution fails closed if the source no longer matches it before mutation.')
            $lines.Add('')
            $lines.Add('| Field | Value |')
            $lines.Add('| --- | --- |')
            $lines.Add("| Repository | $(Get-CgrObjectProperty -InputObject $sourceState -Name 'Repository') |")
            $lines.Add("| Repository ID | $(Get-CgrObjectProperty -InputObject $sourceState -Name 'RepositoryId') |")
            $lines.Add("| Repository node ID | $(Get-CgrObjectProperty -InputObject $sourceState -Name 'RepositoryNodeId') |")
            $lines.Add("| Default branch | $(Get-CgrObjectProperty -InputObject $sourceState -Name 'DefaultBranch') |")
            $capturedAtUtc = Get-CgrObjectProperty -InputObject $sourceState -Name 'CapturedAtUtc'
            if ($capturedAtUtc) { $lines.Add("| Captured at UTC | $capturedAtUtc |") }

            if ($Plan.ContentMode -eq 'Snapshot') {
                $lines.Add("| Approved commit SHA | $(Get-CgrObjectProperty -InputObject $sourceState -Name 'CommitSha') |")
                $lines.Add("| Approved tree SHA | $(Get-CgrObjectProperty -InputObject $sourceState -Name 'TreeSha') |")
                $pointerFiles = @(Get-CgrObjectProperty -InputObject $sourceState -Name 'GitLfsPointerFiles')
                $lines.Add("| Git LFS pointer files | $($pointerFiles.Count) |")
                $lines.Add("| Required Git LFS objects available | $(Get-CgrObjectProperty -InputObject $sourceState -Name 'GitLfsObjectsAvailable') |")

                $historicalRecords = Get-CgrObjectProperty -InputObject $sourceState -Name 'HistoricalRecords'
                if ($historicalRecords) {
                    $tagCount = Get-CgrObjectProperty -InputObject $historicalRecords -Name 'TagCount'
                    $releaseCount = Get-CgrObjectProperty -InputObject $historicalRecords -Name 'ReleaseCount'
                    $tagCountSuffix = if (Get-CgrObjectProperty -InputObject $historicalRecords -Name 'TagCountMayBeTruncated') { '+' } else { '' }
                    $releaseCountSuffix = if (Get-CgrObjectProperty -InputObject $historicalRecords -Name 'ReleaseCountMayBeTruncated') { '+' } else { '' }
                    $lines.Add('')
                    $lines.Add('## Historical Tags and Releases')
                    $lines.Add('')
                    $lines.Add('| Record | Source | Snapshot behavior |')
                    $lines.Add('| --- | ---: | --- |')
                    $tagBehavior = if ($Plan.Mode -eq 'SameNameReplacement') { 'Not copied; retained by the archived original repository.' } else { 'Not copied to the clean destination.' }
                    $releaseBehavior = if ($Plan.Mode -eq 'SameNameReplacement') { 'Not copied; retained by the archived original repository.' } else { 'Not copied to the clean destination.' }
                    $lines.Add("| Git tags | $tagCount$tagCountSuffix | $tagBehavior |")
                    $lines.Add("| GitHub Releases | $releaseCount$releaseCountSuffix | $releaseBehavior |")

                    $tagNames = @(Get-CgrObjectProperty -InputObject $historicalRecords -Name 'TagNames')
                    if ($tagNames.Count -gt 0) {
                        $lines.Add('')
                        $lines.Add("Observed source tags: $($tagNames -join ', ')")
                    }

                    $versionLikeTags = @(Get-CgrObjectProperty -InputObject $historicalRecords -Name 'VersionLikeTagNames')
                    if ($versionLikeTags.Count -gt 0) {
                        $lines.Add('')
                        $lines.Add("Release-like source tags detected: $($versionLikeTags -join ', ')")
                    }

                    if ($Plan.Mode -eq 'SameNameReplacement' -and ($tagCount -gt 0 -or $releaseCount -gt 0)) {
                        $lines.Add('')
                        $lines.Add('**Release safety:** create the new release tag and GitHub Release on the clean replacement only after Snapshot publication and verification complete successfully.')
                    }
                }
            }
            else {
                $refs = @(Get-CgrObjectProperty -InputObject $sourceState -Name 'Refs')
                $branchTrees = @(Get-CgrObjectProperty -InputObject $sourceState -Name 'BranchTrees')
                $lines.Add("| Approved branch/tag refs | $($refs.Count) |")
                $lines.Add("| Reachable commit count | $(Get-CgrObjectProperty -InputObject $sourceState -Name 'ReachableCommitCount') |")
                $lines.Add("| Branch-tip trees | $($branchTrees.Count) |")
                $lines.Add("| Reachable Git LFS objects available | $(Get-CgrObjectProperty -InputObject $sourceState -Name 'GitLfsObjectsAvailable') |")
            }
        }

        $lines.Add('')
        $lines.Add('## Steps')
        $lines.Add('')
        $lines.Add('| Order | Step | Mutates GitHub | Description |')
        $lines.Add('| ---: | --- | --- | --- |')

        foreach ($step in @($Plan.Steps)) {
            $description = ([string] $step.Description).Replace('|', '\|')
            $lines.Add("| $($step.Order) | $($step.Name) | $($step.MutatesGitHub) | $description |")
        }

        $lines.Add('')
        $lines.Add('This report is a dry-run repository copy plan. No GitHub repository was created, renamed, overwritten, or deleted.')

        return $lines -join [Environment]::NewLine
    }
}