PowerLFM.psm1

using namespace System.Management.Automation

New-Variable -Name baseUrl -Value 'https://ws.audioscrobbler.com/2.0'

function Add-LFMAlbumTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Album,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateCount(1, 10)]
        [string[]] $Tag
    )

    begin {
        $apiParams = @{
            'method' = 'album.addTags'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $convertedParams = ConvertTo-LFMParameter $noCommonParams
        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        if ($PSCmdlet.ShouldProcess("Album: $Album", "Adding album tag: $Tag")) {
            try {
                $irm = Invoke-LFMApiUri -Uri $apiUrl -Method Post
                if ($irm.Lfm.Status -eq 'ok') {Write-Verbose "Tag: $Tag has been added"}
            }
            catch {
                throw $_
            }
        }
    }
}

function Add-LFMArtistTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateCount(1, 10)]
        [string[]] $Tag
    )

    begin {
        $apiParams = @{
            'method' = 'artist.addTags'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $convertedParams = ConvertTo-LFMParameter $noCommonParams
        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        if ($PSCmdlet.ShouldProcess("Artist: $Artist", "Adding artist tag: $Tag")) {
            try {
                $irm = Invoke-LFMApiUri -Uri $apiUrl -Method Post
                if ($irm.Lfm.Status -eq 'ok') {Write-Verbose "Tag: $Tag has been added"}
            }
            catch {
                throw $_
            }
        }
    }
}

function Add-LFMConfiguration {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $ApiKey,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $SessionKey,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $SharedSecret
    )

    process {
        try {
            $keys = $PSBoundParameters.Keys.Where({$_ -in  @('ApiKey', 'SessionKey', 'SharedSecret')})

            foreach ($param in $keys) {
                $ssParams = @{
                    Name = "LFM$param"
                    Secret = $PSBoundParameters[$param]
                    Vault = 'BuiltInLocalVault'
                    NoClobber = $true
                }

                try {
                    $null = Get-Secret -Name "LFM$param" -ErrorAction Stop

                    $message = "There is already a value present for $param, do you wish to update the value?"

                    if ($PSCmdlet.ShouldProcess('BuiltInLocalVault', $message)) {
                        $ssParams.Remove('NoClobber')
                        Set-Secret @ssParams
                    }
                }
                catch {
                    Set-Secret @ssParams
                }
            }
        }
        catch {
            throw $_
        }
    }
}

function Add-LFMTrackTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateCount(1, 10)]
        [string[]] $Tag
    )

    begin {
        $apiParams = @{
            'method' = 'track.addTags'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $convertedParams = ConvertTo-LFMParameter $noCommonParams
        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        if ($PSCmdlet.ShouldProcess("Track: $Track", "Adding track tag: $Tag")) {
            try {
                $irm = Invoke-LFMApiUri -Uri $apiUrl -Method Post
                if ($irm.Lfm.Status -eq 'ok') {Write-Verbose "Tag: $Tag has been added"}
            }
            catch {
                throw $_
            }
        }
    }
}

function Get-LFMAlbumInfo {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'album')]
    [OutputType('PowerLFM.Album.Info')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'album')]
        [ValidateNotNullOrEmpty()]
        [string] $Album,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 1,
                   ParameterSetName = 'album')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [string] $UserName,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'album.getInfo'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $tracks = foreach ($track in $irm.Album.Tracks.Track) {
                $trackInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Album.Track'
                    'Track' = $track.Name
                    'Duration' = [int] $track.Duration
                    'Url' = [uri] $track.Url
                }
                Write-Output $trackInfo
            }

            $tags = foreach ($tag in $irm.Album.Tags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Album.Tag'
                    'Tag' = $tag.Name
                    'Url' = [uri] $tag.Url
                }
                Write-Output $tagInfo
            }

            $albumInfo = @{
                'PSTypeName' = 'PowerLFM.Album.Info'
                'Artist' = $irm.Album.Artist
                'Album' = $irm.Album.Name
                'Id' = $irm.Album.Mbid
                'Listeners' = [int] $irm.Album.Listeners
                'PlayCount' = [int] $irm.Album.PlayCount
                'Url' = [uri] $irm.Album.Url
                'Summary' = $irm.Album.Wiki.Summary
                'Tracks' = $tracks
                'Tags' = $tags
            }

            $userPlayCount = [int] $irm.Album.UserPlayCount
            if ($PSBoundParameters.ContainsKey('UserName')) {
                $albumInfo.Add('UserPlayCount', $userPlayCount)
            }

            $albumInfo = [pscustomobject] $albumInfo
            Write-Output $albumInfo
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMAlbumTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'album')]
    [OutputType('PowerLFM.Album.Tag')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'album')]
        [ValidateNotNullOrEmpty()]
        [string] $Album,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 1,
                   ParameterSetName = 'album')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [string] $UserName,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'album.getTags'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($tag in $irm.Tags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Album.Tag'
                    'Tag' = $tag.Name
                    'Url' = [uri] $tag.Url
                }

                Write-Output $tagInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMAlbumTopTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'album')]
    [OutputType('PowerLFM.Album.Tag')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'album')]
        [ValidateNotNullOrEmpty()]
        [string] $Album,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 1,
                   ParameterSetName = 'album')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'album.getTopTags'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($tag in $irm.TopTags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Album.Tag'
                    'Tag' = $tag.Name
                    'Url' = [uri] $tag.Url
                    'Match' = [int] $tag.Count
                }

                Write-Output $tagInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMArtistCorrection {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Artist.Correction')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist
    )

    begin {
        $apiParams = @{
            'method' = 'artist.getCorrection'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $correction = $irm.Corrections.Correction.Artist
            $correctedArtistInfo = [pscustomobject] @{
                'PSTypeName' = 'PowerLFM.Artist.Correction'
                'Artist' = $correction.Name
                'Id' = $correction.Mbid
                'Url' = [uri] $correction.Url
            }

            Write-Output $correctedArtistInfo
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMArtistInfo {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'artist')]
    [OutputType('PowerLFM.Artist.Info')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'artist')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [string] $UserName,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'artist.getInfo'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $similarArtists = foreach ($similar in $irm.Artist.Similar.Artist) {
                $similarInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Artist.Similar'
                    'Artist' = $similar.Name
                    'Url' = [uri] $similar.Url
                }

                Write-Output $similarInfo
            }

            $tags = foreach ($tag in $irm.Artist.Tags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Artist.Tag'
                    'Tag' = $tag.Name
                    'Url' = [uri] $tag.Url
                }

                Write-Output $tagInfo
            }

            switch ($irm.Artist.OnTour) {
                '0' {$tour = 'No'}
                '1' {$tour = 'Yes'}
            }

            $artistInfo = @{
                'PSTypeName' = 'PowerLFM.Artist.Info'
                'Artist' = $irm.Artist.Name
                'Id' = $irm.Artist.Mbid
                'Listeners' = [int] $irm.Artist.Stats.Listeners
                'PlayCount' = [int] $irm.Artist.Stats.PlayCount
                'OnTour' = $tour
                'Url' = [uri] $irm.Artist.Url
                'Summary' = $irm.Artist.Bio.Summary
                'SimilarArtists' = $similarArtists
                'Tags' = $tags
            }

            $userPlayCount = [int] $irm.Artist.Stats.UserPlayCount
            if ($PSBoundParameters.ContainsKey('UserName')) {
                $artistInfo.Add('UserPlayCount', $userPlayCount)
            }

            $artistInfo = [pscustomobject] $artistInfo
            Write-Output $artistInfo
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMArtistSimilar {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'artist')]
    [OutputType('PowerLFM.Artist.Similar')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'artist')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [int] $Limit,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'artist.getSimilar'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($similar in $irm.SimilarArtists.Artist) {
                $similarInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Artist.Similar'
                    'Artist' = $similar.Name
                    'Id' = $similar.Mbid
                    'Url' = [uri] $similar.Url
                    'Match' = [math]::Round($similar.Match, 2)
                }

                Write-Output $similarInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMArtistTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'artist')]
    [OutputType('PowerLFM.Artist.Tag')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'artist')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [string] $UserName,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'artist.getTags'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($tag in $irm.Tags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Artist.Tag'
                    'Tag' = $tag.Name
                    'Url' = [uri] $tag.Url
                }

                Write-Output $tagInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMArtistTopAlbum {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'artist')]
    [OutputType('PowerLFM.Artist.Album')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'artist')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'artist.getTopAlbums'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($album in $irm.TopAlbums.Album) {
                $albumInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Artist.Album'
                    'Album' = $album.Name
                    'Id' = $album.Mbid
                    'Url' = [uri]$album.Url
                    'PlayCount' = [int] $album.PlayCount
                }

                Write-Output $albumInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMArtistTopTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'artist')]
    [OutputType('PowerLFM.Artist.Tag')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'artist')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'artist.getTopTags'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($tag in $irm.TopTags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Artist.Tag'
                    'Tag' = $tag.Name
                    'Url' = [uri] $tag.Url
                    'Match' = [int] $tag.Count
                }

                Write-Output $tagInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMArtistTopTrack {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'artist')]
    [OutputType('PowerLFM.Artist.Track')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'artist')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'artist.getTopTracks'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($track in $irm.TopTracks.Track) {
                $trackInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Artist.Track'
                    'Track' = $track.Name
                    'Id' = $track.Mbid
                    'Url' = [uri] $track.Url
                    'Listeners' = [int] $track.Listeners
                    'PlayCount' = [int] $track.PlayCount
                }

                Write-Output $trackInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMChartTopArtist {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Chart.TopArtists')]
    param (
        [Parameter()]
        [ValidateRange(1, 119)]
        [int] $Limit,

        [int] $Page
    )

    $apiParams = @{
        'method' = 'chart.getTopArtists'
        'api_key' = $script:LFMConfig.ApiKey
        'format' = 'json'
    }

    $noCommonParams = Remove-CommonParameter $PSBoundParameters
    $convertedParams = ConvertTo-LFMParameter $noCommonParams

    $query = New-LFMApiQuery ($convertedParams + $apiParams)
    $apiUrl = "$baseUrl/?$query"

    try {
        $irm = Invoke-LFMApiUri -Uri $apiUrl

        foreach ($artist in $irm.Artists.Artist) {
            $artistInfo = [pscustomobject] @{
                'PSTypeName' = 'PowerLFM.Chart.TopArtists'
                'Artist' = $artist.Name
                'Id' = $artist.Mbid
                'Url' = [uri] $artist.Url
                'Listeners' = [int] $artist.Listeners
                'PlayCount' = [int] $artist.PlayCount
            }

            Write-Output $artistInfo
        }
    }
    catch {
        throw $_
    }
}

function Get-LFMChartTopTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Chart.TopTags')]
    param (
        [Parameter()]
        [ValidateRange(1, 119)]
        [int] $Limit,

        [int] $Page
    )

    $apiParams = @{
        'method' = 'chart.getTopTags'
        'api_key' = $script:LFMConfig.ApiKey
        'format' = 'json'
    }

    $noCommonParams = Remove-CommonParameter $PSBoundParameters
    $convertedParams = ConvertTo-LFMParameter $noCommonParams

    $query = New-LFMApiQuery ($convertedParams + $apiParams)
    $apiUrl = "$baseUrl/?$query"

    try {
        $irm = Invoke-LFMApiUri -Uri $apiUrl

        foreach ($tag in $irm.Tags.Tag) {
            $tagInfo = [pscustomobject] @{
                'PSTypeName' = 'PowerLFM.Chart.TopTags'
                'Tag' = ConvertTo-TitleCase -String $tag.Name
                'Url' = [uri] $tag.Url
                'Reach' = [int] $tag.Reach
                'TotalTags' = [int] $tag.Taggings
            }

            Write-Output $tagInfo
        }
    }
    catch {
        throw $_
    }
}

function Get-LFMChartTopTrack {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Chart.TopTracks')]
    param (
        [Parameter()]
        [ValidateRange(1, 119)]
        [int] $Limit,

        [int] $Page
    )

    $apiParams = @{
        'method' = 'chart.getTopTracks'
        'api_key' = $script:LFMConfig.ApiKey
        'format' = 'json'
    }

    $noCommonParams = Remove-CommonParameter $PSBoundParameters
    $convertedParams = ConvertTo-LFMParameter $noCommonParams

    $query = New-LFMApiQuery ($convertedParams + $apiParams)
    $apiUrl = "$baseUrl/?$query"

    try {
        $irm = Invoke-LFMApiUri -Uri $apiUrl

        foreach ($track in $irm.Tracks.Track) {
            $trackInfo = [pscustomobject] @{
                'PSTypeName' = 'PowerLFM.Chart.TopTracks'
                'Track' = $track.Name
                'TrackId' = $track.Mbid
                'TrackUrl' = [uri] $track.Url
                'Artist' = $track.Artist.Name
                'ArtistId' = $track.Artist.Mbid
                'ArtistUrl' = [uri] $track.Artist.Url
                'Duration' = [int] $track.Duration
                'Listeners' = [int] $track.Listeners
                'PlayCount' = [int] $track.PlayCount
            }

            Write-Output $trackInfo
        }
    }
    catch {
        throw $_
    }
}

function Get-LFMConfiguration {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    param ()

    try {
        $script:LFMConfig = [pscustomobject] @{
            'ApiKey' = Get-Secret -Name LFMApiKey -Vault BuiltInLocalVault -AsPlainText
            'SessionKey' = Get-Secret -Name LFMSessionKey -Vault BuiltInLocalVault -AsPlainText
            'SharedSecret' = Get-Secret -Name LFMSharedSecret -Vault BuiltInLocalVault -AsPlainText
        }
        Write-Verbose 'LFMConfig is loaded in to the session'
    }
    catch {
        throw $_
    }
}

function Get-LFMGeoTopArtist {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Geo.TopArtists')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Country,

        [Parameter()]
        [ValidateRange(1, 119)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'geo.getTopArtists'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($artist in $irm.TopArtists.Artist) {
                $artistInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Geo.TopArtists'
                    'Artist' = $artist.Name
                    'Id' = $artist.Mbid
                    'Url' = [uri] $artist.Url
                    'Listeners' = [int] $artist.Listeners
                }

                Write-Output $artistInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMGeoTopTrack {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Geo.TopTracks')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Country,

        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $City,

        [Parameter()]
        [ValidateRange(1, 119)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'geo.getTopTracks'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($track in $irm.Tracks.Track) {
                $trackInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Geo.TopTracks'
                    'Track' = $track.Name
                    'TrackId' = $track.Mbid
                    'TrackUrl' = [uri] $track.Url
                    'Artist' = $track.Artist.Name
                    'ArtistId' = $track.Artist.Mbid
                    'ArtistUrl' = [uri] $track.Artist.Url
                    'Rank' = [int] $track.'@attr'.Rank
                    'Listeners' = [int] $track.Listeners
                }

                Write-Output $trackInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMLibraryArtist {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Library.Artist')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'library.getArtists'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($artist in $irm.Artists.Artist) {
                $artistInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Library.Artist'
                    'Artist' = $artist.Name
                    'PlayCount' = [int] $artist.PlayCount
                    'Url' = [uri] $artist.Url
                    'Id' = $artist.Mbid
                }

                Write-Output $artistInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTagInfo {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Tag.Info')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Tag,

        [Parameter()]
        [string] $Language
    )

    begin {
        $apiParams = @{
            'method' = 'tag.getInfo'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $tagInfo = [pscustomobject] @{
                'PSTypeName' = 'PowerLFM.Tag.Info'
                'Tag' = $irm.Tag.Name
                'Url' = [uri] "http://www.last.fm/tag/$Tag" -replace ' ', '+'
                'Reach' = [int] $irm.Tag.Reach
                'TotalTags' = [int] $irm.Tag.Total
                'Summary' = $irm.Tag.Wiki.Summary
            }

            Write-Output $tagInfo
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTagSimilar {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Tag.Similar')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Tag
    )

    begin {
        $apiParams = @{
            'method' = 'tag.getSimilar'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $tagInfo = [pscustomobject] @{
                'PSTypeName' = 'PowerLFM.Tag.Similar'
                'Tag' = $irm.Tag.Name
                'Url' = [uri] $irm.Tag.Url
            }

            # This api method seems broken at the moment.
            # It does not return anything.
            Write-Output $tagInfo
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTagTopAlbum {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Tag.TopAlbums')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Tag,

        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'tag.getTopAlbums'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($album in $irm.Albums.Album) {
                $albumInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Tag.TopAlbums'
                    'Album' = $album.Name
                    'AlbumId' = $album.Mbid
                    'AlbumUrl' = [uri] $album.Url
                    'Artist' = $album.Artist.Name
                    'ArtistId' = $album.Artist.Mbid
                    'ArtistUrl' = [uri] $album.Artist.Url
                    'Rank' = [int] $album.'@attr'.Rank
                }

                Write-Output $albumInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTagTopArtist {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Tag.TopArtists')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Tag,

        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'tag.getTopArtists'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($artist in $irm.TopArtists.Artist) {
                $artistInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Tag.TopArtists'
                    'Artist' = $artist.Name
                    'ArtistId' = $artist.Mbid
                    'ArtistUrl' = [uri] $artist.Url
                    'Rank' = [int] $artist.'@attr'.Rank
                }

                Write-Output $artistInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTagTopTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Tag.TopTags')]
    param ()

    begin {
        $apiParams = @{
            'method' = 'tag.getTopTags'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($tag in $irm.TopTags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Tag.TopTags'
                    'Tag' = $tag.Name
                    'Count' = [int] $tag.Count
                    'Reach' = [int] $tag.Reach
                }

                Write-Output $tagInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTagTopTrack {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Tag.TopTracks')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Tag,

        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'tag.getTopTracks'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($track in $irm.Tracks.Track) {
                $trackInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Tag.TopTracks'
                    'Track' = $track.Name
                    'TrackId' = $track.Mbid
                    'TrackUrl' = [uri] $track.Url
                    'Artist' = $track.Artist.Name
                    'ArtistId' = $track.Artist.Mbid
                    'ArtistUrl' = [uri] $track.Artist.Url
                    'Rank' = [int] $track.'@attr'.Rank
                    'Duration' = [int] $track.Duration
                }

                Write-Output $trackInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTagWeeklyChartList {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Tag.WeeklyChartList')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Tag
    )

    begin {
        $apiParams = @{
            'method' = 'tag.getWeeklyChartList'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $chartList = $irm.WeeklyChartList.Chart.GetEnumerator() |
                Sort-Object {$_.From} -Descending

            foreach ($chart in $chartList) {
                $chartInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Tag.WeeklyChartList'
                    'StartDate' = $chart.From | ConvertFrom-UnixTime -Local
                    'EndDate' = $chart.To | ConvertFrom-UnixTime -Local
                }

                Write-Output $chartInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTrackCorrection {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Track.Correction')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist
    )

    begin {
        $apiParams = @{
            'method' = 'track.getCorrection'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $correction = $irm.Corrections.Correction.Track
            $correctedTrackInfo = [pscustomobject] @{
                'PSTypeName' = 'PowerLFM.Track.Correction'
                'Track' = $correction.Name
                'TrackUrl' = [uri] $correction.Url
                'Artist' = $correction.Artist.Name
                'ArtistUrl' = [uri] $correction.Artist.Url
                'ArtistId' = $correction.Artist.Mbid
            }

            Write-Output $correctedTrackInfo
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTrackInfo {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'track')]
    [OutputType('PowerLFM.Track.Info')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'track')]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 1,
                   ParameterSetName = 'track')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [string] $UserName,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'track.getInfo'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $tags = foreach ($tag in $irm.Track.TopTags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Album.Tag'
                    'Tag' = $tag.Name
                    'Url' = [uri] $tag.Url
                }
                Write-Output $tagInfo
            }

            switch ($irm.Track.UserLoved) {
                '0' {$loved = 'No'}
                '1' {$loved = 'Yes'}
            }

            $trackInfo = @{
                'PSTypeName' = 'PowerLFM.Album.Info'
                'Track' = $irm.Track.Name
                'Artist' = $irm.Track.Artist.Name
                'Album' = $irm.Track.Album.Title
                'Id' = $irm.Track.Mbid
                'Listeners' = [int] $irm.Track.Listeners
                'PlayCount' = [int] $irm.Track.PlayCount
                'Url' = [uri] $irm.Track.Url
                'Tags' = $tags
            }

            $userPlayCount = [int] $irm.Track.UserPlayCount
            if ($PSBoundParameters.ContainsKey('UserName')) {
                $trackInfo.Add('UserPlayCount', $userPlayCount)
                $trackInfo.Add('Loved', $loved)
            }

            $trackInfo = [pscustomobject] $trackInfo
            Write-Output $trackInfo
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTrackSimilar {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'track')]
    [OutputType('PowerLFM.Track.Similar')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'track')]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 1,
                   ParameterSetName = 'track')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [int] $Limit = '5',

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'track.getSimilar'
            'api_key' = $script:LFMConfig.ApiKey
            'limit' = $Limit
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($similar in $irm.SimilarTracks.Track) {
                $similarInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Track.Similar'
                    'Track' = $similar.Name
                    'Artist' = $similar.Artist.Name
                    'Id' = $similar.Mbid
                    'PlayCount' = [int] $similar.PlayCount
                    'Url' = [uri] $similar.Url
                    'Match' = [math]::Round($similar.Match, 2)
                }

                Write-Output $similarInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTrackTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'track')]
    [OutputType('PowerLFM.Track.UserTag')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'track')]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 1,
                   ParameterSetName = 'track')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [string] $UserName,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'track.getTags'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($tag in $irm.Tags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Track.Tag'
                    'Tag' = $tag.Name
                    'Url' = [uri] $tag.Url
                }

                Write-Output $tagInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMTrackTopTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(DefaultParameterSetName = 'track')]
    [OutputType('PowerLFM.Track.TopTag')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 0,
                   ParameterSetName = 'track')]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   Position = 1,
                   ParameterSetName = 'track')]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'id')]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [switch] $AutoCorrect
    )

    begin {
        $apiParams = @{
            'method' = 'track.getTopTags'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($tag in $irm.TopTags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Track.Tag'
                    'Tag' = $tag.Name
                    'Url' = [uri] $tag.Url
                    'Match' = [int] $tag.Count
                }

                Write-Output $tagInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserFriend {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.Info')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'user.getFriends'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($friend in $irm.Friends.User) {
                $userInfo = @{
                    'PSTypeName' = 'PowerLFM.User.Info'
                    'UserName' = $friend.Name
                    'RealName' = $friend.RealName
                    'Url' = [uri] $friend.Url
                    'Country' = $friend.Country
                    'Registered' = ConvertFrom-UnixTime -UnixTime $friend.Registered.UnixTime -Local
                    'PlayLists' = [int] $friend.PlayLists
                }

                $userInfo = [pscustomobject] $userInfo
                Write-Output $userInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserInfo {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.Info')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName
    )

    begin {
        $apiParams = @{
            'method' = 'user.GetInfo'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $userInfo = [pscustomobject] @{
                'PSTypeName' = 'PowerLFM.User.Info'
                'UserName' = $irm.User.Name
                'RealName' = $irm.User.RealName
                'Url' = [uri] $irm.User.Url
                'Country' = $irm.User.Country
                'Registered' = ConvertFrom-UnixTime -UnixTime $irm.User.Registered.UnixTime -Local
                'PlayCount' = [int] $irm.User.PlayCount
                'PlayLists' = [int] $irm.User.PlayLists
            }

            Write-Output $userInfo
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserLovedTrack {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.Track')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'user.getLovedTracks'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($track in $irm.LovedTracks.Track) {
                $trackInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.Track'
                    'Track' = $track.Name
                    'TrackUrl' = [uri] $track.Url
                    'TrackId' = $track.Mbid
                    'Artist' = $track.Artist.Name
                    'ArtistUrl' = [uri] $track.Artist.Url
                    'ArtistId' = $track.Artist.Mbid
                    'Date' = ConvertFrom-UnixTime -UnixTime $track.Date.Uts -Local
                }

                Write-Output $trackInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserPersonalTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.PersonalTag')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Tag,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('Artist', 'Album', 'Track')]
        [string] $TagType,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'user.getPersonalTags'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($userTag in $irm.Taggings.Artists.Artist) {
                $userTagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.PersonalTag'
                    'Artist' = $userTag.Name
                    'Id' = $userTag.Mbid
                    'Url' = [uri] $userTag.Url
                }

                Write-Output $userTagInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserRecentTrack {
    [CmdletBinding()]
    [OutputType('PowerLFM.User.RecentTrack')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [datetime] $StartDate,

        [Parameter(ValueFromPipelineByPropertyName)]
        [datetime] $EndDate,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'user.getRecentTracks'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'extended' = 1
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $i = 0
            foreach ($track in $irm.RecentTracks.Track) {
                switch ($track.Loved) {
                    '0' {$loved = 'No'}
                    '1' {$loved = 'Yes'}
                }

                $trackInfo = @{
                    'PSTypeName' = 'PowerLFM.User.RecentTrack'
                    'Track' = $track.Name
                    'Artist' = $track.Artist.Name
                    'Album' = $track.Album.'#text'
                    'Loved' = $loved
                }

                $scrobbleTime = ConvertFrom-UnixTime -UnixTime $track.Date.Uts -Local
                switch ($track.'@attr'.NowPlaying) {
                    $true {$trackInfo.Add('ScrobbleTime', 'Now Playing')}
                    $null {$trackInfo.Add('ScrobbleTime', $scrobbleTime)}
                }

                # This prevents a track that is currently playing from being displayed when
                # an end date is specified because the tracks should only be in the past.
                if ($PSBoundParameters.ContainsKey('EndDate') -and $track.'@attr'.NowPlaying -eq 'true') {
                    $trackInfo = $trackInfo[1]
                }

                # This prevents more tracks in the output than specified with the limit
                # parameter when a track is currently playing. Previously, if the limit
                # was set to two there would be three objects in the output including
                # the currently playing track.
                if ($irm.RecentTracks.Track[0].'@attr'.NowPlaying -and
                    $PSBoundParameters.ContainsKey('Limit') -and
                    $Limit -eq $i) {
                    break
                }
                $i++

                $trackInfo = [pscustomobject] $trackInfo
                Write-Output $trackInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserTopAlbum {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.TopAlbum')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [Parameter()]
        [ValidateSet('Overall', '7 Days', '1 Month',
                     '3 Months', '6 Months', '1 Year')]
        [string] $TimePeriod,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'user.getTopAlbums'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($album in $irm.TopAlbums.Album) {
                $albumInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.Album'
                    'Album' = $album.Name
                    'PlayCount' = [int] $album.PlayCount
                    'AlbumUrl' = [uri] $album.Url
                    'AlbumId' = $album.Mbid
                    'Artist' = $album.Artist.Name
                    'ArtistUrl' = [uri] $album.Artist.Url
                    'ArtistId' = $album.Artist.Mbid
                }

                Write-Output $albumInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserTopArtist {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.TopArtist')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [Parameter()]
        [ValidateSet('Overall', '7 Days', '1 Month',
                     '3 Months', '6 Months', '1 Year')]
        [string] $TimePeriod,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'user.getTopArtists'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($artist in $irm.TopArtists.Artist) {
                $artistInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.Artist'
                    'Artist' = $artist.Name
                    'PlayCount' = [int] $artist.PlayCount
                    'Url' = [uri] $artist.Url
                    'Id' = $artist.Mbid
                }

                Write-Output $artistInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserTopTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.TopTag')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [int] $Limit
    )

    begin {
        $apiParams = @{
            'method' = 'user.getTopTags'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($tag in $irm.TopTags.Tag) {
                $tagInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.TopTag'
                    'Tag' = $tag.Name
                    'TagUrl' = [uri] $tag.Url
                    'Count' = [int] $tag.Count
                }

                Write-Output $tagInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserTopTrack {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.TopTrack')]
    param (
        [Parameter()]
        [ValidateSet('Overall', '7 Days', '1 Month',
                     '3 Months', '6 Months', '1 Year')]
        [string] $TimePeriod,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'user.getTopTracks'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($track in $irm.TopTracks.Track) {
                $trackInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.TopTrack'
                    'Track' = $track.Name
                    'PlayCount' = [int] $track.PlayCount
                    'TrackUrl' = [uri] $track.Url
                    'TrackId' = $track.Mbid
                    'Artist' = $track.Artist.Name
                    'ArtistUrl' = [uri] $track.Artist.Url
                    'ArtistId' = $track.Artist.Mbid
                }

                Write-Output $trackInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserTrackScrobble {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.TrackScrobble')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'user.getTrackScrobbles'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($scrobble in $irm.TrackScrobbles.Track) {
                $scrobbleInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.TrackScrobble'
                    'Track' = $scrobble.Name
                    'TrackId' = $scrobble.Mbid
                    'TrackUrl' = $scrobble.Url
                    'Artist' = $scrobble.Artist.'#text'
                    'Album' = $scrobble.Album.'#text'
                    'Date' = ConvertFrom-UnixTime -UnixTime $scrobble.Date.Uts -Local
                }

                Write-Output $scrobbleInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserWeeklyAlbumChart {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.WeeklyAlbumChart')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [datetime] $StartDate,

        [Parameter(ValueFromPipelineByPropertyName)]
        [datetime] $EndDate,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName
    )

    begin {
        $apiParams = @{
            'method' = 'user.getWeeklyAlbumChart'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($album in $irm.WeeklyAlbumChart.Album) {
                $albumInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.WeeklyChartList'
                    'Album' = $album.Name
                    'Url' = [uri] $album.Url
                    'Id' = $album.Mbid
                    'Artist' = $album.Artist.'#text'
                    'ArtistId' = $album.Artist.Mbid
                    'PlayCount' = [int] $album.PlayCount
                    'StartDate' = ConvertFrom-UnixTime -UnixTime $irm.WeeklyAlbumChart.'@attr'.From -Local
                    'EndDate' = ConvertFrom-UnixTime -UnixTime $irm.WeeklyAlbumChart.'@attr'.To -Local
                }

                Write-Output $albumInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserWeeklyArtistChart {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.WeeklyArtistChart')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [datetime] $StartDate,

        [Parameter(ValueFromPipelineByPropertyName)]
        [datetime] $EndDate,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName
    )

    begin {
        $apiParams = @{
            'method' = 'user.getWeeklyArtistChart'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($artist in $irm.WeeklyArtistChart.Artist) {
                $artistInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.WeeklyArtistChart'
                    'Artist' = $artist.Name
                    'Url' = [uri] $artist.Url
                    'Id' = $artist.Mbid
                    'PlayCount' = [int] $artist.PlayCount
                    'StartDate' = ConvertFrom-UnixTime -UnixTime $irm.WeeklyArtistChart.'@attr'.From -Local
                    'EndDate' = ConvertFrom-UnixTime -UnixTime $irm.WeeklyArtistChart.'@attr'.To -Local
                }

                Write-Output $artistInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserWeeklyChartList {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.WeeklyChartList')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName
    )

    begin {
        $apiParams = @{
            'method' = 'user.getWeeklyChartList'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $chartList = $irm.WeeklyChartList.Chart |
                Sort-Object -Property From -Descending

            foreach ($chart in $chartList) {
                $chartInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.WeeklyChartList'
                    'StartDate' = $chart.From | ConvertFrom-UnixTime -Local
                    'EndDate' = $chart.To | ConvertFrom-UnixTime -Local
                }

                Write-Output $chartInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Get-LFMUserWeeklyTrackChart {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.User.WeeklyTrackChart')]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [datetime] $StartDate,

        [Parameter(ValueFromPipelineByPropertyName)]
        [datetime] $EndDate,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $UserName
    )

    begin {
        $apiParams = @{
            'method' = 'user.getWeeklyTrackChart'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($track in $irm.WeeklyTrackChart.Track) {
                $trackInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.User.WeeklyTrackChart'
                    'Track' = $track.Name
                    'Url' = [uri] $track.Url
                    'Id' = $track.Mbid
                    'Artist' = $track.Artist.'#text'
                    'ArtistId' = $track.Artist.Mbid
                    'PlayCount' = [int] $track.PlayCount
                    'StartDate' = ConvertFrom-UnixTime -UnixTime $irm.WeeklyTrackChart.'@attr'.From -Local
                    'EndDate' = ConvertFrom-UnixTime -UnixTime $irm.WeeklyTrackChart.'@attr'.To -Local
                }

                Write-Output $trackInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Remove-LFMAlbumTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Album,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Tag
    )

    begin {
        $apiParams = @{
            'method' = 'album.removeTag'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $convertedParams = ConvertTo-LFMParameter $noCommonParams
        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        if ($PSCmdlet.ShouldProcess("Album: $Album", "Removing album tag: $Tag")) {
            try {
                $irm = Invoke-LFMApiUri -Uri $apiUrl -Method Post
                if ($irm.Lfm.Status -eq 'ok') {Write-Verbose "Tag: $Tag has been removed"}
            }
            catch {
                throw $_
            }
        }
    }
}

function Remove-LFMArtistTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Tag
    )

    begin {
        $apiParams = @{
            'method' = 'artist.removeTag'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $convertedParams = ConvertTo-LFMParameter $noCommonParams
        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        if ($PSCmdlet.ShouldProcess("Artist: $Artist", "Removing artist tag: $Tag")) {
            try {
                $irm = Invoke-LFMApiUri -Uri $apiUrl -Method Post
                if ($irm.Lfm.Status -eq 'ok') {Write-Verbose "Tag: $Tag has been removed"}
            }
            catch {
                throw $_
            }
        }
    }
}

function Remove-LFMConfiguration {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'High')]
    param ()

    process {
        try {
            $secrets = Get-SecretInfo -Name LFM* -Vault BuiltInLocalVault

            foreach ($secret in $secrets) {
                if ($PSCmdlet.ShouldProcess('BuiltInLocalVault', "Removing secret: $($secret.Name)")) {
                    Remove-Secret -Name $secret.Name -Vault BuiltInLocalVault -Verbose
                }
            }
        } catch {
            throw $_
        }
    }
}

function Remove-LFMTrackTag {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Tag
    )

    begin {
        $apiParams = @{
            'method' = 'track.removeTag'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $convertedParams = ConvertTo-LFMParameter $noCommonParams
        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        if ($PSCmdlet.ShouldProcess("Track: $Track", "Removing track tag: $Tag")) {
            try {
                $irm = Invoke-LFMApiUri -Uri $apiUrl -Method Post
                if ($irm.Lfm.Status -eq 'ok') {Write-Verbose "Tag: $Tag has been removed"}
            }
            catch {
                throw $_
            }
        }
    }
}

function Request-LFMSession {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('System.String')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $ApiKey,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Token,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $SharedSecret
    )

    process {
        $apiParams = @{
            'method' = 'auth.getSession'
            'api_key' = $ApiKey
            'token' = $Token
            'format' = 'json'
        }

        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"

        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            $obj = [pscustomobject] @{
                'ApiKey' = $ApiKey
                'SessionKey' = $irm.Session.Key
                'SharedSecret' = $SharedSecret
            }
            Write-Output $obj
        }
        catch {
            throw $_
        }
    }
}

function Request-LFMToken {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('System.String')]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $ApiKey,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $SharedSecret
    )

    $apiParams = @{
        'method' = 'auth.getToken'
        'api_key' = $ApiKey
        'format' = 'json'
    }

    $noCommonParams = Remove-CommonParameter $PSBoundParameters
    $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
    $apiParams.Add('api_sig', $apiSig)

    $query = New-LFMApiQuery $apiParams
    $apiUrl = "$baseUrl/?$query"

    try {
        Write-Verbose "Requesting token from $baseUrl"
        $token = (Invoke-LFMApiUri -Uri $apiUrl).Token

        Write-Verbose "Authorizing application with requested token on account"
        Show-LFMAuthWindow -Url "http://www.last.fm/api/auth/?api_key=$ApiKey&token=$token"

        $obj = [pscustomobject] @{
            'ApiKey' = $ApiKey
            'Token' = $token
            'SharedSecret' = $SharedSecret
        }
        Write-Output $obj
    } catch {
        throw $_
    }
}

function Search-LFMAlbum {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Album.Search')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Album,

        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'album.search'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($match in $irm.Results.AlbumMatches.Album) {
                $matchInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Album.Search'
                    'Album' = $match.Name
                    'Artist' = $match.Artist
                    'Id' = $match.Mbid
                    'Url' = [uri] $match.Url
                }

                Write-Output $matchInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Search-LFMArtist {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Artist.Search')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter()]
        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'artist.search'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($match in $irm.Results.ArtistMatches.Artist) {
                $matchInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Artist.Search'
                    'Artist' = $match.Name
                    'Id' = $match.Mbid
                    'Listeners' = [int] $match.Listeners
                    'Url' = [uri] $match.Url
                }

                Write-Output $matchInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Search-LFMTrack {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding()]
    [OutputType('PowerLFM.Track.Search')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [ValidateRange(1, 50)]
        [int] $Limit,

        [int] $Page
    )

    begin {
        $apiParams = @{
            'method' = 'track.search'
            'api_key' = $script:LFMConfig.ApiKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $convertedParams = ConvertTo-LFMParameter $noCommonParams

        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        try {
            $irm = Invoke-LFMApiUri -Uri $apiUrl

            foreach ($match in $irm.Results.TrackMatches.Track) {
                $matchInfo = [pscustomobject] @{
                    'PSTypeName' = 'PowerLFM.Track.Search'
                    'Track' = $match.Name
                    'Artist' = $match.Artist
                    'Id' = $match.Mbid
                    'Listeners' = [int] $match.Listeners
                    'Url' = [uri] $match.Url
                }

                Write-Output $matchInfo
            }
        }
        catch {
            throw $_
        }
    }
}

function Set-LFMTrackLove {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Track
    )

    begin {
        $apiParams = @{
            'method' = 'track.love'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $convertedParams = ConvertTo-LFMParameter $noCommonParams
        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        if ($PSCmdlet.ShouldProcess("Track: $Track", "Adding love")) {
            try {
                $irm = Invoke-LFMApiUri -Uri $apiUrl -Method Post
                if ($irm.Lfm.Status -eq 'ok') {Write-Verbose "Track: $Track has been loved"}
            }
            catch {
                throw $_
            }
        }
    }
}

function Set-LFMTrackNowPlaying {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'Medium')]
    [OutputType('PowerLFM.Track.NowPlaying')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Album,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [int] $Duration,

        [Parameter()]
        [switch] $PassThru
    )

    begin {
        $apiParams = @{
            'method' = 'track.updateNowPlaying'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $convertedParams = ConvertTo-LFMParameter $noCommonParams
        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        if ($PSCmdlet.ShouldProcess("Track: $Track", "Setting track to now playing")) {
            try {
                $irm = Invoke-LFMApiUri -Uri $apiUrl -Method Post

                $code = Get-LFMIgnoredMessage -Code $irm.NowPlaying.IgnoredMessage.Code
                if ($code.Code -ne 0) {
                    throw "Request has been filtered because of bad meta data. $($code.Message)."
                }

                if ($PassThru) {
                    [pscustomobject] @{
                        PSTypeName = 'PowerLFM.Track.NowPlaying'
                        Artist = $irm.NowPlaying.Artist.'#text'
                        Album = $irm.NowPlaying.Album.'#text'
                        Track = $irm.NowPlaying.Track.'#text'
                    }
                }
            }
            catch {
                throw $_
            }
        }
    }
}

function Set-LFMTrackScrobble {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'Medium')]
    [OutputType('PowerLFM.Track.Scrobble')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Track,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [datetime] $Timestamp,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Album,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [guid] $Id,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [int] $TrackNumber,

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [int] $Duration,

        [Parameter()]
        [switch] $PassThru
    )

    begin {
        $apiParams = @{
            'method' = 'track.scrobble'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
            'format' = 'json'
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $convertedParams = ConvertTo-LFMParameter $noCommonParams
        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        if ($PSCmdlet.ShouldProcess("Track: $Track", "Setting track to now playing")) {
            try {
                $irm = Invoke-LFMApiUri -Uri $apiUrl -Method Post

                $code = Get-LFMIgnoredMessage -Code $irm.Scrobbles.Scrobble.IgnoredMessage.Code
                if ($code.Code -ne 0) {
                    throw "Request has been filtered because of bad meta data. $($code.Message)."
                }

                if ($PassThru) {
                    [pscustomobject] @{
                        PSTypeName = 'PowerLFM.Track.Scrobble'
                        Artist = $irm.Scrobbles.Scrobble.Artist.'#text'
                        Album = $irm.Scrobbles.Scrobble.Album.'#text'
                        Track = $irm.Scrobbles.Scrobble.Track.'#text'
                    }
                }
            }
            catch {
                throw $_
            }
        }
    }
}

function Set-LFMTrackUnlove {
    # .ExternalHelp PowerLFM-help.xml

    [CmdletBinding(SupportsShouldProcess,
                   ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Artist,

        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string] $Track
    )

    begin {
        $apiParams = @{
            'method' = 'track.unlove'
            'api_key' = $script:LFMConfig.ApiKey
            'sk' = $script:LFMConfig.SessionKey
        }
    }
    process {
        $noCommonParams = Remove-CommonParameter $PSBoundParameters
        $apiSig = Get-LFMSignature -Method $apiParams.Method @noCommonParams
        $apiParams.Add('api_sig', $apiSig)

        $convertedParams = ConvertTo-LFMParameter $noCommonParams
        $query = New-LFMApiQuery ($convertedParams + $apiParams)
        $apiUrl = "$baseUrl/?$query"
    }
    end {
        if ($PSCmdlet.ShouldProcess("Track: $Track", "Removing love")) {
            try {
                $irm = Invoke-LFMApiUri -Uri $apiUrl -Method Post
                if ($irm.Lfm.Status -eq 'ok') {Write-Verbose "Track: $Track has been unloved"}
            }
            catch {
                throw $_
            }
        }
    }
}

function ConvertFrom-UnixTime {
    [CmdletBinding()]
    [OutputType('System.DateTime')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline)]
        [double] $UnixTime,

        [switch] $Local
    )

    begin {
        [datetime] $epoch = '1/1/1970 00:00:00'
        $utcOffset = Get-Date -UFormat %Z
    }
    process {
        $time = $epoch.AddSeconds($UnixTime)

        if ($PSBoundParameters.ContainsKey('Local')) {
            Write-Output $time.AddHours($utcOffset)
        }
        else {
            Write-Output $time
        }
    }
}

function ConvertTo-LFMParameter {
    param (
        [psobject] $InputObject
    )

    $lfmParameter = @{
        'Album'       = 'album'
        'Artist'      = 'artist'
        'AutoCorrect' = 'autocorrect'
        'City'        = 'location'
        'Country'     = 'country'
        'Duration'    = 'duration'
        'EndDate'     = 'to'
        'Id'          = 'mbid'
        'Language'    = 'lang'
        'Limit'       = 'limit'
        'Page'        = 'page'
        'StartDate'   = 'from'
        'Tag'         = 'tag'
        'TagType'     = 'taggingtype'
        'TimePeriod'  = 'period'
        'Timestamp'   = 'timestamp'
        'Track'       = 'track'
        'UserName'    = 'user'
        'Token'       = 'token'
        'ApiKey'      = 'api_key'
    }

    $period = @{
        'Overall' = 'overall'
        '7 Days' = '7day'
        '1 Month' = '1month'
        '3 Months' = '3month'
        '6 Months' = '6month'
        '1 Year' = '12month'
    }

    $callingCommand = (Get-PSCallStack)[-2].Command
    if ($callingCommand -like 'Get-LFM*Info') { $lfmParameter['Username'] = 'username' }
    if ($callingCommand -like 'Add-LFM*Tag') { $lfmParameter['Tag'] = 'tags' }

    if ($InputObject.ContainsKey('Timestamp')) { $InputObject['Timestamp'] = (ConvertTo-UnixTime -Date $Timestamp) }
    if ($InputObject.ContainsKey('StartDate')) { $InputObject['StartDate'] = (ConvertTo-UnixTime -Date $StartDate) }
    if ($InputObject.ContainsKey('EndDate')) { $InputObject['EndDate'] = (ConvertTo-UnixTime -Date $EndDate) }
    if ($InputObject.ContainsKey('TimePeriod')) { $InputObject['TimePeriod'] = $period[$InputObject['TimePeriod']] }

    $null = if ($InputObject.ContainsKey('Method')) { $InputObject.Remove('Method') }
    $null = If ($InputObject.ContainsKey('SharedSecret')) { $InputObject.Remove('SharedSecret') }
    $null = If ($InputObject.ContainsKey('PassThru')) { $InputObject.Remove('PassThru') }

    $hash = @{ }
    foreach ($key in $InputObject.Keys) {
        $hash.Add($lfmParameter[$key], $InputObject[$key])
    }

    Write-Output $hash
}

function ConvertTo-TitleCase {
    [CmdletBinding()]
    [OutputType('System.String')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline)]
        [string[]] $String
    )

    process {
        (Get-Culture).TextInfo.ToTitleCase($String)
    }
}

function ConvertTo-UnixTime {
    [CmdletBinding()]
    [OutputType('System.TimeSpan')]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline)]
        [datetime] $Date
    )

    begin {
        [datetime] $epoch = '1/1/1970 00:00:00'
    }
    process {
        if ($Date.Kind -eq 'Local') {
            $Date = $Date.ToUniversalTime()
        }

        (New-TimeSpan -Start $epoch -End $Date).TotalSeconds
    }
}

function Get-LFMIgnoredMessage {
    param (
        [int] $Code
    )

    $ignoredCode = @{
        0 = 'None (the request passed all filters)'
        1 = 'Filtered artist'
        2 = 'Filtered track'
        3 = 'Timestamp too far in the past'
        4 = 'Timestamp too far in the future'
        5 = 'Max daily scrobbles exceeded'
    }

    [pscustomobject] @{
        Code = $Code
        Message = $ignoredCode[$Code]
    }
}

function Get-LFMSignature {
    [OutputType('System.String')]
    param (
        [Parameter(Mandatory)]
        [ValidateSet('album.addTags','album.removeTag',
                     'artist.addTags','artist.removeTag',
                     'auth.getToken','auth.getSession',
                     'track.addTags','track.removeTag',
                     'track.love','track.unlove',
                     'track.updateNowPlaying', 'track.scrobble')]
        [string] $Method,

        [string] $Album,
        [string] $Artist,
        [string[]] $Tag,
        [string] $Track,
        [datetime] $Timestamp,
        [int] $TrackNumber,
        [int] $Duration,
        [guid] $Id,
        [switch] $Passthru,
        [string] $ApiKey,
        [string] $SharedSecret,
        [string] $Token
    )

    $sigParams = @{
        'method' = $Method
        'api_key' = $script:LFMConfig.ApiKey
        'sk' = $script:LFMConfig.SessionKey
    }

    $convertedParams = ConvertTo-LFMParameter $PSBoundParameters

    if ($PSBoundParameters.ContainsKey('ApiKey')) {
        $sigParams.Remove('api_key')
        $sigParams.Remove('sk')

        $query = New-LFMApiQuery ($convertedParams + $sigParams)

        Get-Md5Hash -String "$query$($SharedSecret)"
        Write-Verbose "$query$($SharedSecret)"
    }
    else {
        $query = New-LFMApiQuery ($convertedParams + $sigParams)

        Get-Md5Hash -String "$query$($script:LFMConfig.SharedSecret)"
        Write-Verbose "$query$($script:LFMConfig.SharedSecret)"
    }
}

function Get-Md5Hash {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string] $String
    )

    $md5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
    $utf8 = New-Object -TypeName System.Text.UTF8Encoding

    $hash = ([System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($String)))) -replace '-'

    Write-Output $hash
}

function Invoke-LFMApiUri {
    [CmdletBinding()]
    param (
        [uri] $Uri,

        [ValidateSet('Get', 'Post')]
        [string] $Method = 'Get'
    )

    try {
        # If Invoke-RestMethod returns, say HTTP 403, it will cause a terminating
        # error and drop down in to the catch block. In most other cases if a bad
        # request is made it will actually return a JSON object with the error.
        $irm = Invoke-RestMethod -Method $Method -Uri $Uri -ErrorAction Stop

        # Api call could return an object with an error code as shown below. This
        # will account for that and throw the object down to the catch block.
        # error message links
        # ----- ------- -----
        # 6 Album not found {}
        if ($irm.Error) {
            throw $irm
        }

        Write-Output $irm
    }
    catch {
        $response = if ($null -ne $_.ErrorDetails) {
            if ($_.ErrorDetails.Message | Test-Json) {
                $_.ErrorDetails.Message | ConvertFrom-Json
            }
            else {
                $_.ErrorDetails.Message
            }
            $errorId = 'PowerLFM.WebResponseException'
            $errorCategory = 'InvalidOperation'
        }
        else {
            $_.TargetObject
            $errorId = 'PowerLFM.ResourceNotFound'
            $errorCategory = 'ObjectNotFound'
        }

        $messagePart1 = '. Run Get-LFMConfiguration if token and session keys have already been requested.'
        $messagePart2 = 'This is not a valid request.'

        # Constructing error message from response object.
        # Capitalizing first letter in sentence.
        $responseMessage = [char]::ToUpper($response.Message[0]) + $response.Message.Substring(1)

        if ($responseMessage -like 'Invalid API key*') {$errorMessage = $responseMessage + $messagePart1}
        else {$errorMessage = "$messagePart2 $responseMessage."}

        $PSCmdlet.ThrowTerminatingError([ErrorRecord]::new(
            $errorMessage,
            $errorId,
            $errorCategory,
            $MyInvocation.MyCommand.Name
        ))
    }
}

function New-LFMApiQuery {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]

    param (
        [psobject] $InputObject
    )

    if ((Get-PSCallStack)[1].Command -like '*Signature') {
        $keyValues = $InputObject.GetEnumerator() | Sort-Object Key | ForEach-Object {
            "$($_.Key)$($_.Value)"
        }

        $query = $keyValues -join ''
    }
    else {
        $keyValues = $InputObject.GetEnumerator() | ForEach-Object {
            "$($_.Key)=$($_.Value)"
        }

        $query = $keyValues -join '&'
    }

    Write-Output $query
}

function Remove-CommonParameter {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]

    param (
        [psobject] $InputObject
    )

    $commonParams = [System.Management.Automation.PSCmdlet]::CommonParameters +
                    [System.Management.Automation.PSCmdlet]::OptionalCommonParameters

    $keysToRemove = $InputObject.Keys.Where({ $_ -in $commonParams })
    $null = $keysToRemove | ForEach-Object { $InputObject.Remove($_) }

    Write-Output $InputObject
}

function Show-LFMAuthWindow {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [uri] $Url
    )

    Add-Type -AssemblyName System.Windows.Forms

    $form = New-Object -TypeName System.Windows.Forms.Form -Property @{
        Width = 516
        Height = 638
        StartPosition = 1
        FormBorderStyle = 'Fixed3D'
        MinimizeBox = $false
        MaximizeBox = $false
        ShowIcon = $false
    }

    $web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{
        Width = 500
        Height = 600
        ScrollBarsEnabled = $false
        Url = $Url
    }

    $docComp = {
        $uri = $web.Url.AbsoluteUri
        if ($uri -cmatch '/None') {
            $form.Close()
        }
    }

    $web.ScriptErrorsSuppressed = $true
    $web.Add_DocumentCompleted($docComp)

    $form.Controls.Add($web)
    $form.Activate()
    $null = $form.ShowDialog()
}

function Test-Json {
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline)]
        [string] $Json
    )

    process {
        $result = $true

        try {
            $null = [Newtonsoft.Json.Linq.JObject]::Parse($Json)
        }
        catch {
            $result = $false
        }

        Write-Output $result
    }
}