//+------------------------------------------------------------------+
//|  AIAdvisor.mq5  v3.2                                             |
//|  AI Trading Advisor — Step 3.5 Flash + MT5 Strategy Tester       |
//|  Fixes: UTF8 WebSocket, buffer 65K, timeout 30 s, new bar        |
//+------------------------------------------------------------------+
#property copyright "AI Trading Advisor"
#property version   "3.20"
#property description "EA with AI price analysis (stepfun/step-3.5-flash)"

#include <Trade\Trade.mqh>
#include <WinAPI\winhttp.mqh>

//── Inputs ─────────────────────────────────────────────
input group "=== SERVER ==="
input string   InpHost         = "127.0.0.1";   // Server host
input int      InpPort         = 8989;           // Server port
input int      InpReceiveMs    = 30000;          // AI response timeout (ms) — Step 3.5 Flash is thinking!

input group "=== TRADING ==="
input double   InpLotSize      = 0.1;            // Lot size
input int      InpSL_Points    = 500;            // Stop Loss (points, 0=no SL)
input int      InpTP_Points    = 1000;           // Take Profit (points, 0=no TP)
input int      InpMagic        = 77777;          // Magic number
input int      InpSlippage     = 10;             // Slippage (pp)
input bool     InpCloseOpposite= true;           // Close reverse positions

input group "=== AI ANALYSIS ==="
input int      InpPriceBars    = 60;             // Bars for analysis
input int      InpAnalysisBars = 1;              // Request every N NEW bars
input string   InpChatMsg      = "";             // Questioning the AI at startup

input group "=== DISPLAY ==="
input bool     InpShowComment  = true;           // Show AI commentary

//── Global variables ─────────────────────────────────────────
CTrade  Trade;

HINTERNET g_hSession   = NULL;
HINTERNET g_hConnect   = NULL;
HINTERNET g_hRequest   = NULL;
HINTERNET g_hWebSocket = NULL;
int       g_socket     = INVALID_HANDLE;
bool      g_useWS      = false;

// ── New bar ──
int      g_barCount  = 0;
datetime g_lastBar   = 0;   // time of opening the last processed bar

int      g_totalBuys  = 0;
int      g_totalSells = 0;
string   g_lastSignal = "hold";
string   g_lastComment= "";

//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit()
  {
   Trade.SetExpertMagicNumber(InpMagic);
   Trade.SetDeviationInPoints(InpSlippage);
   Trade.SetTypeFilling(ORDER_FILLING_IOC);

   Print("╔══════════════════════════════════════╗");
   Print("║   AI Trading Advisor v3.2            ║");
   Print("║   Model:  stepfun/step-3.5-flash     ║");
   Print("╚══════════════════════════════════════╝");
   Print("Connection to ", InpHost, ":", InpPort);

   if(InitWebSocket())
     {
      g_useWS = true;
      Print("✓ WinHTTP WebSocket connected");
     }
   else
     {
      g_socket = SocketCreate();
      if(g_socket != INVALID_HANDLE && SocketConnect(g_socket, InpHost, InpPort, 3000))
         Print("✓ Socket connected");
      else
        {
         if(g_socket != INVALID_HANDLE)
           {
            SocketClose(g_socket);
            g_socket = INVALID_HANDLE;
           }
         Print("✗ No connection to the server! Launch quantum_midas_signal_server.py");
         return INIT_FAILED;
        }
     }

   if(StringLen(InpChatMsg) > 0)
      Print("AI: ", SendChat(InpChatMsg));

   PrintFormat("Parameters: Lot=%.2f  SL=%d  TP=%d  NewBars=%d  Timeout=%d ms",
               InpLotSize, InpSL_Points, InpTP_Points, InpAnalysisBars, InpReceiveMs);
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   string s = "stop";
   SendRaw(s);
   Sleep(300);

   if(g_useWS)
      CloseWebSocket();
   if(g_socket != INVALID_HANDLE)
     {
      SocketClose(g_socket);
      g_socket = INVALID_HANDLE;
     }

   if(InpShowComment)
      Comment("");
   Print("─────────────────────────────────────");
   PrintFormat("Result: BUY=%d  SELL=%d", g_totalBuys, g_totalSells);
   Print("AI Advisor disabled.");
  }

//+------------------------------------------------------------------+
//| OnTick — triggers every tick, but handles NEW BAR                |
//+------------------------------------------------------------------+
void OnTick()
  {
//── CHECKING NEW BAR ──────────────────────────────────────
// iTime(symbol, tf, 0) returns the open time of the current (0th) bar.
// If it has changed, a new bar has appeared.
   datetime barTime = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(barTime == g_lastBar)
      return;   // the same bar again - do nothing
   g_lastBar = barTime;               // remember the new bar

//── COUNTER: request to AI every InpAnalysisBars bars ────────
   g_barCount++;
   if(g_barCount % InpAnalysisBars != 0)
      return;

//── Collect close prices ────────────────────────────────────
   double prices[];
   ArraySetAsSeries(prices, true);
   int copied = CopyClose(_Symbol, PERIOD_CURRENT, 0, InpPriceBars, prices);
   if(copied < 20)
     {
      Print("Few bars: ", copied);
      return;
     }

//── CSV (oldest to newest) ───────────────────────────────────
   string csv = "";
   for(int i = copied - 1; i >= 0; i--)
     {
      csv += DoubleToString(prices[i], _Digits);
      if(i > 0)
         csv += ",";
     }

//── Request to AI ───────────────────────────────────────────────
   string cmd      = "PRICES:" + _Symbol + ":" + csv;
   string response = SendCommand(cmd);

   if(StringLen(response) == 0)
     {
      Print("⚠ No response from AI (timeout or interruption)");
      return;
     }

   string signal  = ParseJson(response, "signal");
   string comment = ParseJson(response, "comment");

   g_lastSignal  = signal;
   g_lastComment = comment;

   Print("▶ AI [", signal, "] ", comment);

//── Chart comment ────────────────────────────────────
   if(InpShowComment)
      Comment(StringFormat(
                 "══ AI Trading Advisor v3.2 ══\n"
                 "Symbol: %s  TF: %s\n"
                 "Bar #%d  |  %s\n"
                 "──────────────────────────\n"
                 "Signal: %s\n"
                 "AI:     %s\n"
                 "──────────────────────────\n"
                 "Positions: %d  Profit: %.2f\n"
                 "Equity:  %.2f\n"
                 "BUY: %d  SELL: %d",
                 _Symbol, EnumToString(Period()),
                 g_barCount, TimeToString(barTime, TIME_DATE|TIME_MINUTES),
                 signal, comment,
                 PositionsTotal(), AccountInfoDouble(ACCOUNT_PROFIT),
                 AccountInfoDouble(ACCOUNT_EQUITY),
                 g_totalBuys, g_totalSells
              ));

//── TRADING LOGIC ───────────────────────────────────────────
   if(signal == "buy")
     {
      if(InpCloseOpposite)
         CloseByType(POSITION_TYPE_SELL);
      if(!PositionExists(POSITION_TYPE_BUY))
         OpenBuy(comment);
     }
   else
      if(signal == "sell")
        {
         if(InpCloseOpposite)
            CloseByType(POSITION_TYPE_BUY);
         if(!PositionExists(POSITION_TYPE_SELL))
            OpenSell(comment);
        }
  }

//+------------------------------------------------------------------+
void OpenBuy(string comment)
  {
   double ask   = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double sl    = (InpSL_Points > 0) ? NormalizeDouble(ask - InpSL_Points * point, _Digits) : 0;
   double tp    = (InpTP_Points > 0) ? NormalizeDouble(ask + InpTP_Points * point, _Digits) : 0;

   if(Trade.Buy(InpLotSize, _Symbol, ask, sl, tp, "AI BUY"))
     { g_totalBuys++; PrintFormat("✅ BUY @ %.5f  SL=%.5f  TP=%.5f", ask, sl, tp); }
   else
      PrintFormat("❌ BUY error: %d %s", Trade.ResultRetcode(), Trade.ResultRetcodeDescription());
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OpenSell(string comment)
  {
   double bid   = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double sl    = (InpSL_Points > 0) ? NormalizeDouble(bid + InpSL_Points * point, _Digits) : 0;
   double tp    = (InpTP_Points > 0) ? NormalizeDouble(bid - InpTP_Points * point, _Digits) : 0;

   if(Trade.Sell(InpLotSize, _Symbol, bid, sl, tp, "AI SELL"))
     { g_totalSells++; PrintFormat("✅ SELL @ %.5f  SL=%.5f  TP=%.5f", bid, sl, tp); }
   else
      PrintFormat("❌ SELL error: %d %s", Trade.ResultRetcode(), Trade.ResultRetcodeDescription());
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CloseByType(ENUM_POSITION_TYPE type)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(!PositionSelectByTicket(ticket))
         continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;
      if(PositionGetInteger(POSITION_MAGIC)  != InpMagic)
         continue;
      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type)
         Trade.PositionClose(ticket);
     }
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool PositionExists(ENUM_POSITION_TYPE type)
  {
   for(int i = 0; i < PositionsTotal(); i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(!PositionSelectByTicket(ticket))
         continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;
      if(PositionGetInteger(POSITION_MAGIC)  != InpMagic)
         continue;
      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type)
         return true;
     }
   return false;
  }

//+------------------------------------------------------------------+
string SendChat(string question)
  {
   string cmd  = "CHAT:" + question;
   string resp = SendCommand(cmd);
   return ParseJson(resp, "comment");
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string SendCommand(string cmd)
  {
   if(!SendRaw(cmd))
      return "";
   return ReceiveRaw();
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool SendRaw(string msg)    // string by value - no compilation errors
  {
   return g_useWS ? WsSend(msg) : SocketSendStr(msg);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string ReceiveRaw()
  {
   return g_useWS ? WsReceive(InpReceiveMs) : SocketReceiveStr(InpReceiveMs);
  }

//══════════════════════════════════════════════════════════════════
//  WinHTTP WebSocket
//══════════════════════════════════════════════════════════════════
bool InitWebSocket()
  {
   uchar nullbuf[] = {};

   g_hSession = WinHttpOpen("MT5/AIAdvisor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
   if(!g_hSession)
      return false;

   g_hConnect = WinHttpConnect(g_hSession, InpHost, InpPort, 0);
   if(!g_hConnect)
     {
      CloseWebSocket();
      return false;
     }

   g_hRequest = WinHttpOpenRequest(g_hConnect, "GET", NULL, NULL, NULL, NULL, 0);
   if(!g_hRequest)
     {
      CloseWebSocket();
      return false;
     }

   if(!WinHttpSetOption(g_hRequest, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, nullbuf, 0))
     { CloseWebSocket(); return false; }

   if(!WinHttpSendRequest(g_hRequest, NULL, 0, nullbuf, 0, 0, 0))
     { CloseWebSocket(); return false; }

   if(!WinHttpReceiveResponse(g_hRequest, nullbuf))
     { CloseWebSocket(); return false; }

   ulong nv = 0;
   g_hWebSocket = WinHttpWebSocketCompleteUpgrade(g_hRequest, nv);
   if(!g_hWebSocket)
     {
      CloseWebSocket();
      return false;
     }

   WinHttpCloseHandle(g_hRequest);
   g_hRequest = NULL;
   return true;
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CloseWebSocket()
  {
   if(g_hWebSocket)
     {
      uchar r[];
      WinHttpWebSocketClose(g_hWebSocket, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, r, 0);
      g_hWebSocket = NULL;
     }
   if(g_hRequest)
     {
      WinHttpCloseHandle(g_hRequest);
      g_hRequest  = NULL;
     }
   if(g_hConnect)
     {
      WinHttpCloseHandle(g_hConnect);
      g_hConnect  = NULL;
     }
   if(g_hSession)
     {
      WinHttpCloseHandle(g_hSession);
      g_hSession  = NULL;
     }
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool WsSend(string msg)  // ── FIX 1: string by value (not const string&)
  {
   if(!g_hWebSocket)
      return false;
   uchar buf[];
   int n = StringToCharArray(msg, buf, 0, StringLen(msg), CP_UTF8);
// ── FIX 2: UTF8 frame type (Python server expects text, not binary!)
   ulong ret = WinHttpWebSocketSend(g_hWebSocket,
                                    WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE,
                                    buf, n);
   return (ret == 0);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string WsReceive(int ms = 30000)  // ── FIX 3: Default 30s for model reasoning
  {
   if(!g_hWebSocket)
      return "";

// ── FIX 4: buffer 65536 instead of 16384 (Step 3.5 Flash writes in detail)
   uchar buf[65536];
   ZeroMemory(buf);
   ulong bytesRead = 0;
   WINHTTP_WEB_SOCKET_BUFFER_TYPE bufType = (WINHTTP_WEB_SOCKET_BUFFER_TYPE)(-1);

   string result = "";
   uint t0 = GetTickCount();

   while(GetTickCount() - t0 < (uint)ms)
     {
      ulong ret = WinHttpWebSocketReceive(g_hWebSocket, buf, ArraySize(buf), bytesRead, bufType);
      if(ret == 0 && bytesRead > 0)
        {
         result += CharArrayToString(buf, 0, (int)bytesRead, CP_UTF8);
         // If the message is fragmented, wait for the final frame
         if(bufType == WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE ||
            bufType == WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE)
           {
            t0 = GetTickCount(); // reset the timer for each fragment
            continue;
           }
         return result;
        }
      Sleep(50);
     }
   return result;
  }

//══════════════════════════════════════════════════════════════════
//  Regular Socket
//══════════════════════════════════════════════════════════════════
bool SocketSendStr(string msg)  // string by value
  {
   if(g_socket == INVALID_HANDLE)
      return false;
   uchar buf[];
   int n = StringToCharArray(msg, buf, 0, StringLen(msg), CP_UTF8);
   return (SocketSend(g_socket, buf, n) == n);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
string SocketReceiveStr(int ms = 30000)
  {
   if(g_socket == INVALID_HANDLE)
      return "";
   string res = "";
   uint t0 = GetTickCount();
   while(GetTickCount() - t0 < (uint)ms)
     {
      uint avail = SocketIsReadable(g_socket);
      if(avail > 0)
        {
         uchar buf[];
         int n = SocketRead(g_socket, buf, avail, 2000);
         if(n > 0)
            res += CharArrayToString(buf, 0, n, CP_UTF8);
         if(StringLen(res) > 0)
            break;
        }
      Sleep(50);
     }
   return res;
  }

//══════════════════════════════════════════════════════════════════
//  JSON parser
//══════════════════════════════════════════════════════════════════
string ParseJson(string json, string key)
  {
   string search = "\"" + key + "\"";
   int pos = StringFind(json, search);
   if(pos < 0)
      return json;

   pos = StringFind(json, ":", pos);
   if(pos < 0)
      return "";
   pos++;

   while(pos < StringLen(json) && StringSubstr(json, pos, 1) == " ")
      pos++;

   if(StringSubstr(json, pos, 1) == "\"")
     {
      pos++;
      string val = "";
      while(pos < StringLen(json))
        {
         string ch = StringSubstr(json, pos, 1);
         if(ch == "\"")
            break;
         if(ch == "\\" && pos + 1 < StringLen(json))
           {
            pos++;
            ch = StringSubstr(json, pos, 1);
           }
         val += ch;
         pos++;
        }
      return val;
     }
   else
     {
      string val = "";
      while(pos < StringLen(json))
        {
         string ch = StringSubstr(json, pos, 1);
         if(ch == "," || ch == "}" || ch == "]")
            break;
         val += ch;
         pos++;
        }
      StringTrimLeft(val);
      StringTrimRight(val);
      return val;
     }
  }
//+------------------------------------------------------------------+
