Functions/GenXdev.Coding/Publish-ReleaseNotesToManifest.ps1

###############################################################################
<#
.SYNOPSIS
Publishes uncompleted release notes from README.md to the module manifest.

.DESCRIPTION
Reads the "## Release notes" section from a README.md file, collects all
uncompleted release notes (lines starting with - ☐), writes them into the
ReleaseNotes field of GenXdev.psd1, and marks them as completed (☐ → ☒)
in the README. Both files are updated atomically in one operation.

.LICENSE
Copyright (C) 2026 René Vaessen / GenXdev

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

.PARAMETER UseHomeREADME
Use README.md from PowerShell profile directory instead of current location.

.PARAMETER UseOneDriveREADME
Use README.md from OneDrive directory instead of current location.

.PARAMETER ManifestPath
Path to the .psd1 module manifest to update. If not specified, auto-detects
the GenXdev.psd1 in the module directory.

.EXAMPLE
Publish-ReleaseNotesToManifest

.EXAMPLE
Publish-ReleaseNotesToManifest -ManifestPath ".\MyModule.psd1"
#>

function Publish-ReleaseNotesToManifest {

    [CmdletBinding(SupportsShouldProcess = $true)]
    [Alias('pubrelnotes')]
    param(
        #######################################################################
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Path to the .psd1 module manifest'
        )]
        [string] $ManifestPath,
        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Use README in PowerShell profile directory'
        )]
        [switch] $UseHomeREADME,

        #######################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Use README in OneDrive directory'
        )]
        [switch] $UseOneDriveREADME
    )

    begin {
        Microsoft.PowerShell.Utility\Write-Verbose `
            'Starting Publish-ReleaseNotesToManifest'

        # Resolve README path (same logic as Add-LineToREADME begin block)
        if ($UseHomeREADME) {
            $readmePath = GenXdev\Expand-Path `
                -FilePath "$([IO.Path]::GetDirectoryName($Profile))\README.md"
        }
        elseif ($UseOneDriveREADME) {
            $readmePath = GenXdev\Expand-Path `
                -FilePath "$(GenXdev\Get-KnownFolderPath -KnownFolder 'OneDrive')\README.md" `
                -CreateDirectory
        }
        else {
            # Walk up from current directory to find README
            $startDir = GenXdev\Expand-Path $PWD
            $readmePath = $null
            $currentDir = $startDir
            while ($true) {
                $candidate = "$currentDir\README.md"
                if ([System.IO.File]::Exists($candidate)) {
                    $readmePath = $candidate
                    break
                }
                $parent = GenXdev\Expand-Path "$currentDir\..\"
                if ($parent -eq $currentDir) { break }
                $currentDir = $parent
            }
            if (-not $readmePath) {
                $readmePath = GenXdev\Expand-Path `
                    -FilePath "$(GenXdev\Get-KnownFolderPath -KnownFolder 'OneDrive')\README.md" `
                    -CreateDirectory
            }
        }

        Microsoft.PowerShell.Utility\Write-Verbose `
            "Using README at: ${readmePath}"

        if (![IO.File]::Exists($readmePath)) {
            throw "README file not found at: ${readmePath}"
        }

        if ([String]::IsNullOrWhiteSpace($ManifestPath)) {

            throw "Specify a valid ManifestPath -ManifestPath."
        }

        $ManifestPath = GenXdev\Expand-Path $ManifestPath;

        if (-not [IO.File]::Exists($ManifestPath)) {

            throw "Module manifest not found. Specify -ManifestPath."
        }

        Microsoft.PowerShell.Utility\Write-Verbose `
            "Using manifest at: ${ManifestPath}"
    }

    process {
        # Read README content
        $readmeContent = [IO.File]::ReadAllText($readmePath)
        $lineEnding = if ($readmeContent.IndexOf("`r`n") -ge 0) { "`r`n" } `
            else { "`n" }

        $section = '## Release notes'

        # Find the section
        $sectionStart = $readmeContent.IndexOf(
            $section,
            [System.StringComparison]::OrdinalIgnoreCase
        )
        if ($sectionStart -lt 0) {
            Microsoft.PowerShell.Utility\Write-Warning `
                "Section '${section}' not found in ${readmePath}"
            return
        }

        $sectionContentStart = $sectionStart + $section.Length +
            $lineEnding.Length
        $sectionLevel = ($section -replace '^([#]+).*$', '$1').Length
        $rest = $readmeContent.Substring($sectionContentStart)
        $nextHeading = [regex]::Match($rest, "(?m)^#{1,$sectionLevel}\s")
        $sectionEnd = if ($nextHeading.Success) {
            $sectionContentStart + $nextHeading.Index
        }
        else { $readmeContent.Length }

        $sectionContent = $readmeContent.Substring(
            $sectionContentStart,
            $sectionEnd - $sectionContentStart
        )
        $sectionLines = $sectionContent -split $lineEnding

        # Collect uncompleted release notes (lines with ☐)
        $uncompletedLines = [System.Collections.Generic.List[string]]::new()
        $uncompletedRaw = [System.Collections.Generic.List[string]]::new()
        $lineIndices = [System.Collections.Generic.List[int]]::new()

        for ($i = 0; $i -lt $sectionLines.Count; $i++) {
            $ln = $sectionLines[$i]
            if ($ln -match '^- ☐ \[P') {
                # Strip prefix and date: "- ☐ [P{N}] yyyyMMdd --> " → raw text
                $rawText = $ln -replace '^- ☐ \[P-?\d+\] \d{8}\s*-->\s*', ''
                $null = $uncompletedLines.Add($ln)
                $null = $uncompletedRaw.Add($rawText)
                $null = $lineIndices.Add($i)
            }
        }

        if ($uncompletedRaw.Count -eq 0) {
            Microsoft.PowerShell.Utility\Write-Host `
                'No uncompleted release notes found.'
            return
        }

        # Build release notes text for the manifest
        $releaseNotesText = ($uncompletedRaw -join "`n")

        # Mark lines as done in README
        foreach ($idx in $lineIndices) {
            $sectionLines[$idx] = $sectionLines[$idx].Replace('☐', '☒')
        }
        $newSectionContent = $sectionLines -join $lineEnding
        $newReadmeContent = $readmeContent.Substring(0, $sectionContentStart) +
            $newSectionContent +
            $readmeContent.Substring($sectionEnd)

        # Write both files atomically
        if ($PSCmdlet.ShouldProcess(
                "README and ${ManifestPath}",
                'Publish release notes to manifest'
            )) {
            # Save backup copies first
            $readmeBackup = $readmeContent
            $manifestBackup = [IO.File]::ReadAllText($ManifestPath)

            try {
                GenXdev\Write-TextFileAtomic `
                    -FilePath $readmePath `
                    -Data $newReadmeContent

                PowerShellGet\Update-ModuleManifest -Path $ManifestPath -ReleaseNotes $releaseNotesText -Confirm:$false

                Microsoft.PowerShell.Utility\Write-Host `
                    "Published $($uncompletedRaw.Count) release note(s) to ${ManifestPath}"
            }
            catch {
                # Rollback on failure
                Microsoft.PowerShell.Utility\Write-Warning `
                    "Write failed, rolling back: $_"
                try {
                    GenXdev\Write-TextFileAtomic `
                        -FilePath $readmePath `
                        -Data $readmeBackup
                }
                catch {
                    Microsoft.PowerShell.Utility\Write-Warning `
                        "Rollback also failed: $_"
                }
                throw
            }
        }
    }

    end {
    }
}