#include "TouchManager.h"
#include "Arduino.h"
#include "StepperManager.h"
#include "config.h"
#include "storage.h"

TouchManager touchManager;

TouchManager::TouchManager()
    : _fsmState(TouchState::IDLE), _pressStartTime(0), _releaseTime(0),
      _secondPressStart(0), _lastReading(false), _debouncedState(false),
      _lastChangeTime(0) {}

void TouchManager::begin() {
  pinMode(PIN_TOUCH, INPUT); // TTP223 is open-drain with pull-ups usually, but
                             // ESP32 inputs are fine
}

void TouchManager::update() {
  bool reading = digitalRead(PIN_TOUCH) == HIGH;

  // If the reading has changed, reset the debounce timer
  if (reading != _lastReading) {
    _lastChangeTime = millis();
  }
  _lastReading = reading;

  // Only accept the reading once it has been stable for DEBOUNCE_DELAY ms
  bool stateChanged = false;
  if ((millis() - _lastChangeTime) > DEBOUNCE_DELAY) {
    if (reading != _debouncedState) {
      _debouncedState = reading;
      stateChanged = true;
    }
  }

  unsigned long now = millis();

  switch (_fsmState) {
    case TouchState::IDLE:
      if (stateChanged && _debouncedState) {
        _fsmState = TouchState::PRESSED;
        _pressStartTime = now;
      }
      break;

    case TouchState::PRESSED:
      if (stateChanged && !_debouncedState) {
        // Finger released — check for double tap
        _fsmState = TouchState::RELEASED_WAIT;
        _releaseTime = now;
      } else if (now - _pressStartTime > 10000) {
        // Ultra-long press (10 seconds) - trigger WiFi reset
        Serial.println("[TOUCH] 10s hold! Resetting WiFi credentials but keeping calibration...");
        stepperManager.stop();
        
        // Only clear WiFi to force device back into Setup Mode, 
        // preserving mechanical limits so the button still works afterwards!
        config.setup_complete = false;
        memset(config.wifi_ssid, 0, sizeof(config.wifi_ssid));
        memset(config.wifi_pass, 0, sizeof(config.wifi_pass));
        storage.save();
        
        delay(500);
        ESP.restart();
      } else if (stepperManager.isMoving() && !stepperManager.isRelaxing() &&
                 (now - _pressStartTime > HOLD_THRESHOLD)) {
        // BUG #2 FIX: Hold while moving → emergency stop.
        // Only triggers if motor is performing a user-commanded movement
        // (not during tension relief micro-movement). This gives users a
        // reliable way to instantly halt the blind in an emergency by
        // holding the touch sensor for 1.5 seconds.
        Serial.println("[TOUCH] Hold detected while moving -> EMERGENCY STOP!");
        stepperManager.emergencyStop();
        _fsmState = TouchState::HOLD_TRIGGERED;
      }
      break;

    case TouchState::RELEASED_WAIT:
      if (stateChanged && _debouncedState) {
        // Second press detected within window → double tap
        _fsmState = TouchState::PRESSED_SECOND;
        _secondPressStart = now;
        handleDoubleTap();
      } else if (now - _releaseTime > DOUBLE_TAP_TIMEOUT) {
        // Window expired, it was a single tap
        _fsmState = TouchState::IDLE;
        handleSingleTap();
      }
      break;

    case TouchState::PRESSED_SECOND:
      if (stateChanged && !_debouncedState) {
        // Second release, go back to idle
        _fsmState = TouchState::IDLE;
      } else if (now - _secondPressStart > PRESSED_SECOND_TIMEOUT) {
        // BUG #7 FIX: Safety timeout to escape stuck state.
        // If the touch sensor has electrical noise or the user holds
        // the second press for an extremely long time, force-reset to
        // IDLE so the sensor doesn't appear permanently dead.
        Serial.println("[TOUCH] PRESSED_SECOND safety timeout -> IDLE");
        _fsmState = TouchState::IDLE;
      }
      break;

    case TouchState::HOLD_TRIGGERED:
      // BUG #2: Stay in this state until the user releases the touch sensor.
      // This prevents the WiFi reset (10s) from also firing after the
      // emergency stop, and prevents any further actions until the finger
      // is lifted. No timeout needed — the user MUST release to resume.
      if (stateChanged && !_debouncedState) {
        _fsmState = TouchState::IDLE;
      }
      break;
  }
}

void TouchManager::handleSingleTap() {
  Serial.println("[TOUCH] Single Tap Active!");
  if (!stepperManager.isCalibrated()) {
    Serial.println(
        "[TOUCH] Cannot move - Stepper is uncalibrated! (top == bottom)");
    return;
  }

  // BUG #4 FIX: If the motor is in tension relief (relax phase), cancel
  // the relax and treat this tap as if the motor was idle. The relax
  // micro-movement is invisible to the user, so consuming a tap just to
  // stop it makes the sensor feel unresponsive.
  if (stepperManager.isRelaxing()) {
    Serial.println("[TOUCH] Cancelling tension relief for user input.");
    stepperManager.stopRelax();
    // Fall through to the idle branch below (toggle direction)
  }

  // Toggle state
  if (stepperManager.isMoving()) {
    // BUG #1 FIX: Use stop() for graceful deceleration using the configured
    // acceleration rate (the "braking speed" in settings), instead of
    // emergencyStop() which halts instantly with mechanical shock and
    // disables the motor.
    stepperManager.stop();
  } else {
    // If closed (0-49%), open it. If open (50-100%), close it.
    if (stepperManager.getCurrentPercentage() < 50) {
      stepperManager.open();
    } else {
      stepperManager.close();
    }
  }
}

void TouchManager::handleDoubleTap() {
  Serial.println("[TOUCH] Double Tap Active!");
  if (!stepperManager.isCalibrated()) {
    Serial.println(
        "[TOUCH] Cannot move - Stepper is uncalibrated! (top == bottom)");
    return;
  }

  // BUG #4 FIX: Handle double tap during tension relief — cancel relax
  // and then apply the double-tap toggle logic as if the motor was idle.
  if (stepperManager.isRelaxing()) {
    Serial.println("[TOUCH] Cancelling tension relief for double tap.");
    stepperManager.stopRelax();
    // Fall through to the idle handler below
    handleSingleTap();
    return;
  }

  if (stepperManager.isMoving()) {
    // Determine reverse direction BEFORE stopping, then use instantStop()
    // for instant halt and immediately issue the reverse command.
    // Using instantStop() (not emergencyStop()) avoids the unnecessary
    // motor-disable + flash-write + MQTT-publish overhead since we're
    // about to immediately re-enable the motor for the reverse move.
    int target = stepperManager.getTargetPercentage();
    int current = stepperManager.getCurrentPercentage();
    bool wasOpening = (target > current);
    
    // Instant halt — no deceleration ramp, no motor disable
    stepperManager.instantStop();
    
    if (wasOpening) {
      // It was currently opening, so reverse to close
      Serial.println("[TOUCH] Double Tap -> Instant reverse to CLOSE");
      stepperManager.close();
    } else {
      // It was currently closing, so reverse to open
      Serial.println("[TOUCH] Double Tap -> Instant reverse to OPEN");
      stepperManager.open();
    }
  } else {
    // If double tapped while completely stopped, treat as a single tap
    handleSingleTap();
  }
}
