#include <stdint.h>
  #include <Arduino.h>
  #include "Settings.h"
  #include "quality.h"

  /**
  * @brief Завершить процесс дистилляции, выключить нагрев и сбросить счетчики.
  */
  void distiller_finish();

  /**
  * @brief Установить режим питания.
  * @param Mode Режим (строка)
  */
  void set_power_mode(String Mode);

  /**
  * @brief Включить или выключить питание.
  * @param On true — включить, false — выключить
  */
  void set_power(bool On);

  /**
  * @brief Создать файл с данными текущей сессии дистилляции.
  */
  void create_data();

  /**
  * @brief Открыть или закрыть клапан.
  * @param Val true — открыть, false — закрыть
  * @param msg true — отправить сообщение
  */
  void open_valve(bool Val, bool msg);

  /**
  * @brief Установить ШИМ для насоса.
  * @param duty Значение ШИМ
  */
  void set_pump_pwm(float duty);

  /**
  * @brief Установить программу дистилляции из строки.
  * @param WProgram Строка с программой
  */
  void set_dist_program(String WProgram);

  /**
  * @brief Перейти к строке программы дистилляции с номером num.
  * @param num Номер строки программы
  */
  void run_dist_program(uint8_t num);

  /**
  * @brief Получить текущую программу дистилляции в виде строки.
  * @return Строка с программой
  */
  String get_dist_program();

  /**
  * @brief Сбросить прогноз времени.
  */
  void resetTimePredictor();

  /**
  * @brief Обновить прогноз времени.
  */
  void updateTimePredictor();

  /**
  * @brief Получить расчетное оставшееся время.
  * @return Оставшееся время (минуты)
  */
  float get_dist_remaining_time();

  /**
  * @brief Получить расчетное общее время.
  * @return Общее время (минуты)
  */
  float get_dist_predicted_total_time();

  /**
  * @brief Проверить аварийные ситуации дистиллятора.
  */
  void check_alarm_distiller();

  /**
  * @brief Включить или выключить буззер.
  * @param On true — включить, false — выключить
  */
  void set_buzzer(bool On);

  /**
  * @brief Установить текущую мощность.
  * @param power Мощность (Ватт)
  */
  void set_current_power(float power);

  /**
  * @brief Установить скорость насоса.
  * @param speed Скорость
  * @param msg true — отправить сообщение
  */
  void set_pump_speed(float speed, bool msg);

  /**
  * @brief Получить статус самовара в виде строки.
  * @return Статус (строка)
  */
  String get_Samovar_Status();

  /**
  * @brief Получить расчетную спиртуозность по температуре.
  * @param t Температура
  * @return Спиртуозность (%)
  */
  float get_alcohol(float t);

  /**
  * @brief Получить расчетную спиртуозность пара по температуре.
  * @param t Температура
  * @return Спиртуозность пара (%)
  */
  float get_steam_alcohol(float t);

  /**
  * @brief Установить емкость.
  * @param cap Номер емкости
  */
  void set_capacity(uint8_t cap);

  /**
  * @brief Сбросить счетчик датчиков.
  */
  void reset_sensor_counter();

  /**
  * @brief Проверить ошибки питания и обработать их.
  */
  void check_power_error();

  /**
  * @brief Установить емкость (дублирующая функция).
  * @param cap Номер емкости
  */
  void set_capacity(uint8_t cap);

  /**
  * @brief Проверить, идет ли кипячение.
  * @return true если кипит, иначе false
  */
  bool check_boiling();

  /**
  * @brief Отправить сообщение пользователю.
  * @param m Текст сообщения
  * @param msg_type Тип сообщения
  */
  void SendMsg(const String& m, MESSAGE_TYPE msg_type);
    void WaterSratusCheck();
  /**
  * @brief Структура для прогнозирования времени процесса дистилляции.
  */
  void stop_process(String reason);
  void WaterTempAlarmPowerReg();
  void WaterPumpReg();// регулируем водяной насос   
  void CheckTWatherPumpOFF(); // Отключение насоса по снижению Т воды
  void SetWatherValve2(bool state);

  struct TimePredictor {
      unsigned long startTime;           ///< Время начала процесса
      float initialAlcohol;              ///< Начальное содержание спирта
      float initialTemp;                 ///< Начальная температура
      float lastTemp;                    ///< Последняя температура
      float tempChangeRate;              ///< Скорость изменения температуры
      unsigned long lastUpdateTime;      ///< Время последнего обновления
      float predictedTotalTime;          ///< Прогнозируемое общее время (мин)
      float remainingTime;               ///< Оставшееся время (мин)
  };

  TimePredictor timePredictor = {0, 0, 0, 0, 0, 0, 0, 0};
  // Минимальные пороги, чтобы не делить на ноль и не спамить оценками
  static constexpr float MIN_TEMP_RATE = 0.01f;    // °C/мин
  static constexpr float MIN_ALC_RATE  = 0.001f;   // доля/мин
  static constexpr unsigned long PREDICTOR_UPDATE_MS = 30000; // шаг пересчёта, мс
  bool LevelTrigger[3];

bool TaraFull() { //Проверка наполнения приёмных емеостей
      whls.tick(); 
      if (LevelTrigger[0] && whls.isHolded()) {
        LevelTrigger[0] = 0;
        //whls.resetStates();  
        SendMsg("Тара 1 наполнилась.", WARNING_MSG);
        return (true);
      }
    #if BOARD == ESP32S3 //Датчики уровня барды
      BLU_s.tick();
      if (LevelTrigger[1] && BLU_s.isHolded()) {
        LevelTrigger[1] = 0;
        //BLU_s.resetStates();  
        SendMsg("Тара 2 наполнилась.", WARNING_MSG);
        return (true);
      }
      BLD_s.tick();
      if (LevelTrigger[2] && BLD_s.isHolded()) {
        LevelTrigger[2] = 0;
        //BLD_s.resetStates();  
        SendMsg("Тара 3 наполнилась.", WARNING_MSG);
        return (true);
      }
    #endif
    return false;
}
/**
 * @brief Основной цикл обработки процесса дистилляции.
 *
 * Вызывает обработку текущего этапа, обновляет прогноз времени, контролирует аварии и переходы между этапами.
 */
void distiller_proc() {
 //    SendMsg("Статус: " + String(SamovarStatusInt) + 
 //            ", Режим: " + String(Samovar_Mode) + 
 //            ", PowerOn: " + String(PowerOn), NOTIFY_MSG);
    
  if (SamovarStatusInt != 1000) return;

  if (!PowerOn) {
    if (SamSetup.UseMQTT) {
      SessionDescription.replace(",", ";");
      MqttSendMsg((String)chipId + "," + SamSetup.TimeZone + "," + SAMOVAR_VERSION + "," + get_dist_program() + "," + SessionDescription, "st");
    }
    set_power(true);
    if (SamSetup.PwrType != NO_POVER_REG) {
      delay(1000);
      set_power_mode(POWER_SPEED_MODE);
    } else {
      current_power_mode = POWER_SPEED_MODE;
      digitalWrite(RELE_CHANNEL4, SamSetup.rele4);
    }
    create_data();  //создаем файл с данными
    SteamSensor.Start_Pressure = bme_pressure;
    SendMsg(("Включен нагрев дистиллятора"), NOTIFY_MSG);
    run_dist_program(0);
    d_s_temp_prev = WaterSensor.avgTemp;
    if (SamSetup.PwrType != NO_POVER_REG) {
        digitalWrite(RELE_CHANNEL4, SamSetup.rele4);
    }
    // Инициализируем систему прогнозирования
    resetTimePredictor();
  }

  // Обновляем прогноз времени
  updateTimePredictor();

  if (TankSensor.avgTemp >= SamSetup.DistTemp) {
    distiller_finish();
  }

  //Обрабатываем программу дистилляции (только если есть программы для выполнения)
  if (ProgramNum < ProgramLen && program[ProgramNum].WType.length() > 0) {
    if (program[ProgramNum].WType == "T" && program[ProgramNum].Speed <= TankSensor.avgTemp) {
      //Если температура куба превысила заданное в программе значение - переходим на следующую строку программы
      run_dist_program(ProgramNum + 1);
    } else if (program[ProgramNum].WType == "A" && program[ProgramNum].Speed >= get_alcohol(TankSensor.avgTemp)) {
      //Если спиртуозность в кубе понизилась до заданного в программе значения - переходим на следующую строку программы
      run_dist_program(ProgramNum + 1);
    } else if (program[ProgramNum].WType == "S" && program[ProgramNum].Speed >= get_alcohol(TankSensor.avgTemp) / get_alcohol(TankSensor.StartProgTemp)) {
      run_dist_program(ProgramNum + 1);
    } else if (program[ProgramNum].WType == "P" && program[ProgramNum].Speed >= get_steam_alcohol(TankSensor.avgTemp)) {
      //Если спиртуозность в кубе понизилась до заданного в программе значения - переходим на следующую строку программы
      run_dist_program(ProgramNum + 1);
    } else if (program[ProgramNum].WType == "R" && program[ProgramNum].Speed >= get_steam_alcohol(TankSensor.avgTemp) / get_steam_alcohol(TankSensor.StartProgTemp)) {
      run_dist_program(ProgramNum + 1);
    } else if (SamSetup.UseHLS_D && TaraFull()) {  //переход по наполнению приёмной тары
      run_dist_program(ProgramNum + 1);
    }
  }
  

  //Если Т в кубе больше 90 градусов и включено напряжение и DistTimeF > 0, проверяем, что DistTimeF минут температура в кубе не меняется от последнего заполненного значения больше, чем на 0.1 градус
  if (TankSensor.avgTemp > 90 && PowerOn && SamSetup.DistTimeF > 0) {
    if (abs(TankSensor.avgTemp - d_s_temp_finish) > 0.1) {
      d_s_temp_finish = TankSensor.avgTemp;
      d_s_time_min = millis();
    } else if ((millis() - d_s_time_min) > SamSetup.DistTimeF * 60 * 1000) {
      SendMsg(("В кубе не осталось спирта"), NOTIFY_MSG);
      distiller_finish();
    }
  }

  // // Добавляем оценку качества
  // static unsigned long lastQualityCheck = 0;
  // if (millis() - lastQualityCheck >= 5000) { // Проверяем каждые 5 секунд
  //   lastQualityCheck = millis();
    
  //   QualityParams quality = getQualityAssessment();
    
  //   // Если качество низкое, отправляем предупреждение
  //   if (quality.overallScore < 70) {
  //     SendMsg(("Внимание! Качество отбора снижено: " + String(quality.overallScore, 1) + 
  //             "%. " + quality.recommendation), WARNING_MSG);
  //   }
    
  //   // Если качество критически низкое, можно автоматически корректировать процесс
  //   if (quality.overallScore < 50) {
  //     // Автоматическая коррекция процесса
  //     if (quality.stabilityScore < 50) {
  //       // Уменьшаем мощность для стабилизации
  //       set_current_power(target_power_volt - 5);
  //     }
  //   }
  // }

  vTaskDelay(10 / portTICK_PERIOD_MS);
}

void distiller_finish() {
  if (SamSetup.PwrType != NO_POVER_REG) {
    digitalWrite(RELE_CHANNEL4, !SamSetup.rele4);
  }
  String timeMsg = "Дистилляция завершена. Общее время: " + String(int((millis() - timePredictor.startTime) / 60000)) + " мин.";
  stop_process(timeMsg);
}


void check_alarm_distiller() {
  if (!valve_status) {
    if (ACPSensor.avgTemp >= SamSetup.Max_ACP_T - 5) {
      set_buzzer(true);
      open_valve(true, true);
    }
    else if (TankSensor.avgTemp >= SamSetup.Opn_Vlv_Tnk_T && PowerOn) {
      set_buzzer(true);
      open_valve(true, true);
    }
  }

  CheckTWatherPumpOFF(); // Отключение насоса по снижению Т воды

  check_boiling(); //Определяем, что началось кипение - вода охлаждения начала нагреваться

  WaterPumpReg();// регулируем водяной насос  

  //Проверяем, что температурные параметры не вышли за предельные значения
  if ((WaterSensor.avgTemp >= SamSetup.Max_Wt_T || ACPSensor.avgTemp >= SamSetup.Max_ACP_T) && PowerOn) {
    //Если с температурой проблемы - выключаем нагрев, пусть оператор разбирается
    set_buzzer(true);
    set_power(false);
    String s = "";
    if (WaterSensor.avgTemp >= SamSetup.Max_Wt_T)
      s = s + " Воды";
    else if (ACPSensor.avgTemp >= SamSetup.Max_ACP_T)
      s = s + " ТСА";
    SendMsg("Аварийное отключение! Превышена максимальная температура" + s, ALARM_MSG);
  }
  if (SamSetup.PwrType != NO_POVER_REG) {
    check_power_error();
  }  
  WaterSratusCheck();
  WaterTempAlarmPowerReg();

 if (SamSetup.UseWV) { //если клапан воды
  if (WaterSensor.avgTemp >= SamSetup.SetWaterTemp + 1) SetWatherValve2(ON);
  else if (WaterSensor.avgTemp <= SamSetup.SetWaterTemp - 1) SetWatherValve2(OFF);
 }
  vTaskDelay(10 / portTICK_PERIOD_MS);
}

void run_dist_program(uint8_t num) {
  if (num == 0) { // Сброс датчиков наполнения тары
    whls.resetStates();  
    #if BOARD == ESP32S3
    BLU_s.resetStates();  
    BLD_s.resetStates(); 
    #endif
    for (uint8_t i=0; i<3; i++) LevelTrigger[i] = true;
  }
  // Проверяем, что номер программы не превышает количество программ
  if (num >= ProgramLen || program[num].WType.length() == 0) {
    // Программы закончились - не увеличиваем ProgramNum выше допустимого
    if (ProgramNum < ProgramLen) {
      ProgramNum = ProgramLen > 0 ? ProgramLen : 0;
    }
    SendMsg("Выполнение программ закончилось, продолжение отбора", NOTIFY_MSG);
    return;
  }
  ProgramNum = num;

  SendMsg("Переход к строке программы №" + (String)(num + 1), NOTIFY_MSG);
  // Сбрасываем прогноз при переходе к новой программе
  resetTimePredictor();
  //запоминаем текущие значения температур
  SteamSensor.StartProgTemp = SteamSensor.avgTemp;
  PipeSensor.StartProgTemp = PipeSensor.avgTemp;
  WaterSensor.StartProgTemp = WaterSensor.avgTemp;
  TankSensor.StartProgTemp = TankSensor.avgTemp;

  if (num > 0) {
    set_capacity(program[num - 1].capacity_num);
    if (program[num - 1].WType.length() > 0) {
      if (SamSetup.PwrType != NO_POVER_REG) {
        int tt=SamSetup.PwrType >= STAB_AVR ? 400 : 40;
            if (abs(program[num - 1].Power) > tt && program[num - 1].Power > 0) {
              set_current_power(program[num - 1].Power);
            } else if (program[num - 1].Power != 0) {
              set_current_power(target_power_volt + program[num - 1].Power);
            }
      }
    }
  }

}

void set_dist_program(String WProgram) {
  char c[1024] = {0};
  WProgram.toCharArray(c, 1024);
  char *pair = strtok(c, ";");
  //String MeshTemplate;
  int i = 0;
  while (pair != NULL && i < MAX_PRG) {
    program[i].WType = pair;
    pair = strtok(NULL, ";");
    program[i].Speed = atof(pair);  //Value
    pair = strtok(NULL, ";");
    program[i].capacity_num = atoi(pair);
    pair = strtok(NULL, "\n");
    program[i].Power = atof(pair);
    i++;
    ProgramLen = i;
    pair = strtok(NULL, ";");
    if ((!pair || pair == NULL || pair[0] == 13) && i < MAX_PRG) {
      program[i].WType = "";
      break;
    }
  }
}

String get_dist_program() {
  String Str = "";
  int k = MAX_PRG;
  for (uint8_t i = 0; i < k; i++) {
    if (program[i].WType.length() == 0) {
      i = MAX_PRG + 1;
    } else {
      Str += program[i].WType + ";";
      Str += (String)program[i].Speed + ";";
      Str += (String)(int)program[i].capacity_num + ";";
      Str += (String)program[i].Power + "\n";
    }
  }
  return Str;
}

void resetTimePredictor() {
    timePredictor.startTime = millis();
    timePredictor.initialAlcohol = get_alcohol(TankSensor.avgTemp);
    timePredictor.initialTemp = TankSensor.avgTemp;
    timePredictor.lastTemp = TankSensor.avgTemp;
    timePredictor.lastUpdateTime = millis();
    timePredictor.tempChangeRate = 0;
    timePredictor.predictedTotalTime = 0;
    timePredictor.remainingTime = 0;
}

void updateTimePredictor() {
    unsigned long currentTime = millis();
    float currentTemp = TankSensor.avgTemp;
    float currentAlcohol = get_alcohol(currentTemp);

    unsigned long dtMs = currentTime - timePredictor.lastUpdateTime;
    if (dtMs < PREDICTOR_UPDATE_MS) return; // считаем не чаще, чем нужно

    float dtMin = dtMs / 60000.0f;
    timePredictor.tempChangeRate = (currentTemp - timePredictor.lastTemp) / dtMin; // °C/мин
    timePredictor.lastTemp = currentTemp;
    timePredictor.lastUpdateTime = currentTime;

    // Обновляем прогноз по спирту (используем долю, а не %)
    float alcoholDelta = timePredictor.initialAlcohol - currentAlcohol;
    float alcoholChangeRate = (dtMin > 0) ? (alcoholDelta / ((currentTime - timePredictor.startTime) / 60000.0f)) : 0; // доля/мин

    float remaining = 0;
    String wtype = program[ProgramNum].WType;

    if (wtype == "T") {
        float targetTemp = program[ProgramNum].Speed;
        float dT = targetTemp - currentTemp;
        if (dT <= 0) {
            remaining = 0;
        } else if (timePredictor.tempChangeRate > MIN_TEMP_RATE) {
            remaining = dT / timePredictor.tempChangeRate;
        }
    } else if (wtype == "A" || wtype == "S") {
        float targetAlcohol = program[ProgramNum].Speed;
        if (wtype == "S") {
            targetAlcohol *= get_alcohol(TankSensor.StartProgTemp);
        }
        float dA = currentAlcohol - targetAlcohol;
        if (dA <= 0) {
            remaining = 0;
        } else if (alcoholChangeRate > MIN_ALC_RATE) {
            remaining = dA / alcoholChangeRate;
        }
    } else if (wtype == "P" || wtype == "R") {
        // Ориентируемся на крепость пара
        float currentSteamAlcohol = get_steam_alcohol(currentTemp);
        float target = program[ProgramNum].Speed;
        if (wtype == "R") {
            target *= get_steam_alcohol(TankSensor.StartProgTemp);
        }
        float dS = currentSteamAlcohol - target;
        if (dS <= 0) {
            remaining = 0;
        } else if (alcoholChangeRate > MIN_ALC_RATE) {
            remaining = dS / alcoholChangeRate;
        }
    } else {
        // Для прочих шагов оставляем 0 — нет метрики для прогноза
        remaining = 0;
    }

    timePredictor.remainingTime = max(0.0f, remaining);
    float elapsedMinutes = (currentTime - timePredictor.startTime) / 60000.0f;
    timePredictor.predictedTotalTime = elapsedMinutes + timePredictor.remainingTime;
}

float get_dist_remaining_time() {
    return timePredictor.remainingTime;
}

float get_dist_predicted_total_time() {
    return timePredictor.predictedTotalTime;
}
