//+------------------------------------------------------------------+
//|                                              CSVExporter.mqh     |
//|                  Production-Grade CSV Export Engine for MQL5     |
//+------------------------------------------------------------------+
#property strict

//--- Column separator constant for CSV output streams
#define CSV_SEPARATOR ","

//--- Schema column count for validation
#define SCHEMA_COLUMNS 9

//+------------------------------------------------------------------+
//| CCSVExporter: Encapsulates all CSV file I/O for structured export|
//+------------------------------------------------------------------+
class CCSVExporter
  {
private:
   //--- Internal file handle; INVALID_HANDLE when no stream is open
   int               m_handle;

   //--- Stores the resolved file name for diagnostic messages
   string            m_filename;

   //--- Tracks whether the file was opened in append mode
   bool              m_append_mode;

   //--- Writes the schema header row to the open stream
   bool              WriteHeader();

public:
   //--- Constructor initializes handle to invalid state
                     CCSVExporter();

   //--- Destructor guarantees file handle release
                    ~CCSVExporter();

   //--- Opens or creates the target CSV file
   //--- Pass use_common=true to resolve path via FILE_COMMON
   bool              Open(const string filename,const bool append=false,const bool use_common=false);

   //--- Appends a single result row to the open stream
   bool              WriteRow(const string test_phase,const string symbol,const string timeframe,const string indicator_name,const int filter_period,const double net_profit,const double sortino_ratio,const int false_flips,const double avg_lag_bars);

   //--- Flushes and closes the file stream safely
   void              Close();

   //--- Returns true if the stream is currently open and valid
   bool              IsOpen() const;
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCSVExporter::CCSVExporter()
  {
   m_handle      = INVALID_HANDLE;
   m_filename    = "";
   m_append_mode = false;
  }

//+------------------------------------------------------------------+
//| Destructor: releases file handle if caller neglected to Close()  |
//+------------------------------------------------------------------+
CCSVExporter::~CCSVExporter()
  {
   if(m_handle != INVALID_HANDLE)
     {
      FileClose(m_handle);
      m_handle = INVALID_HANDLE;
     }
  }

//+------------------------------------------------------------------+
//| Open: opens or creates the target CSV file                       |
//+------------------------------------------------------------------+
bool CCSVExporter::Open(const string filename,
                        const bool   append     = false,
                        const bool   use_common = false)
  {
//--- Reject open attempts if a stream is already active
   if(m_handle != INVALID_HANDLE)
     {
      Print("CCSVExporter::Open() error — stream already open on [",m_filename,"]. Call Close() first.");
      return(false);
     }

//--- Build the flag set for the requested mode
   int flags = FILE_CSV|FILE_ANSI;

   if(use_common)
      flags |= FILE_COMMON;

   m_append_mode = append;
   m_filename    = filename;

   if(append)
     {
      //--- Append mode: combine FILE_WRITE and FILE_READ to prevent truncation
      flags |= (FILE_WRITE|FILE_READ);
      m_handle = FileOpen(filename,flags);

      if(m_handle == INVALID_HANDLE)
        {
         //--- File may not exist yet; attempt creation on first run
         flags = FILE_WRITE|FILE_CSV|FILE_ANSI;
         if(use_common)
            flags |= FILE_COMMON;
         m_handle = FileOpen(filename,flags);

         if(m_handle == INVALID_HANDLE)
           {
            PrintFormat("CCSVExporter::Open() — FileOpen failed. Error: %d | File: %s",GetLastError(),filename);
            return(false);
           }

         //--- New file created; write the schema header
         return(WriteHeader());
        }
      else
        {
         //--- Existing file opened; seek to end for append
         FileSeek(m_handle,0,SEEK_END);
         return(true);
        }
     }
   else
     {
      //--- Overwrite mode: FILE_WRITE alone truncates and recreates
      flags |= FILE_WRITE;
      m_handle = FileOpen(filename,flags);

      if(m_handle == INVALID_HANDLE)
        {
         PrintFormat("CCSVExporter::Open() — FileOpen failed. Error: %d | File: %s",GetLastError(),filename);
         return(false);
        }

      //--- Write header to fresh file
      return(WriteHeader());
     }
  }

//+------------------------------------------------------------------+
//| WriteHeader: outputs the schema column header row                |
//+------------------------------------------------------------------+
bool CCSVExporter::WriteHeader()
  {
   if(m_handle == INVALID_HANDLE)
      return(false);

//--- Construct the header string as a single write operation
   string header = "Test_Phase"           + CSV_SEPARATOR +
                   "Symbol"               + CSV_SEPARATOR +
                   "Timeframe"            + CSV_SEPARATOR +
                   "Indicator_Name"       + CSV_SEPARATOR +
                   "Filter_Period"        + CSV_SEPARATOR +
                   "Net_Profit_$"         + CSV_SEPARATOR +
                   "Sortino_Ratio"        + CSV_SEPARATOR +
                   "False_Flips_Whipsaws" + CSV_SEPARATOR +
                   "Avg_Lag_On_Turn_Bars";

//--- FileWriteString appends a newline when FILE_CSV mode is active
   uint bytes = FileWriteString(m_handle,header + "\n");

   if(bytes == 0)
     {
      PrintFormat("CCSVExporter::WriteHeader() — write failed. Error: %d",GetLastError());
      return(false);
     }

   return(true);
  }

//+------------------------------------------------------------------+
//| WriteRow: appends a single data record to the CSV stream         |
//+------------------------------------------------------------------+
bool CCSVExporter::WriteRow(const string test_phase,
                            const string symbol,
                            const string timeframe,
                            const string indicator_name,
                            const int    filter_period,
                            const double net_profit,
                            const double sortino_ratio,
                            const int    false_flips,
                            const double avg_lag_bars)
  {
   if(m_handle == INVALID_HANDLE)
     {
      Print("CCSVExporter::WriteRow() — no open stream. Call Open() first.");
      return(false);
     }

//--- Format floating point values to consistent decimal precision
   string profit_str  = DoubleToString(net_profit,2);
   string sortino_str = DoubleToString(sortino_ratio,4);
   string lag_str     = DoubleToString(avg_lag_bars,2);

//--- Assemble the row as a single concatenated string
   string row = test_phase                     + CSV_SEPARATOR +
                symbol                         + CSV_SEPARATOR +
                timeframe                      + CSV_SEPARATOR +
                indicator_name                 + CSV_SEPARATOR +
                IntegerToString(filter_period) + CSV_SEPARATOR +
                profit_str                     + CSV_SEPARATOR +
                sortino_str                    + CSV_SEPARATOR +
                IntegerToString(false_flips)   + CSV_SEPARATOR +
                lag_str;

   uint bytes = FileWriteString(m_handle,row + "\n");

   if(bytes == 0)
     {
      PrintFormat("CCSVExporter::WriteRow() — write failed. Error: %d",GetLastError());
      return(false);
     }

   return(true);
  }

//+------------------------------------------------------------------+
//| Close: flushes and releases the file handle                      |
//+------------------------------------------------------------------+
void CCSVExporter::Close()
  {
   if(m_handle != INVALID_HANDLE)
     {
      FileClose(m_handle);
      m_handle   = INVALID_HANDLE;
      m_filename = "";
     }
  }

//+------------------------------------------------------------------+
//| IsOpen: returns handle validity state                            |
//+------------------------------------------------------------------+
bool CCSVExporter::IsOpen() const
  {
   return(m_handle != INVALID_HANDLE);
  }
//+------------------------------------------------------------------+