ui/search-panel.psm1

using module ..\utils\debounce.psm1

Class SearchPanel {
    [object]$parent
    [System.Windows.Forms.Control]$container
    [System.Windows.Forms.Panel]$panelSearch
    [System.Windows.Forms.TextBox]$txtSearch
    [Debounce]$debounce

    SearchPanel([object]$parent, [System.Windows.Forms.Control]$container) {
        $this.parent = $parent
        $this.container = $container
        $this.debounce = [Debounce]::new(300)
        $this.init()
    }

    [void]init() {
        $this.panelSearch = [System.Windows.Forms.Panel]::new()
        $this.panelSearch.Tag = $this
        $this.panelSearch.Add_GotFocus({
                param($s, $e)
                $self = $s.Tag
                $self.txtSearch.Focus()
            })

        $this.txtSearch = [System.Windows.Forms.TextBox]::new()
        $this.txtSearch.Tag = $this
        $this.txtSearch.Name = "txtSearch"
        $this.txtSearch.Width = 100
        $this.txtSearch.Left = 3
        $this.txtSearch.BackColor = [System.Drawing.Color]::LemonChiffon
        $this.txtSearch.BorderStyle = [System.Windows.Forms.BorderStyle]::None

        $prevButton = [System.Windows.Forms.Button]::new()
        $prevButton.Tag = $this
        $prevButton.Text = "▲"
        $prevButton.Font = [System.Drawing.Font]::new("Segoe UI", 10)
        $prevButton.ForeColor = [System.Drawing.Color]::Gray
        $prevButton.Width = $this.txtSearch.Height
        $prevButton.Height = $this.txtSearch.Height
        $prevButton.Left = $this.txtSearch.Right + 3
        $prevButton.Top = $this.txtSearch.Top
        $prevButton.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
        $prevButton.FlatAppearance.BorderSize = 0
        $prevButton.Cursor = [System.Windows.Forms.Cursors]::Arrow
        $prevButton.Add_Click({ 
                param($s, $e)
                $self = $s.Tag
                $self.parent.onSearch($self.txtSearch.Text, "Prev")
                
            })
        $prevButton.Add_GotFocus({
                param($s, $e)
                $self = $s.Tag
                $self.txtSearch.Focus()
            })
        $this.panelSearch.Controls.Add($prevButton)
    
        $nextButton = [System.Windows.Forms.Button]::new()
        $nextButton.Tag = $this
        $nextButton.Text = "▼"
        $nextButton.Font = [System.Drawing.Font]::new("Segoe UI", 10)
        $nextButton.ForeColor = [System.Drawing.Color]::Gray
        $nextButton.Width = $this.txtSearch.Height
        $nextButton.Height = $this.txtSearch.Height
        $nextButton.Left = $prevButton.Right
        $nextButton.Top = $this.txtSearch.Top
        $nextButton.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
        $nextButton.FlatAppearance.BorderSize = 0
        $nextButton.Cursor = [System.Windows.Forms.Cursors]::Arrow
        $nextButton.Add_Click({ 
                param($s, $e)
                $self = $s.Tag
                $self.parent.onSearch($self.txtSearch.Text, "Next")
            })
        $nextButton.Add_GotFocus({
                param($s, $e)
                $self = $s.Tag
                $self.txtSearch.Focus()
            })
        $this.panelSearch.Controls.Add($nextButton)

        $closeButton = [System.Windows.Forms.Button]::new()
        $closeButton.Tag = $this
        $closeButton.Text = "✖"
        $closeButton.Font = [System.Drawing.Font]::new("Segoe UI", 12)
        $closeButton.ForeColor = [System.Drawing.Color]::Red
        $closeButton.Width = $this.txtSearch.Height
        $closeButton.Height = $this.txtSearch.Height
        $closeButton.Left = $nextButton.Right
        $closeButton.Top = $this.txtSearch.Top
        $closeButton.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
        $closeButton.FlatAppearance.BorderSize = 0
        $closeButton.Cursor = [System.Windows.Forms.Cursors]::Arrow
        $closeButton.Add_Click({ 
                param($s, $e)
                $self = $s.Tag
                $self.panelSearch.Visible = $false
                $self.txtSearch.Text = ""
                $self.container.Focus()
            })
        $this.panelSearch.Controls.Add($closeButton)

        $this.panelSearch.Height = $this.txtSearch.Height
        $this.panelSearch.Width = $closeButton.Right
        $this.panelSearch.BackColor = $this.txtSearch.BackColor
        $this.panelSearch.Visible = $false
        $this.panelSearch.Anchor = "Bottom, Left"
        $this.panelSearch.Top = $this.container.Height - $this.panelSearch.Height - 8
        $this.panelSearch.Left = 5
        $this.panelSearch.BorderStyle = [System.Windows.Forms.BorderStyle]::FixedSingle
        $this.panelSearch.Controls.Add($this.txtSearch)
    
        $this.container.Controls.Add($this.panelSearch)
        $this.panelSearch.BringToFront()
      
        $this.txtSearch.Add_KeyDown({
                param($s, $e)
                $self = $s.Tag
                if ($e.KeyCode -eq [System.Windows.Forms.Keys]::Escape) { 
                    $self.show($false)
                    $e.Handled = $true
                    $e.SuppressKeyPress = $true
                }
                elseif ($e.Control -and $e.KeyCode -eq [System.Windows.Forms.Keys]::F) {
                    $text = $self.parent.getSelectedText().Trim()
                    if ($text) {
                        $self.txtSearch.Text = $text
                        $self.txtSearch.SelectionStart = 0
                        $self.txtSearch.SelectionLength = $text.Length
                    }
                    $e.Handled = $true
                    $e.SuppressKeyPress = $true
                }
                elseif (($e.KeyCode -eq [System.Windows.Forms.Keys]::F3 -or $e.KeyCode -eq ([System.Windows.Forms.Keys]::Enter)) -and -not $e.Control) {
                    $direction = if ($e.Shift) { "Prev" } else { "Next" }
                    $self.parent.onSearch($s.Text, $direction)
                    $e.Handled = $true
                    $e.SuppressKeyPress = $true
                }

            })

        $this.txtSearch.Add_TextChanged({
                param($s, $e)
                $self = $s.Tag
                $self.debounce.run({ param($_self, [string]$txt) $_self.parent.onSearch($txt, "") }, @($self, $s.Text))
            }) 


    }

    [void]show([bool]$state) {
        $this.show($state, "")
    }

    [void]show([bool]$state, [string]$initialVal) {
        $this.panelSearch.Visible = $state

        if ($state) { 
            $this.txtSearch.Focus() 
            $this.txtSearch.Text = $initialVal
            if ($initialVal) { 
                $this.txtSearch.SelectionStart = 0
                $this.txtSearch.SelectionLength = $initialVal.Length
            }
        }
        else { $this.txtSearch.Text = "" }
    }

    [void]toggle() {
        $this.toggle("")
    }

    [void]toggle([string]$initialVal) {
        $this.show(-not $this.panelSearch.Visible, $initialVal)
    }

    [bool]isVisible() {
        return $this.panelSearch.Visible
    }

    [string]getSearchText() {
        return $this.txtSearch.Text
    }

    [void]setSearchText([string]$text) {
        $this.txtSearch.Text = $text
    }

    [void]invokeDebouncedSearch([string]$direction, [bool]$keepScrollPos) {
        $this.invokeDebouncedSearch($direction, $keepScrollPos, -1)
    }
    
    # Debounced search rerun if visible
    [void]invokeDebouncedSearch([string]$direction, [bool]$keepScrollPos, [int]$searchStartPos) {
        if (-not $this.isVisible()) { return }
        $this.debounce.run({ param($self, [string]$txt, [string]$dir, [bool]$keepScroll, [int]$startPos) $self.parent.onSearch($txt, $dir, $keepScroll, $startPos) }, @($this, $this.txtSearch.Text, $direction, $keepScrollPos, $searchStartPos)) 
    }
}