#include <Arduino.h>
  #include "Settings.h"
  #include "pumppwm.h"

    extern void DbgMsg(const String& Msg, bool ln);
  /**
  * @brief Прочитать конфигурацию из памяти.
  */
  void read_config();

  /**
  * @brief Получить значение из строки по разделителю.
  * @param data Строка
  * @param separator Разделитель
  * @param index Индекс значения
  * @return Значение (строка)
  */
  String getValue(const String& data, char separator, int index);

  /**
  * @brief Запустить сервисные задачи (например, шаговый двигатель).
  */
  void startService(void);

  /**
  * @brief Остановить сервисные задачи.
  */
  void stopService(void);

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

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

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

  /**
  * @brief Сохранить текущий профиль настроек.
  */
  void save_profile();

  /**
  * @brief Завершить программу затирания, выключить нагрев, насос и клапаны.
  */
  void beer_finish();

  /**
  * @brief Управлять состоянием нагревателя по ПИД-регулятору.
  * @param setpoint Целевая температура
  * @param temp Текущая температура
  */
  void set_heater_state(float setpoint, float temp);

  /**
  * @brief Установить ШИМ для нагревателя.
  * @param dutyCycle Скважность (0.0 - 1.0)
  */
  void set_heater(double dutyCycle);

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

  /**
  * @brief Перейти к строке программы с номером num, инициализировать этап.
  * @param num Номер строки программы
  */
  void run_beer_program(uint8_t num);

  /**
  * @brief Запустить автотюнинг ПИД-регулятора.
  */
  void StartAutoTune();

  /**
  * @brief Завершить автотюнинг ПИД-регулятора и применить параметры.
  */
  void FinishAutoTune();

  /**
  * @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 m Текст сообщения
  * @param msg_type Тип сообщения
  */
  void SendMsg(const String& m, MESSAGE_TYPE msg_type);

  /**
  * @brief Получить строковое описание программы затирания.
  * @return Строка с описанием программы
  */
  String get_beer_program();

  /**
  * @brief Проверить и обработать состояние мешалки и насоса.
  */
  void check_mixer_state();

  /**
  * @brief Установить состояние мешалки.
  * @param state true — включить, false — выключить
  * @param dir true — реверс, false — прямое вращение
  */
  void set_mixer_state(bool state, bool dir);

  /**
  * @brief Установить целевое состояние насоса через I2C.
  * @param on 1 — включить, 0 — выключить
  * @return true если успешно
  */
  bool set_mixer_pump_target(uint8_t on);

  /**
  * @brief Управлять шаговым двигателем по времени.
  * @param spd Скорость
  * @param direction Направление
  * @param time Время работы (мс)
  * @return true если успешно
  */
  bool set_stepper_by_time(uint16_t spd, uint8_t direction, uint16_t time);

  /**
  * @brief Совершить шаг шаговым двигателем для засыпи хмеля.
  */
  void HopStepperStep(uint16_t speed, uint16_t steps);

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

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

  /**
  * @brief Сбросить счетчик датчиков и состояния процесса.
  */
  void reset_sensor_counter(void);
  void stop_process(String reason);
  bool set_stepper2_by_time(uint16_t spd, uint8_t direction, uint16_t time);
  bool isBoilingStarted(float currentTemp);

  #define TEMP_HISTORY_SIZE 10  // Размер буфера истории температур (точек)
  #define BOILING_DETECT_THRESHOLD 0.08  // Порог по стандартному отклонению, °C
  #define MIN_BOILING_TEMP 98.0  // Минимальная температура кипения (с учетом погрешности)
  #define STABLE_WINDOWS_REQUIRED 5 // Кол-во стабильных окон подряд для фиксации кипения
  #define MAX_TREND_ABS_PER_SEC 0.02 // Макс. модуль тренда в °C/с для стабильности

  struct BoilingDetector {
      float tempHistory[TEMP_HISTORY_SIZE];
      uint8_t historyIndex = 0;
      uint8_t samplesFilled = 0;
      bool isBoiling = false;
      unsigned long lastUpdateTime = 0;
      uint8_t stableCount = 0;
  };

  BoilingDetector boilingDetector;

  static inline void resetBoilingDetector() {
      boilingDetector.historyIndex = 0;
      boilingDetector.samplesFilled = 0;
      boilingDetector.stableCount = 0;
      boilingDetector.isBoiling = false;
      boilingDetector.lastUpdateTime = 0;
      for (int i = 0; i < TEMP_HISTORY_SIZE; i++) boilingDetector.tempHistory[i] = 0;
  }
//
float getBeerCurrentTemp() {// Получить температуру с выбранного датчика для режима пива
  if (SamovarStatusInt != 2000) return 0.0;
  
  switch (program[ProgramNum].TempSensor) {
    case 0:
      return TankSensor.avgTemp;
    case 1:
      return WaterSensor.avgTemp;
    case 2:
      return PipeSensor.avgTemp;
    case 3:
      return SteamSensor.avgTemp;
    case 4:
      return ACPSensor.avgTemp;
    default:
      return TankSensor.avgTemp;
  }
}

/**
 * @brief Основной цикл запуска процесса затирания. Инициализация и старт программы.
 */
void beer_proc() {
  if (SamovarStatusInt != 2000) return;

  if (startval == 2000 && !PowerOn) {
    // Сброс детектора кипения при запуске процесса
    resetBoilingDetector();
  if (SamSetup.UseMQTT) {
      SessionDescription.replace(",", ";");
      MqttSendMsg(String(chipId) + "," + SamSetup.TimeZone + "," + SAMOVAR_VERSION + "," + get_beer_program() + "," + SessionDescription, "st");
  }
    create_data();  //создаем файл с данными
    PowerOn = true;
    set_power(true);
    run_beer_program(0);
  }
  vTaskDelay(10 / portTICK_PERIOD_MS);
}

/**
 * @brief Переход к этапу программы с номером num, обработка сообщений и сброс переменных этапа.
 * @param num Номер этапа программы
 */
void run_beer_program(uint8_t num) {
  if (Samovar_Mode != SAMOVAR_BEER_MODE || !PowerOn) return;
  if (startval == 2000) startval = 2001;
  ProgramNum = num;
  begintime = 0;
  msgfl = true;

  if (program[ProgramNum].WType == "A") {
    StartAutoTune();
  }

  if (ProgramNum > ProgramLen - 1) {
    num = MAX_PRG;
    SetScriptOff = 1;
  }

  //Если предыдущая программа была программой Lua и скрипт запущен - останавливаем скрипт
  if (ProgramNum > 0 && program[ProgramNum-1].WType == "L" && loop_lua_fl) {
    SetScriptOff = 1;
  }

  String msg = "Переход к строке программы №" + String((num + 1));
  if (num == MAX_PRG) {
    //если num = MAX_PRG значит мы достигли финала (или процесс сброшен принудительно), завершаем работу
    beer_finish();
    return;
  } else {
    if (program[num].WType == "M") {
      msg += "; Нагрев до температуры засыпи солода: " + String(program[num].Temp) + "°";
    } else if (program[num].WType == "P") {
      msg += "; Температурная пауза: " + String(program[num].Temp) + "°, время: " + String(program[num].Time) + " мин";
    } else if (program[num].WType == "B") {
      msg += "; Кипячение, время: " + String(program[num].Time) + " мин";
    } else if (program[num].WType == "C") {
      msg += "; Охлаждение до температуры: " + String(program[num].Temp) + "°";
    } else if (program[num].WType == "F") {
      msg += "; Ферментация, поддержание температуры: " + String(program[num].Temp) + "°";
    } else if (program[num].WType == "W") {
      msg += "; Режим ожидания";
    }
  }

  if (SamSetup.ChangeProgramBuzzer) {
    set_buzzer(true);
    SendMsg(msg, ALARM_MSG);
  } else {
    SendMsg(msg, NOTIFY_MSG);
  }

  //сбрасываем переменные для мешалки и насоса
  alarm_c_low_min = 0;  //мешалка вкл
  alarm_c_min = 0;  //мешалка пауза
  currentstepcnt = 0; //счетчик циклов мешалки
}

/**
 * @brief Завершает процесс затирания: выключает насос, нагрев, клапаны, сбрасывает состояния.
 */
void beer_finish() {
  if (valve_status) {
    open_valve(false, true);
  }
  // Сброс детектора кипения при завершении процесса
  resetBoilingDetector();
  set_mixer_state(OFF);
  if (SamSetup.UseWP) {
  set_pump_pwm(0);
  pump_started = false;
  }
  setHeaterPosition(false);
  PowerOn = false;
  heater_state = false;
  startval = 0;
  stop_process("Программа затирания завершена");
}

/**
 * @brief Проверяет и управляет состоянием процесса затирания, включая нагрев, охлаждение, паузы и кипячение.
 */
void check_alarm_beer() {

  if (startval <= 2000) return;

  // Если процесс запущен, обеспечиваем корректное состояние флага PowerOn
  if (!PowerOn && startval > 2000) {
    PowerOn = true;
  }

  float temp = 0;
  switch (program[ProgramNum].TempSensor) {
    case 0:
      temp = TankSensor.avgTemp;
      break;
    case 1:
      temp = WaterSensor.avgTemp;
      break;
    case 2:
      temp = PipeSensor.avgTemp;
      break;
    case 3:
      temp = SteamSensor.avgTemp;
      break;
    case 4:
      temp = ACPSensor.avgTemp;
      break;
  }

  //Обрабатываем программу

  //Проверяем, что клапан воды охлаждения не открыт, когда не нужно
  if (program[ProgramNum].WType != "C" && program[ProgramNum].WType != "F" && valve_status && PowerOn && program[ProgramNum].WType != "L") {
    //Закрываем клапан воды
    open_valve(false, false);
  }

  //Если программа - Lua - ждем, ничего не делаем
  if (program[ProgramNum].WType == "L") {
    return;
  }

  //Если программа - ожидание - ждем, ничего не делаем
  if (program[ProgramNum].WType == "W") {
    if (begintime == 0) {
      begintime = millis();
      setHeaterPosition(false);
      open_valve(false, false);
    }
    check_mixer_state(); // Управление мешалкой и насосом по параметрам программы
    return;
  }

  //Если режим Автотюнинг
  if (program[ProgramNum].WType == "A") {
    if (tuning) {
      set_heater_state(program[ProgramNum].Temp, temp);
    } else {
      beer_finish();
    }
  }

  //Если режим Засыпь солода или Пауза
  if (program[ProgramNum].WType == "M" || program[ProgramNum].WType == "P") {
    set_heater_state(program[ProgramNum].Temp, temp);
  }

  //Если режим Брага
  if (program[ProgramNum].WType == "F") {
    //Если температура меньше целевой - греем, иначе охлаждаем.
    if (temp < program[ProgramNum].Temp - TankSensor.SetTemp) {
      if (valve_status) {
        //Закрываем клапан воды
        open_valve(false, false);
      }
      //Поддерживаем целевую температуру
      set_heater_state(program[ProgramNum].Temp, temp);
    } else if (temp > program[ProgramNum].Temp + TankSensor.SetTemp) {
      {
        if (!valve_status) {
          //Отключаем нагреватель
          setHeaterPosition(false);
          //Открываем клапан воды
          open_valve(true, false);
        }
      }
    } else {
      //Так как находимся в пределах температурной уставки, не нужно ни греть, ни охлаждать
      //Отключаем нагреватель
      setHeaterPosition(false);
      //Закрываем клапан воды, если температура в кубе чуть меньше температурной уставки, чтобы часто не щелкать клапаном
      if ((temp < program[ProgramNum].Temp + TankSensor.SetTemp - 0.1) && valve_status && PowerOn) {
        open_valve(false, false);
      }
    }
  }

  if (program[ProgramNum].WType == "M" && temp >= program[ProgramNum].Temp - TankSensor.SetTemp) {
    //Достигли температуры засыпи солода. Пишем об этом. Продолжаем поддерживать температуру. Переход с этой строки программы на следующую возможен только в ручном режиме
    if (startval == 2001) {
      set_buzzer(true);
      SendMsg(("Достигнута температура засыпи солода!"), NOTIFY_MSG);
    }
    startval = 2002;
  }

  if (program[ProgramNum].WType == "P" && temp >= program[ProgramNum].Temp - TankSensor.SetTemp) {
    if (begintime == 0) {
      //Засекаем время для отсчета, сколько держать паузу
      begintime = millis();
      SendMsg("Достигнута температурная пауза " + String(program[ProgramNum].Temp) + "°. Ждем " + String(program[ProgramNum].Time) + " минут.", NOTIFY_MSG);
    }
  }

  //Если программа - охлаждение - ждем, когда температура в кубе упадет ниже заданной, и управляем водой для охлаждения
  if (program[ProgramNum].WType == "C") {
    if (begintime == 0) {
      begintime = millis();
      setHeaterPosition(false);
      //Открываем клапан воды
      open_valve(true, false);
      if (SamSetup.UseWP) {
          set_pump_pwm(1023);
          pump_started = true;
      }
    }
    if (temp <= program[ProgramNum].Temp) {
      //Если температура упала
      //Закрываем клапан воды
      open_valve(false, false);
      if (SamSetup.UseWP) {
          set_pump_pwm(0);
          pump_started = false;
      }
      //запускаем следующую программу
      run_beer_program(ProgramNum + 1);
    }
  }

  //Если программа - кипячение
  if (program[ProgramNum].WType == "B") {
    //Если предыдущая программа была программой кипячения - просто продолжаем кипятить.
    if (begintime == 0 && ProgramNum > 0 && program[ProgramNum - 1].WType == "B") begintime = millis();

    if (begintime == 0) {
      //Определяем начало кипения
      if (isBoilingStarted(temp)) {
        msgfl = true;
        begintime = millis();
        SendMsg(("Начался режим кипячения"), NOTIFY_MSG);
      }
    }

    //Греем до температуры кипения, исходя из того, что датчик в кубе врет не сильно
    if (begintime == 0) {
      set_heater_state(SamSetup.Boilng_T + 5, temp);
    } else {
      //Иначе поддерживаем температуру
      heater_state = true;
    if (SamSetup.PwrType > 0) { 
	  //Устанавливаем заданное напряжение
      set_current_power(SamSetup.BVolt);
    } else {
      current_power_mode = POWER_WORK_MODE;
      digitalWrite(RELE_CHANNEL1, SamSetup.rele1);
    }
      if (SamSetup.UseST) {
        digitalWrite(RELE_CHANNEL4, SamSetup.rele4);
      } else {
        digitalWrite(RELE_CHANNEL4, !SamSetup.rele4);
      }
    }

    //Проверяем, что еще нужно держать паузу. За 30 секунд до окончания шлем сообщение
    if (begintime > 0 && msgfl && ((float(millis()) - begintime) / 1000 / 60 + 0.5 >= program[ProgramNum].Time)) {
      set_buzzer(true);
      msgfl = false;
      SendMsg(("Засыпьте хмель!"), NOTIFY_MSG);
      //DbgMsg("Засыпьте хмель!",1);
      HopStepperStep(SamSetup.D_Speed, SamSetup.D_Steps);
    }
  }

  //Проверяем, что еще нужно держать паузу
  if (begintime > 0 && (program[ProgramNum].WType == "B" || program[ProgramNum].WType == "P") && ((millis() - begintime) / 1000 / 60 >= program[ProgramNum].Time)) {
    //Запускаем следующую программу
    run_beer_program(ProgramNum + 1);
  }
  
  //Обрабатываем мешалку и насос
  check_mixer_state();

  vTaskDelay(10 / portTICK_PERIOD_MS);
}


/**
 * @brief Возвращает строковое описание текущей программы затирания.
 * @return Строка с описанием программы
 */
String get_beer_program() {
  String Str = "";
  Str.reserve(1024);
  for (uint8_t i = 0; i < MAX_PRG; i++) {
    if (program[i].WType.length() == 0) {
      return Str;
    } else {
      Str += program[i].WType + ";";
      Str += (String)program[i].Temp + ";";
      Str += (String)(int)program[i].Time + ";";
      Str += (String)program[i].capacity_num + "^" + program[i].Speed + "^" + program[i].Volume + "^" + program[i].Power + ";";
      Str += (String)program[i].TempSensor + "\n";
    }
  }
  return Str;
}

/**
 * @brief Устанавливает программу затирания из строки.
 * @param WProgram Строка с описанием программы
 */
void set_beer_program(String WProgram) {
  //M - malt application temp, P - pause, B - boil, C - cool
  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].Temp = atof(pair);
    pair = strtok(NULL, ";");
    program[i].Time = atof(pair);
    pair = strtok(NULL, ";");
    //разберем шаблон для насоса/мешалки по частям
    program[i].capacity_num = getValue(pair, '^', 0).toInt();  //Тип устройства - 1 - мешалка, 2 - насос, 3 - мешалка и насос одновременно
    program[i].Speed = getValue(pair, '^', 1).toInt();         //Направление вращения, если задано отрицательное значение - мешалка после паузы меняет направление вращения
    program[i].Volume = getValue(pair, '^', 2).toInt();        //Время включения в секундах
    program[i].Power = getValue(pair, '^', 3).toInt();         //Время выключения в секундах
    pair = strtok(NULL, "\n");
    program[i].TempSensor = atoi(pair);

    i++;
    ProgramLen = i;
    pair = strtok(NULL, ";");
    if ((!pair || pair == NULL || pair[0] == 13) && i < MAX_PRG) {
      program[i].WType = "";
      break;
    }
  }
}

/**
 * @brief Запускает автотюнинг ПИД-регулятора.
 */
void StartAutoTune() {
  // REmember the mode we were in
  ATuneModeRemember = heaterPID.GetMode();

  Output = 50;

  aTune.SetControlType(1);

  // set up the auto-tune parameters
  aTune.SetNoiseBand(aTuneNoise);
  aTune.SetOutputStep(aTuneStep);
  aTune.SetLookbackSec((int)aTuneLookBack);
  tuning = true;
}

/**
 * @brief Завершает автотюнинг ПИД-регулятора, применяет параметры и сохраняет профиль.
 */
void FinishAutoTune() {
  aTune.Cancel();
  tuning = false;

  // Extract the auto-tune calculated parameters
  SamSetup.Kp = aTune.GetKp();
  SamSetup.Ki = aTune.GetKi();
  SamSetup.Kd = aTune.GetKd();

  WriteConsoleLog("Kp = " + (String)Kp);
  WriteConsoleLog("Ki = " + (String)Ki);
  WriteConsoleLog("Kd = " + (String)Kd);

  // Re-tune the PID and revert to normal control mode
  heaterPID.SetTunings(SamSetup.Kp, SamSetup.Ki, SamSetup.Kd);
  heaterPID.SetMode(ATuneModeRemember);

  save_profile();
  set_heater_state(0, 50);
}


