Forge.psm1

function Get-Issue {
    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [string]
        $Id,

        [Parameter()]
        [ValidateSet('open', 'closed', 'all')]
        [string]
        $State,

        [Parameter()]
        [switch]
        $Mine,

        [Parameter()]
        [string]
        $Group,

        [Parameter()]
        [string]
        $Assignee,

        [Parameter()]
        [string]
        $Author,

        [Parameter()]
        [string]
        $Labels,

        [Parameter()]
        [string]
        $Since,

        [Parameter()]
        [ValidateSet('created', 'updated', 'comments')]
        [string]
        $Sort,

        [Parameter()]
        [ValidateSet('asc', 'desc')]
        [string]
        $Direction,

        [Parameter()]
        [uint]
        $MaxPages,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-Issue' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            if ($Id)        { $Params.IssueId      = $Id }
            if ($Repo)      { $Params.RepositoryId = $Repo }
            if ($State)     { $Params.State         = $State }
            if ($Mine)      { $Params.Mine          = $true }
            if ($Group)     { $Params.Organization  = $Group }
            if ($Assignee)  { $Params.Assignee      = $Assignee }
            if ($Author)    { $Params.Creator        = $Author }
            if ($Labels)    { $Params.Labels         = $Labels }
            if ($Since)     { $Params.Since          = $Since }
            if ($Sort)      { $Params.Sort           = $Sort }
            if ($Direction) { $Params.Direction       = $Direction }
            if ($MaxPages)  { $Params.MaxPages       = $MaxPages }
            if ($All)       { $Params.All            = $true }
        }
        'gitlab' {
            if ($Id)        { $Params.IssueId          = $Id }
            if ($Repo)      { $Params.ProjectId        = $Repo }
            if ($Mine)      { $Params.Mine             = $true }
            if ($Group)     { $Params.GroupId           = $Group }
            if ($Assignee)  { $Params.AssigneeUsername  = $Assignee }
            if ($Author)    { $Params.AuthorUsername    = $Author }
            if ($Since)     { $Params.CreatedAfter      = $Since }
            if ($MaxPages)  { $Params.MaxPages          = $MaxPages }
            if ($All)       { $Params.All               = $true }
            if ($State) {
                $Params.State = switch ($State) {
                    'open'   { 'opened' }
                    'closed' { 'closed' }
                    'all'    { $null }
                }
            }
            if ($Labels)    { $Params.Labels  = $Labels }
            if ($Sort) {
                $Params.OrderBy = switch ($Sort) {
                    'created'  { 'created_at' }
                    'updated'  { 'updated_at' }
                    'comments' { $null }
                }
                if ($Sort -eq 'comments') {
                    Write-Warning "Get-Issue -Sort 'comments' is not supported by the Gitlab provider"
                }
            }
            if ($Direction) { $Params.Sort = $Direction }
        }
    }

    & $Target.Command @Params
}

function Get-ChangeRequest {
    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [string]
        $Id,

        [Parameter()]
        [ValidateSet('open', 'closed', 'all', 'merged')]
        [string]
        $State,

        [Parameter()]
        [switch]
        $Mine,

        [Parameter()]
        [string]
        $Group,

        [Parameter()]
        [Alias('Branch')]
        [string]
        $SourceBranch,

        [Parameter()]
        [string]
        $TargetBranch,

        [Parameter()]
        [string]
        $Author,

        [Parameter()]
        [switch]
        $IsDraft,

        [Parameter()]
        [string]
        $Since,

        [Parameter()]
        [string]
        $Until,

        [Parameter()]
        [string]
        $Reviewer,

        [Parameter()]
        [uint]
        $MaxPages,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-ChangeRequest' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            if ($Id)           { $Params.PullRequestId = $Id }
            if ($Repo)         { $Params.RepositoryId  = $Repo }
            if ($State) {
                $Params.State = switch ($State) {
                    'open'   { 'open' }
                    'closed' { 'closed' }
                    'all'    { 'all' }
                    'merged' { 'merged' }
                }
            }
            if ($Mine)         { $Params.Mine          = $true }
            if ($SourceBranch) { $Params.Head           = $SourceBranch }
            if ($TargetBranch) { $Params.Base            = $TargetBranch }
            if ($MaxPages)     { $Params.MaxPages        = $MaxPages }
            if ($All)          { $Params.All             = $true }
            if ($Author)       { $Params.Author          = $Author }
            if ($IsDraft)      { $Params.IsDraft          = $true }
            if ($Since)        { $Params.Since            = $Since }
            if ($Until)        { $Params.Until            = $Until }
            if ($Reviewer)     { $Params.ReviewedBy       = $Reviewer }
            # Use cross-repo search when filters are present but no repo context
            if (-not $Id -and -not $Mine -and -not $Repo -and ($Author -or $Reviewer -or $Since -or $Until)) {
                $Context = Get-ForgeRemoteHost
                if ($Context.Host -notmatch 'github') {
                    $Params.Search = $true
                }
            }
        }
        'gitlab' {
            if ($Id)           { $Params.MergeRequestId = $Id }
            if ($Repo)         { $Params.ProjectId      = $Repo }
            if ($Mine)         { $Params.Mine            = $true }
            if ($Group)        { $Params.GroupId          = $Group }
            if ($SourceBranch) { $Params.SourceBranch     = $SourceBranch }
            if ($Author)       { $Params.Username         = $Author }
            if ($IsDraft)      { $Params.IsDraft          = $true }
            if ($Since)        { $Params.CreatedAfter     = $Since }
            if ($Until)        { $Params.CreatedBefore    = $Until }
            if ($Reviewer)     { $Params.ReviewerUsername  = $Reviewer }
            if ($MaxPages)     { $Params.MaxPages         = $MaxPages }
            if ($All)          { $Params.All              = $true }
            if ($State) {
                $Params.State = switch ($State) {
                    'open'   { 'opened' }
                    'closed' { 'closed' }
                    'all'    { 'all' }
                    'merged' { 'merged' }
                }
            }
            if ($TargetBranch) { $Params.TargetBranch = $TargetBranch }
        }
    }

    & $Target.Command @Params
}

function Get-Branch {
    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [string]
        $Name,

        [Parameter()]
        [switch]
        $Protected,

        [Parameter()]
        [string]
        $Search,

        [Parameter()]
        [uint]
        $MaxPages,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-Branch' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            if ($Name)      { $Params.Name     = $Name }
            if ($Repo)      { $Params.RepositoryId = $Repo }
            if ($Protected) { $Params.Protected = $true }
            if ($MaxPages)  { $Params.MaxPages  = $MaxPages }
            if ($All)       { $Params.All       = $true }
            if ($Search)    { Write-Warning "Get-Branch -Search is not supported by the Github provider; use -Name for exact match" }
        }
        'gitlab' {
            if ($Name)      { $Params.Ref      = $Name }
            if ($Repo)      { $Params.ProjectId = $Repo }
            if ($Search)    { $Params.Search   = $Search }
            if ($MaxPages)  { $Params.MaxPages = $MaxPages }
            if ($All)       { $Params.All      = $true }
            if ($Protected) { Write-Warning "Get-Branch -Protected is not supported by the Gitlab provider; use Get-GitlabProtectedBranch" }
        }
    }

    & $Target.Command @Params
}

function Get-Release {
    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [string]
        $Tag,

        [Parameter()]
        [switch]
        $Latest,

        [Parameter()]
        [uint]
        $MaxPages,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-Release' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            if ($Tag)      { $Params.Tag      = $Tag }
            if ($Repo)     { $Params.RepositoryId = $Repo }
            if ($Latest)   { $Params.Latest   = $true }
            if ($MaxPages) { $Params.MaxPages = $MaxPages }
            if ($All)      { $Params.All      = $true }
        }
        'gitlab' {
            if ($Tag)      { $Params.Tag      = $Tag }
            if ($Repo)     { $Params.ProjectId = $Repo }
            if ($MaxPages) { $Params.MaxPages = $MaxPages }
            if ($All)      { $Params.All      = $true }
            if ($Latest)   { Write-Warning "Get-Release -Latest is not supported by the Gitlab provider" }
        }
    }

    & $Target.Command @Params
}

function Get-User {
    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [string]
        $Username,

        [Parameter()]
        [switch]
        $Me,

        [Parameter()]
        [string]
        $Select,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-User' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            if ($Username) { $Params.Username = $Username }
            if ($Me)       { $Params.Me       = $true }
            if ($Select)   { $Params.Select   = $Select }
        }
        'gitlab' {
            if ($Username) { $Params.UserId = $Username }
            if ($Me)       { $Params.Me     = $true }
            if ($Select)   { $Params.Select = $Select }
        }
    }

    & $Target.Command @Params
}

function Get-Group {
    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [string]
        $Name,

        [Parameter()]
        [switch]
        $Mine,

        [Parameter()]
        [uint]
        $MaxPages,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-Group' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            if ($Name)     { $Params.Name     = $Name }
            if ($Mine)     { $Params.Mine     = $true }
            if ($MaxPages) { $Params.MaxPages = $MaxPages }
            if ($All)      { $Params.All      = $true }
        }
        'gitlab' {
            if ($Name)     { $Params.GroupId  = $Name }
            if ($MaxPages) { $Params.MaxPages = $MaxPages }
            if ($All)      { $Params.All      = $true }
            if ($Mine)     { Write-Warning "Get-Group -Mine is not directly supported by the Gitlab provider" }
        }
    }

    & $Target.Command @Params
}

function New-Issue {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [string]
        $Title,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [string[]]
        $Assignees,

        [Parameter()]
        [string[]]
        $Labels,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'New-Issue' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.Title = $Title
            if ($Repo)        { $Params.RepositoryId = $Repo }
            if ($Description) { $Params.Description = $Description }
            if ($Assignees)   { $Params.Assignees   = $Assignees }
            if ($Labels)      { $Params.Labels      = $Labels }
        }
        'gitlab' {
            $Params.Title = $Title
            if ($Repo)        { $Params.ProjectId   = $Repo }
            if ($Description) { $Params.Description = $Description }
            if ($Assignees)   { $Params.Assignees   = $Assignees }
            if ($Labels)      { $Params.Labels      = $Labels -join ',' }
        }
    }

    if ($PSCmdlet.ShouldProcess($Title, 'Create Issue')) {
        & $Target.Command @Params
    }
}

function Update-Issue {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Id,

        [Parameter()]
        [string]
        $Title,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [ValidateSet('open', 'closed')]
        [string]
        $State,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Update-Issue' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.IssueId = $Id
            if ($Repo)        { $Params.RepositoryId = $Repo }
            if ($Title)       { $Params.Title       = $Title }
            if ($Description) { $Params.Description = $Description }
            if ($State)       { $Params.State       = $State }
        }
        'gitlab' {
            $Params.IssueId = $Id
            if ($Repo)        { $Params.ProjectId   = $Repo }
            if ($Title)       { $Params.Title       = $Title }
            if ($Description) { $Params.Description = $Description }
            if ($State) {
                $Params.StateEvent = switch ($State) {
                    'open'   { 'reopen' }
                    'closed' { 'close' }
                }
            }
        }
    }

    if ($PSCmdlet.ShouldProcess($Id, 'Update Issue')) {
        & $Target.Command @Params
    }
}

function Close-Issue {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Id,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Close-Issue' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.IssueId = $Id
            if ($Repo) { $Params.RepositoryId = $Repo }
        }
        'gitlab' {
            $Params.IssueId = $Id
            if ($Repo) { $Params.ProjectId = $Repo }
        }
    }

    if ($PSCmdlet.ShouldProcess("Issue #$Id", 'Close')) {
        & $Target.Command @Params
    }
}

function New-ChangeRequest {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [string]
        $Title,

        [Parameter(Mandatory)]
        [Alias('Branch', 'Head')]
        [string]
        $SourceBranch,

        [Parameter()]
        [Alias('Base')]
        [string]
        $TargetBranch,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [switch]
        $Draft,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'New-ChangeRequest' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.Title        = $Title
            $Params.SourceBranch = $SourceBranch
            if ($Repo)         { $Params.RepositoryId = $Repo }
            if ($TargetBranch) { $Params.TargetBranch = $TargetBranch }
            if ($Description)  { $Params.Description  = $Description }
            if ($Draft)        { $Params.Draft        = $true }
        }
        'gitlab' {
            $Params.Title        = $Title
            $Params.SourceBranch = $SourceBranch
            if ($Repo)         { $Params.ProjectId    = $Repo }
            if ($TargetBranch) { $Params.TargetBranch = $TargetBranch }
            if ($Description)  { $Params.Description  = $Description }
            if ($Draft)        { $Params.Draft        = $true }
        }
    }

    if ($PSCmdlet.ShouldProcess("$SourceBranch -> $TargetBranch", 'Create Change Request')) {
        & $Target.Command @Params
    }
}

function Merge-ChangeRequest {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Id,

        [Parameter()]
        [switch]
        $Squash,

        [Parameter()]
        [switch]
        $DeleteSourceBranch,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Merge-ChangeRequest' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.PullRequestId = $Id
            if ($Repo)               { $Params.RepositoryId      = $Repo }
            if ($Squash)             { $Params.MergeMethod        = 'squash' }
            if ($DeleteSourceBranch) { $Params.DeleteSourceBranch = $true }
        }
        'gitlab' {
            $Params.MergeRequestId = $Id
            if ($Repo)               { $Params.ProjectId                = $Repo }
            if ($Squash)             { $Params.Squash                   = $true }
            if ($DeleteSourceBranch) { $Params.ShouldRemoveSourceBranch = $true }
        }
    }

    if ($PSCmdlet.ShouldProcess("Change Request #$Id", 'Merge')) {
        & $Target.Command @Params
    }
}

function New-Repo {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [ValidateSet('public', 'private')]
        [string]
        $Visibility,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'New-Repo' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.Name = $Name
            if ($Description) { $Params.Description = $Description }
            if ($Visibility)  { $Params.Visibility  = $Visibility }
        }
        'gitlab' {
            $Params.Name = $Name
            if ($Description) { $Params.Description = $Description }
            if ($Visibility)  { $Params.Visibility  = $Visibility }
        }
    }

    if ($PSCmdlet.ShouldProcess($Name, 'Create Repository')) {
        & $Target.Command @Params
    }
}

function Get-Commit {
    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [string]
        $Ref,

        [Parameter()]
        [string]
        $Branch,

        [Parameter()]
        [string]
        $Author,

        [Parameter()]
        [string]
        $Since,

        [Parameter()]
        [string]
        $Until,

        [Parameter()]
        [uint]
        $MaxPages,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-Commit' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            if ($Ref)      { $Params.Sha      = $Ref }
            if ($Repo)     { $Params.RepositoryId = $Repo }
            if ($Branch)   { $Params.Branch   = $Branch }
            if ($Author)   { $Params.Author   = $Author }
            if ($Since)    { $Params.Since    = $Since }
            if ($Until)    { $Params.Until    = $Until }
            if ($MaxPages) { $Params.MaxPages = $MaxPages }
            if ($All)      { $Params.All      = $true }
        }
        'gitlab' {
            if ($Ref)      { $Params.Sha      = $Ref }
            if ($Repo)     { $Params.ProjectId = $Repo }
            if ($Branch)   { $Params.Ref      = $Branch }
            if ($MaxPages) { $Params.MaxPages = $MaxPages }
            if ($All)      { $Params.All      = $true }
            if ($Author)   { $Params.Author = $Author }
            if ($Since)    { $Params.Since  = $Since }
            if ($Until)    { $Params.Until  = $Until }
        }
    }

    & $Target.Command @Params
}

function Search-Repo {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Query,

        [Parameter()]
        [ValidateSet('code', 'commits', 'issues')]
        [string]
        $Scope,

        [Parameter()]
        [uint]
        $MaxPages,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Search-Repo' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.Query = $Query
            if ($Scope)    { $Params.Scope    = $Scope }
            if ($MaxPages) { $Params.MaxPages = $MaxPages }
            if ($All)      { $Params.All      = $true }
        }
        'gitlab' {
            $Params.Search = $Query
            if ($MaxPages) { $Params.MaxPages = $MaxPages }
            if ($All)      { $Params.All      = $true }
            if ($Scope) {
                $Params.Scope = switch ($Scope) {
                    'code'    { 'blobs' }
                    'commits' { 'commits' }
                    'issues'  { 'issues' }
                }
            }
        }
    }

    & $Target.Command @Params
}

function Close-ChangeRequest {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Id,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Close-ChangeRequest' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.PullRequestId = $Id
            if ($Repo) { $Params.RepositoryId = $Repo }
        }
        'gitlab' {
            $Params.MergeRequestId = $Id
            if ($Repo) { $Params.ProjectId = $Repo }
        }
    }

    if ($PSCmdlet.ShouldProcess("Change Request #$Id", 'Close')) {
        & $Target.Command @Params
    }
}

function Update-ChangeRequest {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Id,

        [Parameter()]
        [string]
        $Title,

        [Parameter()]
        [string]
        $Description,

        [Parameter()]
        [ValidateSet('open', 'closed')]
        [string]
        $State,

        [Parameter()]
        [string]
        $TargetBranch,

        [Parameter()]
        [switch]
        $Draft,

        [Parameter()]
        [switch]
        $MarkReady,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Update-ChangeRequest' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.PullRequestId = $Id
            if ($Repo)         { $Params.RepositoryId = $Repo }
            if ($Title)        { $Params.Title        = $Title }
            if ($Description)  { $Params.Description  = $Description }
            if ($State)        { $Params.State        = $State }
            if ($TargetBranch) { $Params.TargetBranch = $TargetBranch }
            if ($Draft)        { $Params.Draft            = $true }
            if ($MarkReady)    { $Params.MarkReady        = $true }
        }
        'gitlab' {
            $Params.MergeRequestId = $Id
            if ($Repo)         { $Params.ProjectId   = $Repo }
            if ($Title)        { $Params.Title       = $Title }
            if ($Description)  { $Params.Description = $Description }
            if ($Draft)        { $Params.Draft       = $true }
            if ($MarkReady)    { $Params.MarkReady   = $true }
            if ($State) {
                switch ($State) {
                    'open'   { $Params.Reopen = $true }
                    'closed' { $Params.Close  = $true }
                }
            }
            if ($TargetBranch) { $Params.TargetBranch = $TargetBranch }
        }
    }

    if ($PSCmdlet.ShouldProcess("Change Request #$Id", 'Update')) {
        & $Target.Command @Params
    }
}

function Get-ChangeRequestComment {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Id,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-ChangeRequestComment' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.PullRequestId = $Id
            if ($Repo) { $Params.RepositoryId = $Repo }
        }
        'gitlab' {
            $Params.MergeRequestId = $Id
            if ($Repo) { $Params.ProjectId = $Repo }
        }
    }

    & $Target.Command @Params
}

function Get-ChangeRequestApproval {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Id,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-ChangeRequestApproval' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.PullRequestId = $Id
            if ($Repo) { $Params.RepositoryId = $Repo }
        }
        'gitlab' {
            $Params.MergeRequestId = $Id
            if ($Repo) { $Params.ProjectId = $Repo }
        }
    }

    $Result = & $Target.Command @Params

    switch ($Target.Provider) {
        'github' {
            # Normalize GitHub reviews to ApprovedBy list
            $Approved = $Result | Where-Object State -eq 'APPROVED'
            [PSCustomObject]@{
                ApprovedBy = @($Approved | ForEach-Object { $_.User.Login })
            }
        }
        'gitlab' {
            $Result
        }
    }
}

function Open-Issue {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Id,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Open-Issue' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.IssueId = $Id
            if ($Repo) { $Params.RepositoryId = $Repo }
        }
        'gitlab' {
            $Params.IssueId = $Id
            if ($Repo) { $Params.ProjectId = $Repo }
        }
    }

    & $Target.Command @Params
}

function New-IssueComment {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Id,

        [Parameter(Mandatory)]
        [string]
        $Body,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'New-IssueComment' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.IssueId = $Id
            if ($Repo) { $Params.RepositoryId = $Repo }
            $Params.Body    = $Body
        }
        'gitlab' {
            $Params.IssueId = $Id
            if ($Repo) { $Params.ProjectId = $Repo }
            $Params.Note    = $Body
        }
    }

    if ($PSCmdlet.ShouldProcess("Issue #$Id", 'Add Comment')) {
        & $Target.Command @Params
    }
}

function New-Branch {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Ref,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'New-Branch' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.Name = $Name
            if ($Repo) { $Params.RepositoryId = $Repo }
            if ($Ref)  { $Params.Ref = $Ref }
        }
        'gitlab' {
            $Params.Branch = $Name
            if ($Repo) { $Params.ProjectId = $Repo }
            if ($Ref)  { $Params.Ref = $Ref }
        }
    }

    if ($PSCmdlet.ShouldProcess($Name, 'Create Branch')) {
        & $Target.Command @Params
    }
}

function Remove-Branch {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Remove-Branch' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.BranchId = $Name
            if ($Repo) { $Params.RepositoryId = $Repo }
        }
        'gitlab' {
            $Params.Branch = $Name
            if ($Repo) { $Params.ProjectId = $Repo }
        }
    }

    if ($PSCmdlet.ShouldProcess($Name, 'Delete Branch')) {
        & $Target.Command @Params
    }
}

function Remove-Repo {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Id,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Remove-Repo' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.RepositoryId = $Id
        }
        'gitlab' {
            $Params.ProjectId = $Id
        }
    }

    if ($PSCmdlet.ShouldProcess($Id, 'Delete Repository')) {
        & $Target.Command @Params
    }
}

function Get-GroupMember {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position=0)]
        [string]
        $Group,

        [Parameter()]
        [string]
        $Username,

        [Parameter()]
        [uint]
        $MaxPages,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-GroupMember' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.Organization = $Group
            if ($Username) { $Params.Username = $Username }
            if ($MaxPages) { $Params.MaxPages = $MaxPages }
            if ($All)      { $Params.All      = $true }
        }
        'gitlab' {
            $Params.GroupId = $Group
            if ($Username) { $Params.UserId   = $Username }
            if ($MaxPages) { $Params.MaxPages = $MaxPages }
            if ($All)      { $Params.All      = $true }
        }
    }

    & $Target.Command @Params
}

function Add-GroupMember {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [string]
        $Group,

        [Parameter(Mandatory, Position=0)]
        [string]
        $Username,

        [Parameter()]
        [string]
        $Role,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Add-GroupMember' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.Organization = $Group
            $Params.Username     = $Username
            if ($Role) { $Params.Role = $Role }
        }
        'gitlab' {
            $Params.GroupId = $Group
            $Params.UserId  = $Username
            if ($Role) { $Params.AccessLevel = $Role }
        }
    }

    if ($PSCmdlet.ShouldProcess("$Group/$Username", 'Add Member')) {
        & $Target.Command @Params
    }
}

function Remove-GroupMember {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [string]
        $Group,

        [Parameter(Mandatory, Position=0)]
        [string]
        $Username,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Remove-GroupMember' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            $Params.Organization = $Group
            $Params.Username     = $Username
        }
        'gitlab' {
            $Params.GroupId = $Group
            $Params.UserId  = $Username
        }
    }

    if ($PSCmdlet.ShouldProcess("$Group/$Username", 'Remove Member')) {
        & $Target.Command @Params
    }
}

function Get-Milestone {
    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [string]
        $Id,

        [Parameter()]
        [ValidateSet('open', 'closed', 'all')]
        [string]
        $State,

        [Parameter()]
        [string]
        $Repo,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-Milestone' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            if ($Id)    { $Params.MilestoneId = $Id }
            if ($Repo)  { $Params.RepositoryId = $Repo }
            if ($State) { $Params.State       = $State }
        }
        'gitlab' {
            if ($Id)   { $Params.MilestoneId = $Id }
            if ($Repo) { $Params.ProjectId = $Repo }
            if ($State) {
                $Params.State = switch ($State) {
                    'open'   { 'active' }
                    'closed' { 'closed' }
                    'all'    { $null }
                }
            }
        }
    }

    & $Target.Command @Params
}

function Get-Repo {
    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [string]
        $Id,

        [Parameter()]
        [switch]
        $Mine,

        [Parameter()]
        [string]
        $Group,

        [Parameter()]
        [string]
        $Select,

        [Parameter()]
        [switch]
        $IncludeArchived,

        [Parameter()]
        [uint]
        $MaxPages,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-Repo' -Provider $Provider
    $Params = @{}

    switch ($Target.Provider) {
        'github' {
            if ($Id)       { $Params.RepositoryId = $Id }
            if ($Mine)     { $Params.Mine         = $true }
            if ($Group)    { $Params.Organization = $Group }
            if ($Select)   { $Params.Select       = $Select }
            if ($MaxPages) { $Params.MaxPages     = $MaxPages }
            if ($All)      { $Params.All          = $true }
            if ($IncludeArchived) { Write-Warning "Get-Repo -IncludeArchived is not applicable to Github" }
        }
        'gitlab' {
            if ($Id)       { $Params.ProjectId    = $Id }
            if ($Mine)     { $Params.Mine         = $true }
            if ($Group)    { $Params.GroupId      = $Group }
            if ($Select)   { $Params.Select       = $Select }
            if ($MaxPages) { $Params.MaxPages     = $MaxPages }
            if ($All)      { $Params.All          = $true }
            if ($IncludeArchived) { $Params.IncludeArchived = $true }
        }
    }

    & $Target.Command @Params
}

function Get-UserActivity {
    [CmdletBinding()]
    param(
        [Parameter()]
        [switch]
        $Mine,

        [Parameter()]
        [string]
        $Username,

        [Parameter()]
        [string]
        $Since,

        [Parameter()]
        [string]
        $Until,

        [Parameter()]
        [string]
        $Action,

        [Parameter()]
        [string]
        $TargetType,

        [Parameter()]
        [string]
        $Branch,

        [Parameter()]
        [switch]
        $ExcludeMerges,

        [Parameter()]
        [uint]
        $MaxPages,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [ValidateSet([SupportedProvider])]
        [string]
        $Provider
    )

    $Target = Resolve-ForgeCommand -CommandName 'Get-UserActivity' -Provider $Provider
    $Params = @{}
    $ClientFilters = @()

    switch ($Target.Provider) {
        'github' {
            if ($Mine) {
                $Me = Get-GithubUser -Me
                $Params.Username = $Me.Login
            }
            if ($Username)  { $Params.Username  = $Username }
            if ($MaxPages)  { $Params.MaxPages  = $MaxPages }
            if ($All)       { $Params.All       = $true }
            if ($Since) {
                $SinceDate = [datetime]::Parse($Since)
                $ClientFilters += { param($e) $e.CreatedAt -and $e.CreatedAt -ge $SinceDate }.GetNewClosure()
            }
            if ($Until) {
                $UntilDate = [datetime]::Parse($Until)
                $ClientFilters += { param($e) $e.CreatedAt -and $e.CreatedAt -le $UntilDate }.GetNewClosure()
            }
            if ($Action) {
                $ActionTypeMap = @{
                    'pushed'    = @('PushEvent')
                    'created'   = @('IssuesEvent', 'PullRequestEvent')
                    'merged'    = @('PullRequestEvent')
                    'commented' = @('IssueCommentEvent', 'PullRequestReviewCommentEvent')
                    'approved'  = @('PullRequestReviewEvent')
                    'closed'    = @('IssuesEvent', 'PullRequestEvent')
                    'reopened'  = @('IssuesEvent', 'PullRequestEvent')
                }
                $AllowedTypes = $ActionTypeMap[$Action]
                if ($AllowedTypes) {
                    $ClientFilters += { param($e) $e.Type -in $AllowedTypes }.GetNewClosure()
                } else {
                    Write-Warning "Get-UserActivity -Action '$Action' has no known GitHub event type mapping; returning unfiltered results"
                }
            }
            if ($TargetType) {
                $TargetTypeMap = @{
                    'issue'         = @('IssuesEvent', 'IssueCommentEvent')
                    'merge_request' = @('PullRequestEvent', 'PullRequestReviewEvent', 'PullRequestReviewCommentEvent')
                    'note'          = @('IssueCommentEvent', 'PullRequestReviewCommentEvent')
                }
                $AllowedTypes = $TargetTypeMap[$TargetType]
                if ($AllowedTypes) {
                    $ClientFilters += { param($e) $e.Type -in $AllowedTypes }.GetNewClosure()
                } else {
                    Write-Warning "Get-UserActivity -TargetType '$TargetType' has no known GitHub event type mapping; returning unfiltered results"
                }
            }
        }
        'gitlab' {
            if ($Mine)       { $Params.Me         = $true }
            if ($Username)   { $Params.UserId     = $Username }
            if ($Since)      { $Params.After      = $Since }
            if ($Until)      { $Params.Before     = $Until }
            if ($Action)     { $Params.Action     = $Action }
            if ($TargetType) { $Params.TargetType = $TargetType }
            if ($MaxPages)   { $Params.MaxPages   = $MaxPages }
            if ($All)        { $Params.All        = $true }
            $Params.FetchProjects = $true
        }
    }

    if ($Branch) {
        $BranchName = $Branch
        $ClientFilters += {
            param($e)
            if ($e.Type -eq 'PushEvent') {
                # GitHub: refs/heads/main
                ($e.Payload.ref -replace '^refs/heads/', '') -eq $BranchName
            } elseif ($e.PushData) {
                # GitLab: ref = main
                $e.PushData.ref -eq $BranchName
            } else {
                $true
            }
        }.GetNewClosure()
    }
    if ($ExcludeMerges) {
        $ClientFilters += {
            param($e)
            $title = if ($e.Type -eq 'PushEvent') {
                ($e.Payload.commits | Select-Object -First 1).message
            } elseif ($e.PushData) {
                $e.PushData.commit_title
            } else { $null }
            -not ($title -match '^Merge branch ')
        }.GetNewClosure()
    }

    $Results = & $Target.Command @Params

    if ($Target.Provider -eq 'gitlab') {
        $Results = @($Results | ForEach-Object {
            $_ | Add-Member -NotePropertyName 'ProjectPath' -NotePropertyValue $_.Project.PathWithNamespace -PassThru
        })
    }

    foreach ($Filter in $ClientFilters) {
        $Results = @($Results | Where-Object { & $Filter $_ })
    }

    $Results
}