#include <Wire.h>
#include <RTClib.h>
#include <Servo.h>
#include <LiquidCrystal_I2C.h>
#include <avr/wdt.h>

// =================================================
// PINES
// =================================================
#define PIN_BTN_HORAS        2
#define PIN_BTN_MINUTOS      3
#define PIN_BTN_CUENTABOLAS  4
#define PIN_BTN_STOP         A0
#define PIN_BTN_DIAG         12

#define PIN_SERVO_FLIPPER     9
#define PIN_SERVO_MOTOR      10
#define PIN_SERVO_CAMPANADAS 11

#define PIN_MOTOR_PWMA  5
#define PIN_MOTOR_AIN1  7
#define PIN_MOTOR_AIN2  8

// =================================================
// CONFIGURACIÓN
// =================================================
const uint8_t CAMPANADA_DELAY_SEG = 0;

const unsigned long DEBOUNCE_MS    = 80;
const unsigned long LCD_ROTATE_MS  = 2000;
const unsigned long LCD_UPDATE_MS  = 200;

const int SERVO_MOTOR_REPOSO = 84;
const int SERVO_MOTOR_DELTA  = 44;

// ---- PROTECCIÓN MOTOR ----
const unsigned long MOTOR_TIMEOUT_MS = 120000UL;

// ---- BOTÓN DIAG ----
const unsigned long DIAG_LONG_PRESS_MS = 3000;

// ---- PROTECCIÓN CONTEO BOLAS ----
const unsigned long BALL_GUARD_MS = 400;

// =================================================
// OBJETOS
// =================================================
RTC_DS3231 rtc;
LiquidCrystal_I2C lcd(0x27, 16, 2);

Servo servoFlipper;
Servo servoMotor;
Servo servoCampanadas;

// =================================================
// VARIABLES
// =================================================
enum { BTN_H, BTN_M, BTN_CB, BTN_STOP_I, BTN_TOTAL };

bool lastBtn[BTN_TOTAL];
unsigned long lastBtnTime[BTN_TOTAL];

unsigned long nowMs;
unsigned long servoMotorReturn = 0;
unsigned long servoCampReturn  = 0;

unsigned long lcdLastRotate = 0;
unsigned long lcdLastUpdate = 0;

int lastMinute = -1;   // para servo de minutos
int prevMinute = -1;   // <<< NUEVO: transición real
int lastHour   = -1;

bool flipperAbierto     = false;
bool flipperHechoHora  = false;
bool campanadaHechaHora = false;

int bolasContadas = 0;
int bolasObjetivo = 0;

uint8_t lcdPage = 0;

// ---- Protección motor ----
unsigned long motorStartMs = 0;
bool motorBloqueado = false;

// ---- Botón DIAG ----
unsigned long diagPressStart = 0;
bool diagLongHandled = false;

// ---- Protección conteo bolas ----
unsigned long lastBallMs = 0;

// ---- Botones RTC por flanco ----
bool lastHourBtn   = HIGH;
bool lastMinuteBtn = HIGH;

// =================================================
// FUNCIONES
// =================================================
bool botonPulsado(uint8_t idx, uint8_t pin) {
  bool estado = digitalRead(pin);
  unsigned long ahora = millis();

  if (estado != lastBtn[idx]) {
    lastBtn[idx] = estado;
    lastBtnTime[idx] = ahora;
    return false;
  }

  if (estado == LOW && (ahora - lastBtnTime[idx]) > DEBOUNCE_MS) {
    lastBtnTime[idx] = ahora + 1;
    return true;
  }
  return false;
}

int bolasPorHora(int hora24) {
  int h = hora24 % 12;
  if (h == 0) return 1;
  return h + 1;
}

// =================================================
// SETUP
// =================================================
void setup() {

  wdt_enable(WDTO_4S);

  Wire.begin();
  rtc.begin();

  lcd.begin(16, 2);
  lcd.backlight();
  lcd.clear();

  pinMode(PIN_BTN_HORAS, INPUT_PULLUP);
  pinMode(PIN_BTN_MINUTOS, INPUT_PULLUP);
  pinMode(PIN_BTN_CUENTABOLAS, INPUT_PULLUP);
  pinMode(PIN_BTN_STOP, INPUT_PULLUP);
  pinMode(PIN_BTN_DIAG, INPUT_PULLUP);

  for (int i = 0; i < BTN_TOTAL; i++) lastBtn[i] = HIGH;

  servoFlipper.attach(PIN_SERVO_FLIPPER);
  servoMotor.attach(PIN_SERVO_MOTOR);
  servoCampanadas.attach(PIN_SERVO_CAMPANADAS);

  servoFlipper.write(0);
  servoMotor.write(SERVO_MOTOR_REPOSO);
  servoCampanadas.write(0);

  pinMode(PIN_MOTOR_PWMA, OUTPUT);
  pinMode(PIN_MOTOR_AIN1, OUTPUT);
  pinMode(PIN_MOTOR_AIN2, OUTPUT);

  digitalWrite(PIN_MOTOR_AIN1, HIGH);
  digitalWrite(PIN_MOTOR_AIN2, LOW);

  DateTime now = rtc.now();
  lastMinute = now.minute();
  prevMinute = now.minute();
  lastHour   = now.hour();
}

// =================================================
// LOOP
// =================================================
void loop() {

  wdt_reset();

  DateTime now = rtc.now();
  nowMs = millis();
  int currentMinute = now.minute();

  // =================================================
  // BOTÓN DIAG / RESET LÓGICO (SIN CAMBIOS)
  // =================================================
  if (digitalRead(PIN_BTN_DIAG) == LOW) {

    if (diagPressStart == 0) {
      diagPressStart = nowMs;
      diagLongHandled = false;
    }

    if (!diagLongHandled &&
        (nowMs - diagPressStart) > DIAG_LONG_PRESS_MS) {

      motorBloqueado = false;
      motorStartMs = 0;

      flipperAbierto = false;
      flipperHechoHora = false;

      bolasContadas = 0;
      bolasObjetivo = 0;

      servoFlipper.write(0);
      servoCampanadas.write(0);

      diagLongHandled = true;
    }
  }
  else {
    diagPressStart = 0;
    diagLongHandled = false;
  }

  // =================================================
  // AJUSTE RTC (FLANCO LIMPIO)  <<< CAMBIO 2
  // =================================================
  bool hState = digitalRead(PIN_BTN_HORAS);
  bool mState = digitalRead(PIN_BTN_MINUTOS);

  if (lastHourBtn == HIGH && hState == LOW) {
    rtc.adjust(DateTime(now.year(), now.month(), now.day(),
                        (now.hour() + 1) % 24, 0, 0));
  }
  lastHourBtn = hState;

  if (lastMinuteBtn == HIGH && mState == LOW) {
    rtc.adjust(DateTime(now.year(), now.month(), now.day(),
                        now.hour(), (currentMinute + 1) % 60, 0));
  }
  lastMinuteBtn = mState;

  // =================================================
  // MOTOR DC + FAILSAFE (SIN CAMBIOS)
  // =================================================
  bool motorDebeGirar =
    (digitalRead(PIN_BTN_STOP) != LOW) &&
    !motorBloqueado;

  if (motorDebeGirar) {
    analogWrite(PIN_MOTOR_PWMA, 240);
    if (motorStartMs == 0) motorStartMs = nowMs;
  }
  else {
    analogWrite(PIN_MOTOR_PWMA, 0);
    motorStartMs = 0;
  }

  if (motorStartMs > 0 &&
      (nowMs - motorStartMs) > MOTOR_TIMEOUT_MS &&
      bolasContadas == 0) {

    motorBloqueado = true;
    analogWrite(PIN_MOTOR_PWMA, 0);
  }

  // =================================================
  // SERVO MOTOR (cada minuto) (SIN CAMBIOS)
  // =================================================
  if (currentMinute != lastMinute) {
    lastMinute = currentMinute;
    servoMotor.write(SERVO_MOTOR_REPOSO - SERVO_MOTOR_DELTA);
    servoMotorReturn = nowMs + 2000;
  }

  if (servoMotorReturn && nowMs > servoMotorReturn) {
    servoMotor.write(SERVO_MOTOR_REPOSO);
    servoMotorReturn = 0;
  }

  // =================================================
  // FLIPPER (TRANSICIÓN REAL 29 → 30)  <<< CAMBIO 1
  // =================================================
  if (!flipperHechoHora &&
      prevMinute == 29 &&
      currentMinute == 30) {

    flipperHechoHora = true;
    flipperAbierto = true;
    bolasContadas = 0;
    bolasObjetivo = bolasPorHora(now.hour());
    lastBallMs = 0;
    servoFlipper.write(53);
  }

  if (now.hour() != lastHour) {
    lastHour = now.hour();
    flipperHechoHora = false;
    campanadaHechaHora = false;
  }

  // =================================================
  // CONTEO DE BOLAS (SIN CAMBIOS)
  // =================================================
  if (flipperAbierto &&
      botonPulsado(BTN_CB, PIN_BTN_CUENTABOLAS) &&
      (nowMs - lastBallMs) > BALL_GUARD_MS) {

    lastBallMs = nowMs;
    bolasContadas++;

    if (bolasContadas >= bolasObjetivo) {
      flipperAbierto = false;
      servoFlipper.write(0);
    }
  }

  // =================================================
  // CAMPANADAS (SIN CAMBIOS)
  // =================================================
  if (!campanadaHechaHora &&
      currentMinute == 0 &&
      now.second() == CAMPANADA_DELAY_SEG) {

    servoCampanadas.write(55);
    servoCampReturn = nowMs + 12000;
    campanadaHechaHora = true;
  }

  if (servoCampReturn && nowMs > servoCampReturn) {
    servoCampanadas.write(0);
    servoCampReturn = 0;
  }

  // =================================================
  // LCD (SIN CAMBIOS)
  // =================================================
  if (nowMs - lcdLastUpdate > LCD_UPDATE_MS) {

    lcd.setCursor(0, 0);
    char buf0[17];
    snprintf(buf0, sizeof(buf0),
             "%02d:%02d:%02d   ",
             now.hour(), currentMinute, now.second());
    lcd.print(buf0);

    lcd.setCursor(10, 0);
    lcd.print(digitalRead(PIN_BTN_STOP) == LOW ? "STOP" : "RUN ");

    lcd.setCursor(0, 1);

    if (motorBloqueado) {
      lcd.print("ERROR MOTOR !!! ");
    }
    else if (digitalRead(PIN_BTN_DIAG) == LOW) {
      char diag[17];
      snprintf(diag, sizeof(diag),
               "H:%02d M:%02d B:%d/%d",
               now.hour(), currentMinute,
               bolasContadas, bolasObjetivo);
      lcd.print(diag);
    }
    else {
      if (nowMs - lcdLastRotate > LCD_ROTATE_MS) {
        lcdLastRotate = nowMs;
        lcdPage = (lcdPage + 1) % 3;
      }

      if (lcdPage == 0) {
        lcd.print(flipperAbierto ?
          "FLIP: ABIERTO    " :
          "FLIP: CERRADO    ");
      }
      else if (lcdPage == 1) {
        char buf1[17];
        snprintf(buf1, sizeof(buf1),
                 "BOLAS %2d/%-2d     ",
                 bolasContadas, bolasObjetivo);
        lcd.print(buf1);
      }
      else {
        lcd.print(servoCampReturn ?
          "CAMP: ACTIVO     " :
          "CAMP: REPOSO     ");
      }
    }

    lcdLastUpdate = nowMs;
  }

  // =================================================
  // ACTUALIZAR MINUTO PREVIO
  // =================================================
  prevMinute = currentMinute;
}
