public/controls/New-UiTimePicker.ps1

function New-UiTimePicker {
    <#
    .SYNOPSIS
        Creates a time picker control for selecting hours and minutes.
    .DESCRIPTION
        Creates a labeled time picker with a dropdown popup containing scrollable hour/minute/AM-PM columns.
        Styled to match the DatePicker control with theme support.
    .PARAMETER Label
        Label text displayed above the time picker.
    .PARAMETER Variable
        Variable name to store the selected time value.
    .PARAMETER Default
        Initial time value. Can be a TimeSpan, DateTime, or string like "14:30" or "2:30 PM".
    .PARAMETER Use24Hour
        Use 24-hour format instead of 12-hour with AM/PM.
    .PARAMETER MinuteInterval
        Interval for minute selection (1, 5, 10, 15, 30). Default is 5.
    .PARAMETER FullWidth
        Stretches the control to fill available width instead of fixed sizing.
    .PARAMETER WPFProperties
        Hashtable of additional WPF properties to set on the control.
    .EXAMPLE
        New-UiTimePicker -Label "Start Time" -Variable "startTime" -Default "09:00"
    .EXAMPLE
        New-UiTimePicker -Label "Meeting Time" -Variable "meetingTime" -Use24Hour -MinuteInterval 15
    .EXAMPLE
        New-UiTimePicker -Label "Reminder" -Variable "reminder" -Default (Get-Date).TimeOfDay
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Label,

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

        [object]$Default,

        [switch]$Use24Hour,

        [ValidateSet(1, 5, 10, 15, 30)]
        [int]$MinuteInterval = 5,

        [switch]$FullWidth,

        [Parameter()]
        [hashtable]$WPFProperties
    )

    $session = Assert-UiSession -CallerName 'New-UiTimePicker'
    Write-Debug "Label='$Label', Variable='$Variable', Use24Hour=$Use24Hour"

    $colors  = Get-ThemeColors
    $parent  = $session.CurrentParent
    Write-Debug "Parent: $($parent.GetType().Name)"

    # Parse default time
    $defaultHour = 9
    $defaultMinute = 0
    $defaultAmPm = 'AM'

    if ($Default) {
        if ($Default -is [TimeSpan]) {
            $defaultHour = $Default.Hours
            $defaultMinute = $Default.Minutes
        }
        elseif ($Default -is [DateTime]) {
            $defaultHour = $Default.Hour
            $defaultMinute = $Default.Minute
        }
        elseif ($Default -is [string]) {
            try {
                $parsedTime = [DateTime]::Parse($Default)
                $defaultHour = $parsedTime.Hour
                $defaultMinute = $parsedTime.Minute
            }
            catch {
                Write-Verbose "Failed to parse time string '$Default': $_"
            }
        }

        # Round minute to nearest interval
        $defaultMinute = [Math]::Round($defaultMinute / $MinuteInterval) * $MinuteInterval
        if ($defaultMinute -ge 60) { $defaultMinute = 0 }
    }

    # Convert to 12-hour format for display if needed
    $display24Hour = $defaultHour
    if (!$Use24Hour) {
        if ($defaultHour -ge 12) {
            $defaultAmPm = 'PM'
            if ($defaultHour -gt 12) { $defaultHour -= 12 }
        }
        else {
            $defaultAmPm = 'AM'
            if ($defaultHour -eq 0) { $defaultHour = 12 }
        }
    }

    $outerStack = [System.Windows.Controls.StackPanel]@{
        Margin = [System.Windows.Thickness]::new(4, 4, 4, 8)
    }

    $labelBlock = [System.Windows.Controls.TextBlock]@{
        Text       = $Label
        FontSize   = 12
        Foreground = ConvertTo-UiBrush $colors.ControlFg
        Margin     = [System.Windows.Thickness]::new(0, 0, 0, 4)
        Tag        = 'ControlFgBrush'
    }
    [PsUi.ThemeEngine]::RegisterElement($labelBlock)
    [void]$outerStack.Children.Add($labelBlock)

    # Create the main button container (mimics DatePicker appearance)
    $pickerBorder = [System.Windows.Controls.Border]@{
        Background      = ConvertTo-UiBrush $colors.ControlBg
        BorderBrush     = ConvertTo-UiBrush $colors.Border
        BorderThickness = [System.Windows.Thickness]::new(1)
        CornerRadius    = [System.Windows.CornerRadius]::new(3)
        Height          = 30
        MinWidth        = 140
        Cursor          = [System.Windows.Input.Cursors]::Hand
        Tag             = 'TimePickerBorder'
    }
    [PsUi.ThemeEngine]::RegisterElement($pickerBorder)

    $pickerGrid = [System.Windows.Controls.Grid]::new()
    
    # Define columns: time text and dropdown arrow
    $col1 = [System.Windows.Controls.ColumnDefinition]::new()
    $col1.Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star)
    $col2 = [System.Windows.Controls.ColumnDefinition]::new()
    $col2.Width = [System.Windows.GridLength]::new(28, [System.Windows.GridUnitType]::Pixel)
    [void]$pickerGrid.ColumnDefinitions.Add($col1)
    [void]$pickerGrid.ColumnDefinitions.Add($col2)

    # Time display text
    $timeDisplayFormat = if ($Use24Hour) { "{0:00}:{1:00}" } else { "{0}:{1:00} {2}" }
    $initialDisplayText = if ($Use24Hour) { 
        [string]::Format("{0:00}:{1:00}", $display24Hour, $defaultMinute) 
    } else { 
        [string]::Format("{0}:{1:00} {2}", $defaultHour, $defaultMinute, $defaultAmPm) 
    }

    $timeText = [System.Windows.Controls.TextBlock]@{
        Text                = $initialDisplayText
        FontSize            = 12
        FontFamily          = [System.Windows.Media.FontFamily]::new('Segoe UI')
        Foreground          = ConvertTo-UiBrush $colors.ControlFg
        VerticalAlignment   = [System.Windows.VerticalAlignment]::Center
        Margin              = [System.Windows.Thickness]::new(8, 0, 0, 0)
        Tag                 = 'ControlFgBrush'
    }
    [PsUi.ThemeEngine]::RegisterElement($timeText)
    [System.Windows.Controls.Grid]::SetColumn($timeText, 0)
    [void]$pickerGrid.Children.Add($timeText)

    # Dropdown arrow button
    $arrowBorder = [System.Windows.Controls.Border]@{
        Background = ConvertTo-UiBrush $colors.ControlBg
        Width      = 28
        Tag        = 'TimePickerArrowBorder'
    }
    [PsUi.ThemeEngine]::RegisterElement($arrowBorder)
    $arrowText = [System.Windows.Controls.TextBlock]@{
        Text                = [PsUi.ModuleContext]::GetIcon('ChevronDown')
        FontFamily          = [System.Windows.Media.FontFamily]::new('Segoe MDL2 Assets')
        FontSize            = 10
        Foreground          = ConvertTo-UiBrush $colors.ControlFg
        HorizontalAlignment = [System.Windows.HorizontalAlignment]::Center
        VerticalAlignment   = [System.Windows.VerticalAlignment]::Center
        Tag                 = 'ControlFgBrush'
    }
    [PsUi.ThemeEngine]::RegisterElement($arrowText)
    $arrowBorder.Child = $arrowText
    [System.Windows.Controls.Grid]::SetColumn($arrowBorder, 1)
    [void]$pickerGrid.Children.Add($arrowBorder)

    $pickerBorder.Child = $pickerGrid

    $popup = [System.Windows.Controls.Primitives.Popup]@{
        PlacementTarget = $pickerBorder
        Placement       = [System.Windows.Controls.Primitives.PlacementMode]::Bottom
        StaysOpen       = $false
        AllowsTransparency = $true
    }

    # Popup content container
    $popupBorder = [System.Windows.Controls.Border]@{
        Background      = ConvertTo-UiBrush $colors.ControlBg
        BorderBrush     = ConvertTo-UiBrush $colors.Border
        BorderThickness = [System.Windows.Thickness]::new(1)
        CornerRadius    = [System.Windows.CornerRadius]::new(4)
        Padding         = [System.Windows.Thickness]::new(8)
        MinWidth        = 180
        Tag             = 'TimePickerPopupBorder'
    }

    # Add shadow effect
    try {
        $shadow = [System.Windows.Media.Effects.DropShadowEffect]@{
            BlurRadius  = 10
            ShadowDepth = 3
            Opacity     = 0.3
            Color       = [System.Windows.Media.Colors]::Black
        }
        $popupBorder.Effect = $shadow
    }
    catch { Write-Debug "Drop shadow failed: $_" }

    $popupStack = [System.Windows.Controls.StackPanel]::new()

    # Columns container
    $columnsPanel = [System.Windows.Controls.StackPanel]@{
        Orientation = [System.Windows.Controls.Orientation]::Horizontal
        HorizontalAlignment = [System.Windows.HorizontalAlignment]::Center
    }

    # Helper to create a scrollable column
    $createColumn = {
        param($items, $selectedValue, $width)
        
        $listBox = [System.Windows.Controls.ListBox]@{
            Width      = $width
            Height     = 120
            Margin     = [System.Windows.Thickness]::new(2)
            Background = ConvertTo-UiBrush $colors.ControlBg
            BorderThickness = [System.Windows.Thickness]::new(0)
            HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center
        }
        Set-ListBoxStyle -ListBox $listBox

        foreach ($item in $items) {
            $listItem = [System.Windows.Controls.ListBoxItem]@{
                Content             = $item
                HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center
                FontSize            = 14
                Padding             = [System.Windows.Thickness]::new(8, 4, 8, 4)
            }
            [void]$listBox.Items.Add($listItem)
            if ($item -eq $selectedValue) {
                $listBox.SelectedItem = $listItem
            }
        }
        
        return $listBox
    }

    # Hour column
    $hourItems = if ($Use24Hour) {
        0..23 | ForEach-Object { $_.ToString('00') }
    } else {
        1..12 | ForEach-Object { $_.ToString() }
    }
    $selectedHourStr = if ($Use24Hour) { $display24Hour.ToString('00') } else { $defaultHour.ToString() }
    $hourList = & $createColumn $hourItems $selectedHourStr 50

    # Colon separator
    $colonLabel = [System.Windows.Controls.TextBlock]@{
        Text              = ':'
        FontSize          = 18
        FontWeight        = [System.Windows.FontWeights]::Bold
        Foreground        = ConvertTo-UiBrush $colors.ControlFg
        VerticalAlignment = [System.Windows.VerticalAlignment]::Center
        Margin            = [System.Windows.Thickness]::new(4, 0, 4, 0)
        Tag               = 'TimePickerColon'
    }

    # Minute column
    $minuteItems = for ($m = 0; $m -lt 60; $m += $MinuteInterval) { $m.ToString('00') }
    $minuteList = & $createColumn $minuteItems $defaultMinute.ToString('00') 50

    [void]$columnsPanel.Children.Add($hourList)
    [void]$columnsPanel.Children.Add($colonLabel)
    [void]$columnsPanel.Children.Add($minuteList)

    # AM/PM column (only for 12-hour)
    $ampmList = $null
    if (!$Use24Hour) {
        $ampmList = & $createColumn @('AM', 'PM') $defaultAmPm 50
        [void]$columnsPanel.Children.Add($ampmList)
    }

    [void]$popupStack.Children.Add($columnsPanel)

    # Separator
    $separator = [System.Windows.Controls.Border]@{
        Height     = 1
        Background = ConvertTo-UiBrush $colors.Border
        Margin     = [System.Windows.Thickness]::new(0, 8, 0, 8)
        Tag        = 'TimePickerSeparator'
    }
    [void]$popupStack.Children.Add($separator)

    # OK button
    $okButton = [System.Windows.Controls.Button]@{
        Content             = 'OK'
        Width               = 70
        Height              = 28
        HorizontalAlignment = [System.Windows.HorizontalAlignment]::Center
        Cursor              = [System.Windows.Input.Cursors]::Hand
        Padding             = [System.Windows.Thickness]::new(16, 4, 16, 4)
    }
    Set-ButtonStyle -Button $okButton
    [void]$popupStack.Children.Add($okButton)

    $popupBorder.Child = $popupStack
    $popup.Child = $popupBorder

    # Store references for event handlers
    $state = @{
        TimeText     = $timeText
        HourList     = $hourList
        MinuteList   = $minuteList
        AmPmList     = $ampmList
        Popup        = $popup
        Use24Hour    = $Use24Hour.IsPresent
        PickerBorder = $pickerBorder
        PopupBorder  = $popupBorder
        ArrowBorder  = $arrowBorder
        ArrowText    = $arrowText
        ColonLabel   = $colonLabel
        Separator    = $separator
        LabelBlock   = $labelBlock
    }

    # Click handler to open popup
    $pickerBorder.Add_MouseLeftButtonUp({
        param($sender, $eventArgs)
        $state.Popup.IsOpen = $true
        
        # Scroll selected items into view
        if ($state.HourList.SelectedItem) {
            $state.HourList.ScrollIntoView($state.HourList.SelectedItem)
        }
        if ($state.MinuteList.SelectedItem) {
            $state.MinuteList.ScrollIntoView($state.MinuteList.SelectedItem)
        }
        if ($state.AmPmList -and $state.AmPmList.SelectedItem) {
            $state.AmPmList.ScrollIntoView($state.AmPmList.SelectedItem)
        }
    }.GetNewClosure())

    # Hover effects - get colors dynamically for theme support
    $pickerBorder.Add_MouseEnter({
        param($sender, $eventArgs)
        $currentColors = Get-ThemeColors
        $sender.BorderBrush = ConvertTo-UiBrush $currentColors.Accent
    }.GetNewClosure())

    $pickerBorder.Add_MouseLeave({
        param($sender, $eventArgs)
        $currentColors = Get-ThemeColors
        $sender.BorderBrush = ConvertTo-UiBrush $currentColors.Border
    }.GetNewClosure())

    # OK button click - update display and close
    $okButton.Add_Click({
        param($sender, $eventArgs)
        
        $hour = if ($state.HourList.SelectedItem) { 
            $state.HourList.SelectedItem.Content 
        } else { 
            if ($state.Use24Hour) { '00' } else { '12' }
        }
        $minute = if ($state.MinuteList.SelectedItem) { 
            $state.MinuteList.SelectedItem.Content 
        } else { 
            '00' 
        }
        
        if ($state.Use24Hour) {
            $state.TimeText.Text = "${hour}:${minute}"
        }
        else {
            $ampm = if ($state.AmPmList -and $state.AmPmList.SelectedItem) { 
                $state.AmPmList.SelectedItem.Content 
            } else { 
                'AM' 
            }
            $state.TimeText.Text = "${hour}:${minute} ${ampm}"
        }
        
        $state.Popup.IsOpen = $false
    }.GetNewClosure())

    [void]$outerStack.Children.Add($pickerBorder)
    [void]$outerStack.Children.Add($popup)

    # Tag wrapper for FormLayout unwrapping in New-UiGrid
    Set-UiFormControlTag -Wrapper $outerStack -Label $labelBlock -Control $pickerBorder

    # FullWidth mode
    Set-FullWidthConstraint -Control $outerStack -Parent $parent -FullWidth:$FullWidth

    # Apply custom WPF properties if specified
    if ($WPFProperties) {
        Set-UiProperties -Control $outerStack -Properties $WPFProperties
    }

    Write-Debug "Adding to $($parent.GetType().Name)"
    [void]$parent.Children.Add($outerStack)

    # Create a container object to hold references to all components
    $timePickerContainer = [System.Windows.Controls.Grid]::new()
    $timePickerContainer.Tag = @{
        ControlType  = 'TimePicker'
        HourList     = $hourList
        MinuteList   = $minuteList
        AmPmList     = $ampmList
        Use24Hour    = $Use24Hour.IsPresent
        TimeText     = $timeText
    }

    $session.AddControlSafe($Variable, $timePickerContainer)

    # Register elements with ThemeEngine
    try {
        [PsUi.ThemeEngine]::RegisterElement($pickerBorder)
        [PsUi.ThemeEngine]::RegisterElement($popupBorder)
    }
    catch {
        Write-Verbose "Failed to register TimePicker with ThemeEngine: $_"
    }
}