Private/Replacement/Write-CgrSameNameRecoveryReport.ps1

function Write-CgrSameNameRecoveryReport {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [psobject] $Plan,

        [Nullable[long]] $SourceRepositoryId,

        [psobject] $ArchiveRepository,

        [psobject] $DestinationRepository,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $FailureStage,

        [Parameter(Mandatory)]
        [System.Management.Automation.ErrorRecord] $ErrorRecord,

        [object[]] $CompletedSteps = @(),

        [string] $SourceCommitSha,

        [string] $SourceTreeSha,

        [string] $DestinationRootCommitSha,

        [string] $DestinationTreeSha,

        [Nullable[int]] $SourceFullHistoryRefCount,

        [Nullable[int]] $SourceReachableCommitCount,

        [string] $PreferredReportPath
    )

    $reportPath = if (-not [string]::IsNullOrWhiteSpace($PreferredReportPath)) {
        "$PreferredReportPath.recovery.json"
    }
    else {
        $safeDestinationName = $Plan.DestinationRepository -replace '[^A-Za-z0-9_.-]', '-'
        $timestamp = [DateTimeOffset]::UtcNow.ToString('yyyyMMddTHHmmssfffZ')
        Join-Path (Join-Path (Get-Location) '.copy-github-repo/recovery') "$safeDestinationName-$timestamp.json"
    }

    $resolvedParent = Split-Path -Parent $reportPath
    if (-not [string]::IsNullOrWhiteSpace($resolvedParent)) {
        New-Item -Path $resolvedParent -ItemType Directory -Force | Out-Null
    }

    $sourceWasArchived = $null -ne $ArchiveRepository
    $destinationWasCreated = $null -ne $DestinationRepository
    $archiveRepositoryId = if ($ArchiveRepository) { Get-CgrObjectProperty -InputObject $ArchiveRepository -Name 'Id' } else { $null }
    $destinationRepositoryId = if ($DestinationRepository) { Get-CgrObjectProperty -InputObject $DestinationRepository -Name 'Id' } else { $null }
    $status = if ($destinationWasCreated) {
        'FailedAfterReplacementCreation'
    }
    elseif ($sourceWasArchived) {
        'FailedAfterSourceArchive'
    }
    else {
        'FailedDuringSameNamePreparation'
    }

    $provenance = if ($Plan.ContentMode -eq 'Snapshot') {
        [pscustomobject] @{
            ContentMode = 'Snapshot'
            SourceRepository = $Plan.SourceRepository
            SourceRepositoryId = $SourceRepositoryId
            SourceCommitSha = $SourceCommitSha
            SourceTreeSha = $SourceTreeSha
            ArchiveRepository = if ($ArchiveRepository) { $ArchiveRepository.FullName } else { $Plan.ArchiveRepository }
            ArchiveRepositoryId = $archiveRepositoryId
            DestinationRepository = $Plan.DestinationRepository
            DestinationRepositoryId = $destinationRepositoryId
            DestinationRootCommitSha = $DestinationRootCommitSha
            DestinationTreeSha = $DestinationTreeSha
        }
    }
    else {
        $null
    }

    $recoveryResult = [pscustomobject] @{
        PSTypeName = 'CopyGitHubRepo.SameNameRecoveryResult'
        SchemaVersion = 1
        Status = $status
        RecordedAtUtc = [DateTimeOffset]::UtcNow.ToString('o')
        SourceRepository = $Plan.SourceRepository
        SourceRepositoryId = $SourceRepositoryId
        ArchiveRepository = if ($ArchiveRepository) { $ArchiveRepository.FullName } else { $Plan.ArchiveRepository }
        ArchiveRepositoryId = if ($null -eq $archiveRepositoryId) { $null } else { [long] $archiveRepositoryId }
        ArchiveHtmlUrl = if ($ArchiveRepository) { $ArchiveRepository.HtmlUrl } else { $null }
        DestinationRepository = $Plan.DestinationRepository
        DestinationRepositoryId = if ($null -eq $destinationRepositoryId) { $null } else { [long] $destinationRepositoryId }
        DestinationHtmlUrl = if ($DestinationRepository) { $DestinationRepository.HtmlUrl } else { $null }
        SourceVisibility = $Plan.SourceVisibility
        DestinationVisibility = $Plan.DestinationVisibility
        ContentMode = $Plan.ContentMode
        BranchName = $Plan.SourceDefaultBranch
        SourceCommitSha = $SourceCommitSha
        SourceTreeSha = $SourceTreeSha
        DestinationRootCommitSha = $DestinationRootCommitSha
        DestinationTreeSha = $DestinationTreeSha
        SourceFullHistoryRefCount = $SourceFullHistoryRefCount
        SourceReachableCommitCount = $SourceReachableCommitCount
        Provenance = $provenance
        FailureStage = $FailureStage
        ErrorId = $ErrorRecord.FullyQualifiedErrorId
        ErrorMessage = $ErrorRecord.Exception.Message
        CompletedSteps = @($CompletedSteps)
        Recovery = [pscustomobject] @{
            SourceRepositoryMutated = $sourceWasArchived
            SourceWasArchived = $sourceWasArchived
            DestinationWasCreated = $destinationWasCreated
            AutomaticRollbackAttempted = $false
            AutomaticDeletionAttempted = $false
            RecommendedActions = @(
                'Do not delete or rename the archive until the failure is understood.'
                'Inspect the archive and replacement repositories named in this report before taking corrective action.'
                'If the replacement exists, run Test-GitHubRepositoryMigration with the recorded content mode before deciding whether another migration attempt is necessary.'
                'Do not rely on the original repository URL to reach the archive after the replacement name is reused.'
                'Perform any rollback, rename, or deletion manually only after verifying the current repository state.'
            )
        }
    }

    $recoveryResult |
        ConvertTo-Json -Depth 30 |
        Set-Content -Path $reportPath -Encoding utf8NoBOM

    return [System.IO.Path]::GetFullPath($reportPath)
}