Write-ShellMenu.psm1

function Write-ShellMenu {
    <#
        .SYNOPSIS
             Writes an arrow-key driven menu to the console.
         
        .DESCRIPTION
            Writes an arrow-key driven, Command-Line Interface style Menu to the Console. Includes options for configuring Menu style and formatting.
 
        .PARAMETER MenuHeader
            Specifies a header to be displayed above the Menu Title and body. Intended primarily for branding purposes when Write-ShellMenu is used within the context of a Module or Script Application. Takes objects as input.
 
        .PARAMETER MenuTitle
            Specifies a text string to display above the Menu body, acting as a title, or short descriptor.
 
        .PARAMETER MenuItems
            Specifies an array of items to display as options.
 
        .PARAMETER FocusHighlightingStyle
            Specifies the Highlighting Style used for Menu Items when they are in focus. Valid Styles: HighlightForeground, HighlightBackground, NoHighlighting
 
        .PARAMETER FocusHighlightingColor
            Specifies the Highlighting Color used for Menu Items when they are in focus. Uses ANSI Escape Sequence Color Codes. Valid Colors: 0-255
 
        .PARAMETER FocusBorderStyle
            Specifies the type of Border characters to use as an additional visual cue for Menu Items when in focus. Border characters enclose the in-focus item on either side. Valid Border Styles: Brackets, CurlyBraces, Parentheses, Lines, NoFocusBorder
 
        .PARAMETER FocusPrefixStyle
            Specifies how the Focus Prefix Character should be displayed. Valid Prefix Styles: PrefixOnly, EnclosedPrefix, NoPrefix
 
        .PARAMETER FocusPrefix
            Specifies the Focus Prefix Character used. Default = ❯
 
        .EXAMPLE
            PS> Write-ShellMenu -MenuTitle 'Demo Menu' -MenuItems 'Option One', 'Option Two', 'Option Three', 'Exit' -FocusHighlightingStyle HighlightBackground -FocusHighlightingColor '122' -FocusBorderStyle 'Lines' -FocusPrefixStyle PrefixOnly -FocusPrefix '>'
 
        .NOTES
 
    #>


    [CmdletBinding()]
    param(
        [Parameter()]
        [object]$MenuHeader = $null,
        [Parameter()]
        [string]$MenuTitle = $null,
        [Parameter(Mandatory)]
        [array]$MenuItems,
        [Parameter()]
        [ValidateSet("HighlightForeground","HighlightBackground","NoHighlighting")]
        [string]$FocusHighlightingStyle = "HighlightForeground",
        [Parameter()]
        [string]$FocusHighlightingColor = '228',
        [Parameter()]
        [ValidateSet("Brackets","CurlyBraces","Parentheses","Lines","NoFocusBorder")]
        [string]$FocusBorderStyle = "Brackets",
        [Parameter()]
        [ValidateSet("PrefixOnly","EnclosedPrefix","NoPrefix")]
        [string]$FocusPrefixStyle = "EnclosedPrefix",
        [Parameter()]
        [string]$FocusPrefix = "❯"
    )

    Begin{

        #region Menu Style Settings and Setup

        Clear-Host

        [System.Console]::CursorVisible = $false

        $HighlightReset = "`e[0m"

        if ($FocusHighlightingStyle -eq "HighlightForeground") {
            $HighlightStyle = "`e[38;5;" + $FocusHighlightingColor + "m"
        } elseif ($FocusHighlightingStyle -eq "HighlightBackground") {
            $HighlightStyle = "`e[38;5;" + $FocusHighlightingColor + "m" + "`e[7m"
        } else {
            $HighlightStyle = ''
        }

        $PrefixHighlightStyle = "`e[38;5;" + $FocusHighlightingColor + "m"

        if ($FocusBorderStyle -eq "Brackets") {
            $LBorder = "["
            $RBorder = "]"
        } elseif ($FocusBorderStyle -eq "CurlyBraces") {
            $LBorder = "{"
            $RBorder = "}"
        } elseif ($FocusBorderStyle -eq "Parentheses") {
            $LBorder = "("
            $RBorder = ")"
        } elseif ($FocusBorderStyle -eq "Lines") {
            $LBorder = "|"
            $RBorder = "|"
        } elseif ($FocusBorderStyle -eq "NoFocusBorder") {
            $LBorder = ''
            $RBorder = ''
        }
        

        if ($FocusPrefixStyle -eq "PrefixOnly") {
            $InFocusPrefix = $PrefxHighlightStyle + $FocusPrefix + $HighlightReset
            $OutOfFocusPrefix = "_"
        } elseif ($FocusPrefixStyle -eq "EnclosedPrefix") {
            $InFocusPrefix = "[" + $PrefixHighlightStyle + $FocusPrefix + $HighlightReset + "]"
            $OutOfFocusPrefix = "[_]"
        } else {
            $InFocusPrefix = ''
            $OutOfFocusPrefix = ''
        }

        #endregion

        #region Display Header/Title if applicable

        if ($MenuHeader -ne $null) {
            Write-Output $MenuHeader    
            Write-Output ''
        }

        if ($MenuTitle -ne $null) {
            Write-Output $MenuTitle
        }

        $SelectedIndex = 0

        #endregion

    }

    Process{
        
        #region Display Menu Body

        While ($True) {
            for ($i = 0; $i -lt $MenuItems.Length; $i++) {
                $InFocusItem = $HighlightStyle + $LBorder + $MenuItems[$i] + $RBorder + $HighlightReset
                $OutOfFocusItem = $MenuItems[$i]
                if ($i -eq $SelectedIndex) {
                    Write-Host "$InFocusPrefix" -NoNewline
                    Write-Host " " -NoNewline
                    Write-Host "$InFocusItem" 
                } else {
                    Write-Host "$OutOfFocusPrefix" -NoNewline
                    Write-Host " " -NoNewline
                    Write-Host "$OutOfFocusItem"
                }
            }
            #endregion

            #region Read Keyboard Input via Host.UI.RawUI.ReadKey

            $VKey = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

            Switch($VKey.VirtualKeyCode) {
                38{
                    # Key: UP Arrow
                    $SelectedIndex = ($SelectedIndex - 1 + $MenuItems.Length) % $MenuItems.Length
                }

                40{
                    # Key: DOWN Arrow
                    $SelectedIndex = ($SelectedIndex + 1) % $MenuItems.Length
                }

                13{
                    # Key: ENTER
                    return $SelectedIndex
                }
            }
            #endregion

            #region Re-Draw Menu

            Clear-Host

            if ($MenuHeader -ne $null) {
                Write-Output $MenuHeader
                Write-Output ''
            }

            if ($MenuTitle -ne $null) {
                Write-Output $MenuTitle
            }

            #endregion
        }
    }
}