﻿//+------------------------------------------------------------------+
//|                                                   SearchPanel.mqh|
//|                                   Copyright 2026, MetaQuotes Ltd.|
//|                           https://www.mql5.com/en/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.00"

#ifndef _SEARCH_PANEL_MQH
#define _SEARCH_PANEL_MQH

#include <Controls\Dialog.mqh>
#include <Controls\Edit.mqh>
#include <Controls\Button.mqh>
#include "SearchEngine.mqh"
#include "ChartLauncher.mqh"

//--- Layout constants
#define PANEL_MARGIN      10
#define EDIT_HEIGHT       22
#define BUTTON_HEIGHT     20
#define BUTTON_GAP        2

//+------------------------------------------------------------------+
//| Main search panel class                                          |
//+------------------------------------------------------------------+
class CSearchPanel : public CAppDialog
  {
private:
   //--- Search input field
   CEdit             m_editSearch;
   //--- Symbol input field
   CEdit             m_editSymbol;
   //--- Button to confirm symbol selection
   CButton           m_btnSetSymbol;
   //--- Button to open the first result
   CButton           m_btnOpen;
   //--- Button to close the panel
   CButton           m_btnClose;
   //--- Dynamic array of result buttons (one per matching indicator)
   CButton           m_resultButtons[];
   //--- Number of currently displayed result buttons
   int               m_resultCount;

   //--- Currently selected target symbol
   string            m_targetSymbol;
   //--- Search engine instance
   CSearchEngine     m_engine;
   //--- Filtered indicator results from search
   SIndicatorInfo    m_filtered[];
   //--- Last search text (to avoid re-filtering unnecessarily)
   string            m_lastSearchText;

   //--- Refreshes the list of result buttons based on current filter
   void              UpdateList();
   //--- Attaches the indicator at the given filtered-list index
   void              OpenSelected(int index);
   //--- Removes all existing result buttons from the panel
   void              ClearButtons();

public:
                     CSearchPanel();
                    ~CSearchPanel();

   //--- Creates the panel and all its child controls
   virtual bool      Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2);
   //--- Event handler – routes all custom events to appropriate actions
   virtual bool      OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   //--- Forwards chart events to the base class
   void              ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   //--- Timer callback – checks if the search text has changed
   void              OnTimer();
   //--- Stops the internal timer
   void              KillTimer();
   //--- Hides the panel and stops the timer
   void              ClosePanel();
  };

//+------------------------------------------------------------------+
//| Constructor / Destructor                                         |
//+------------------------------------------------------------------+
CSearchPanel::CSearchPanel() : m_resultCount(0),
   m_lastSearchText(""),
   m_targetSymbol("")
  {}
//--- The destructor stops the timer to prevent callbacks after panel destruction
CSearchPanel::~CSearchPanel() { KillTimer(); }

//+------------------------------------------------------------------+
//| Creates the panel                                                |
//+------------------------------------------------------------------+
bool CSearchPanel::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2)
  {
//--- Create the parent dialog first
   if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2))
      return(false);

   int w = x2 - x1;
   int h = y2 - y1;

//--- Indicator search edit box (top row)
   int editX = PANEL_MARGIN;
   int editY = PANEL_MARGIN;
   int editW = w - 2 * PANEL_MARGIN;
   if(!m_editSearch.Create(chart, name + "Edit", subwin, editX, editY, editX + editW, editY + EDIT_HEIGHT))
      return(false);
   if(!Add(m_editSearch))
      return(false);
   m_editSearch.Text("Type indicator name...");
   m_lastSearchText = "";

//--- Symbol input edit box (second row)
   int symY = editY + EDIT_HEIGHT + PANEL_MARGIN;
   int symW = w - 2 * PANEL_MARGIN - 60 - PANEL_MARGIN;
   if(!m_editSymbol.Create(chart, name + "Symbol", subwin, editX, symY, editX + symW, symY + EDIT_HEIGHT))
      return(false);
   if(!Add(m_editSymbol))
      return(false);
   m_editSymbol.Text(Symbol());
   m_targetSymbol = Symbol();

//--- Set button (confirms the entered symbol)
   int btnX = editX + symW + PANEL_MARGIN;
   if(!m_btnSetSymbol.Create(chart, "SetSymbolBtn", subwin, btnX, symY, btnX + 60, symY + EDIT_HEIGHT))
      return(false);
   if(!Add(m_btnSetSymbol))
      return(false);
   m_btnSetSymbol.Text("Set");

//--- Open button (opens the first search result)
   int btnY = h - PANEL_MARGIN - BUTTON_HEIGHT;
   int btnW = 60;
   int btnH = BUTTON_HEIGHT;
   btnX = w / 2 - btnW - 5;
   if(!m_btnOpen.Create(chart, "OpenBtn", subwin, btnX, btnY, btnX + btnW, btnY + btnH))
      return(false);
   if(!Add(m_btnOpen))
      return(false);
   m_btnOpen.Text("Open");

//--- Close button (closes the panel)
   btnX = w / 2 + 5;
   if(!m_btnClose.Create(chart, "CloseBtn", subwin, btnX, btnY, btnX + btnW, btnY + btnH))
      return(false);
   if(!Add(m_btnClose))
      return(false);
   m_btnClose.Text("Close");

//--- Initialize the result list (empty at start)
   UpdateList();

//--- Start a timer to poll the search box for text changes
   EventSetTimer(1);
   return(true);
  }

//+------------------------------------------------------------------+
//| Stops the internal timer                                         |
//+------------------------------------------------------------------+
void CSearchPanel::KillTimer()
  {
   EventKillTimer();
  }

//+------------------------------------------------------------------+
//| Hides the panel and stops the timer                              |
//+------------------------------------------------------------------+
void CSearchPanel::ClosePanel()
  {
   KillTimer();
   Hide();
  }

//+------------------------------------------------------------------+
//| Event handler – uses sparam for control identification           |
//+------------------------------------------------------------------+
bool CSearchPanel::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
//--- Let base class handle system events (X button, resizing, etc.)
   bool handled = CAppDialog::OnEvent(id, lparam, dparam, sparam);

//--- Only process custom events generated by our controls
   if(id == CHARTEVENT_CUSTOM)
     {
      //--- Set Symbol button: validates and stores the entered symbol
      if(sparam == "SetSymbolBtn")
        {
         string sym = m_editSymbol.Text();
         //--- Validate symbol using SymbolSelect (adds to Market Watch if valid)
         if(SymbolSelect(sym, true))
           {
            m_targetSymbol = sym;
            Print("Symbol set to: ", m_targetSymbol);
           }
         else
           {
            //--- Invalid symbol – revert to current chart symbol
            Print("Invalid symbol: ", sym, " – using current chart");
            m_editSymbol.Text(Symbol());
            m_targetSymbol = Symbol();
           }
         return(true);
        }

      //--- Open button: attaches the first search result (if any)
      if(sparam == "OpenBtn")
        {
         if(m_resultCount > 0)
            OpenSelected(0);
         return(true);
        }

      //--- Close button: hides the panel and stops the timer
      else
         if(sparam == "CloseBtn")
           {
            ClosePanel();
            return(true);
           }

         //--- Result buttons: sparam starts with "ResultBtn_"
         else
            if(StringFind(sparam, "ResultBtn_") == 0)
              {
               //--- Extract the index from the button name, e.g., "ResultBtn_3" → 3
               int index = (int)StringToInteger(StringSubstr(sparam, 10));
               OpenSelected(index);   // attach the indicator at the selected index
               return(true);
              }

            //--- For all other events, return the base class result
            else
              {
               return(handled);
              }
     }

   return(handled);
  }

//+------------------------------------------------------------------+
//| Forwards chart events to the base class                          |
//+------------------------------------------------------------------+
void CSearchPanel::ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CAppDialog::ChartEvent(id, lparam, dparam, sparam);
  }

//+------------------------------------------------------------------+
//| Timer callback – checks if the search text has changed           |
//+------------------------------------------------------------------+
void CSearchPanel::OnTimer()
  {
   string current = m_editSearch.Text();
//--- Only re-filter if the text actually changed
   if(current != m_lastSearchText)
     {
      m_lastSearchText = current;
      m_engine.Filter(current, m_filtered);
      UpdateList();
     }
  }

//+------------------------------------------------------------------+
//| Removes all existing result buttons from the panel               |
//+------------------------------------------------------------------+
void CSearchPanel::ClearButtons()
  {
   for(int i = 0; i < m_resultCount; i++)
      m_resultButtons[i].Destroy(0);
   ArrayResize(m_resultButtons, 0);
   m_resultCount = 0;
  }

//+------------------------------------------------------------------+
//| Refreshes the list of result buttons based on current filter     |
//+------------------------------------------------------------------+
void CSearchPanel::UpdateList()
  {
//--- Clear any existing result buttons before rebuilding
   ClearButtons();

//--- Get the total number of filtered indicators
   int total = ArraySize(m_filtered);
   if(total == 0)
      return;

//--- Calculate the starting Y position for result buttons
//--- The start is below both the search box and the symbol input box
   int startY = PANEL_MARGIN + EDIT_HEIGHT + PANEL_MARGIN + EDIT_HEIGHT + PANEL_MARGIN;
   int btnY = Height() - PANEL_MARGIN - BUTTON_HEIGHT;
   int availableHeight = btnY - startY - PANEL_MARGIN;
   if(availableHeight < BUTTON_HEIGHT)
      return;

//--- Determine how many buttons can fit vertically
   int maxButtons = availableHeight / (BUTTON_HEIGHT + BUTTON_GAP);
   if(maxButtons < 1)
      maxButtons = 1;

//--- Limit the number of buttons to what fits
   int count = (total < maxButtons) ? total : maxButtons;
   ArrayResize(m_resultButtons, count);

//--- Create one button for each matching indicator
   int btnWidth = Width() - 2 * PANEL_MARGIN;
   int yPos = startY;

   for(int i = 0; i < count; i++)
     {
      //--- Each button gets a unique name for event identification
      string btnName = "ResultBtn_" + IntegerToString(i);

      //--- Create the button at the calculated position
      if(!m_resultButtons[i].Create(0, btnName, 0, PANEL_MARGIN, yPos, PANEL_MARGIN + btnWidth, yPos + BUTTON_HEIGHT))
         continue;

      //--- Set the button text to the indicator name
      m_resultButtons[i].Text(m_filtered[i].name);

      //--- Add the button to the panel and make it visible
      if(!Add(m_resultButtons[i]))
         continue;
      m_resultButtons[i].Show();

      //--- Move down for the next button
      yPos += BUTTON_HEIGHT + BUTTON_GAP;
     }

//--- Store the actual number of buttons displayed
   m_resultCount = count;
  }

//+------------------------------------------------------------------+
//| Opens selected indicator on the stored symbol                    |
//+------------------------------------------------------------------+
void CSearchPanel::OpenSelected(int index)
  {
//--- Validate the index is within the filtered results range
   if(index < 0 || index >= ArraySize(m_filtered))
      return;

//--- Determine which symbol to use for attachment
   string symbol = m_targetSymbol;
   if(symbol == "")
      symbol = Symbol();

//--- Log the action for debugging and user feedback
   Print("Attaching ", m_filtered[index].name, " to ", symbol);

//--- Retrieve the indicator type and attach it to the chart
   ENUM_INDICATOR type = m_filtered[index].type;
   AttachIndicator(type, symbol);
  }

#endif // _SEARCH_PANEL_MQH
//+------------------------------------------------------------------+
