#include "Plunger.h"
#include <Arduino.h> // For millis(), delay()

void Plunger::_transitionToState(PlungerState newState)
{
    if (m_state.getValue() == static_cast<uint16_t>(newState))
        return; // No change, do nothing further.

    Log.verboseln("[%s] State transition: %s -> %s", name.c_str(), _plungerStateToString(static_cast<PlungerState>(m_state.getValue())), _plungerStateToString(newState));
    PlungerState oldState = static_cast<PlungerState>(m_state.getValue());
    
    // Update state and fire event
    m_state.update(static_cast<uint16_t>(newState));
    _lastStateChangeTimeMs = millis();
    _fireEvent(PlungerEvent::STATE_CHANGED, static_cast<int16_t>(newState));
    
    // Specific exit logic for states
    if (oldState == PlungerState::POST_FLOW) {
        // Clean up if we are LEAVING post_flow, regardless of destination
        _currentPostFlowState = PostFlowState::NONE;
        _postFlowSubStateTimer.detach();
    }

    // Default timer resets, will be overridden by specific states if needed
    // unsigned long newOperationStartTimeMs = 0; // Not needed here anymore
    // unsigned long newCurrentMaxOperationTimeMs = 0; // Not needed here anymore

    if (newState != PlungerState::JAMMED)
    {
        _jammedStartTimeMs = 0;
    }

    if (newState == PlungerState::IDLE ||
        newState == PlungerState::STOPPING ||
        newState == PlungerState::JAMMED ||
        newState == PlungerState::RESETTING_JAM ||
        newState == PlungerState::HOMING_AUTO || // Joystick hold reset for auto states too
        newState == PlungerState::PLUNGING_AUTO)
    {
        _joystickHoldStartTimeMs = 0;
    }

    // Specific entry logic for states
    if (newState == PlungerState::JAMMED && oldState != PlungerState::JAMMED) {
        _fireEvent(PlungerEvent::JAM_DETECTED);
    } else if (oldState == PlungerState::JAMMED && newState != PlungerState::JAMMED && newState != PlungerState::RESETTING_JAM) {
        _fireEvent(PlungerEvent::JAM_CLEARED);
    }
    
    if (newState == PlungerState::FILLING && oldState != PlungerState::FILLING) {
        _fireEvent(PlungerEvent::FILL_STARTED);
    } else if (oldState == PlungerState::FILLING && newState != PlungerState::FILLING) {
        _fireEvent(PlungerEvent::FILL_COMPLETED);
    }
    
    if (newState == PlungerState::RECORD && oldState != PlungerState::RECORD) {
        _fireEvent(PlungerEvent::RECORD_STARTED);
    } else if (oldState == PlungerState::RECORD && newState != PlungerState::RECORD) {
        _fireEvent(PlungerEvent::RECORD_COMPLETED, static_cast<int16_t>(_recordedPlungeDurationMs));
    }
    
    if (newState == PlungerState::REPLAY && oldState != PlungerState::REPLAY) {
        _fireEvent(PlungerEvent::REPLAY_STARTED);
    } else if (oldState == PlungerState::REPLAY && newState != PlungerState::REPLAY) {
        _fireEvent(PlungerEvent::REPLAY_COMPLETED);
    }
    
    if (newState == PlungerState::POST_FLOW && oldState != PlungerState::POST_FLOW) {
        _fireEvent(PlungerEvent::POST_FLOW_STARTED);
    } else if (oldState == PlungerState::POST_FLOW && newState != PlungerState::POST_FLOW) {
        _fireEvent(PlungerEvent::POST_FLOW_COMPLETED);
    }
    
    // Fire specific operation events for homing and plunging
    if ((newState == PlungerState::HOMING_AUTO || newState == PlungerState::HOMING_MANUAL) && 
        (oldState != PlungerState::HOMING_AUTO && oldState != PlungerState::HOMING_MANUAL)) {
        _fireEvent(PlungerEvent::HOMING_STARTED);
    } else if ((oldState == PlungerState::HOMING_AUTO || oldState == PlungerState::HOMING_MANUAL) && 
               (newState != PlungerState::HOMING_AUTO && newState != PlungerState::HOMING_MANUAL)) {
        _fireEvent(PlungerEvent::HOMING_COMPLETED);
    }
    
    if ((newState == PlungerState::PLUNGING_AUTO || newState == PlungerState::PLUNGING_MANUAL) && 
        (oldState != PlungerState::PLUNGING_AUTO && oldState != PlungerState::PLUNGING_MANUAL)) {
        _fireEvent(PlungerEvent::PLUNGING_STARTED);
    } else if ((oldState == PlungerState::PLUNGING_AUTO || oldState == PlungerState::PLUNGING_MANUAL) && 
               (newState != PlungerState::PLUNGING_AUTO && newState != PlungerState::PLUNGING_MANUAL)) {
        _fireEvent(PlungerEvent::PLUNGING_COMPLETED);
    }
    
    // Fire operation completed when transitioning to IDLE from operational states
    if (newState == PlungerState::IDLE && 
        (oldState == PlungerState::PLUNGING_AUTO || oldState == PlungerState::HOMING_AUTO || 
         oldState == PlungerState::PLUNGING_MANUAL || oldState == PlungerState::HOMING_MANUAL ||
         oldState == PlungerState::STOPPING)) {
        _fireEvent(PlungerEvent::OPERATION_COMPLETED);
    }
    
    if (newState == PlungerState::POST_FLOW) {
        L_INFO("[%s] Initiating POST_FLOW sequence.", name.c_str());
        _vfdStop(); // Initial stop as per PostFlowState::POST_FLOW_STOPPING
        _currentPostFlowState = PostFlowState::POST_FLOW_STOPPING;
        _postFlowSubStateTimer.once_ms(_settings.postFlowStoppingWaitMs, &Plunger::_postFlowSubStateTimerRelay, this);
    }

    if (newState == PlungerState::IDLE || newState == PlungerState::STOPPING) // General cleanup for IDLE/STOPPING
    {
        _currentFillState = FillState::NONE;
        _fillSubStateTimer.detach();
        _currentPostFlowState = PostFlowState::NONE; // Also clean up post-flow sub-state if not already handled by exit logic
        _postFlowSubStateTimer.detach();
    }
    
    // Specific state initializations for timers
    if (newState == PlungerState::HOMING_MANUAL || newState == PlungerState::HOMING_AUTO ||
             newState == PlungerState::PLUNGING_MANUAL || newState == PlungerState::PLUNGING_AUTO)
    {
        // These states might have their own max operation times from the moment they are entered.
        // Or, if a general max time for these states is desired from entry:
        // newOperationStartTimeMs = millis();
        // newCurrentMaxOperationTimeMs = SOME_DEFAULT_MAX_FOR_THESE_STATES; // if applicable
    }

    // Initialize/reset operation timers. They will be set specifically if the new state involves timed motor operation,
    // or by the functions that start specific motor movements within a larger state (e.g., FILLING sub-states).
    _operationStartTimeMs = 0;
    _currentMaxOperationTimeMs = 0;

    // Specific entry logic for states regarding operation timers
    switch (newState)
    {
        case PlungerState::HOMING_MANUAL:
        case PlungerState::HOMING_AUTO:
        case PlungerState::PLUNGING_MANUAL:
        case PlungerState::PLUNGING_AUTO:
            _operationStartTimeMs = millis();
            _currentMaxOperationTimeMs = _settings.defaultMaxOperationDurationMs; // Use from _settings
            break;

        case PlungerState::FILLING:
            _fillOperationStartTimeMs = millis(); // For the overall fill operation.
            // Moving sub-states (PLUNGING, HOMING) will set their own _operationStartTimeMs
            // and _currentMaxOperationTimeMs for the generic timeout check.
            break;

        case PlungerState::POST_FLOW:
            L_INFO("[%s] Initiating POST_FLOW sequence.", name.c_str());
            _vfdStop(); 
            _currentPostFlowState = PostFlowState::POST_FLOW_STOPPING;
            _postFlowSubStateTimer.once_ms(_settings.postFlowStoppingWaitMs, &Plunger::_postFlowSubStateTimerRelay, this);
            // POST_FLOW_STARTING sub-state will set its own _operationStartTimeMs and _currentMaxOperationTimeMs.
            break;
        
        default:
            // For states like IDLE, STOPPING, JAMMED, RESETTING_JAM, timers remain 0.
            break;
    }

    if (newState == PlungerState::IDLE || newState == PlungerState::STOPPING) // General cleanup for IDLE/STOPPING
    {
        _currentFillState = FillState::NONE;
        _fillSubStateTimer.detach();
        _currentPostFlowState = PostFlowState::NONE; // Also clean up post-flow sub-state if not already handled by exit logic
        _postFlowSubStateTimer.detach();
    }
    
    // Specific state initializations for timers were here, now handled in switch or state handlers

    // Apply the determined timer values - ALREADY DONE IN SWITCH OR STATE HANDLERS
    // _operationStartTimeMs = newOperationStartTimeMs;
    // _currentMaxOperationTimeMs = newCurrentMaxOperationTimeMs;

    if (newState == PlungerState::HOMING_AUTO || newState == PlungerState::PLUNGING_AUTO)
    {
        _joystickReleasedSinceAutoStart = false;
    }

    owner->onError(id, static_cast<short>(newState));
}

void Plunger::_handleIdleState()
{
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
    if (joyDir == Joystick::E_POSITION::UP && _lastJoystickDirection != Joystick::E_POSITION::UP)
    {
        _joystickHoldStartTimeMs = millis();
        _vfdStartReverse(static_cast<uint16_t>(_settings.speedSlowHz * 100.0f));
        _transitionToState(PlungerState::HOMING_MANUAL);
    }
    else if (joyDir == Joystick::E_POSITION::DOWN && _lastJoystickDirection != Joystick::E_POSITION::DOWN)
    {
        _joystickHoldStartTimeMs = millis();
        _vfdStartForward(static_cast<uint16_t>(_calculatedPlungingSpeedHz));
        _transitionToState(PlungerState::PLUNGING_MANUAL);
    }
    else if (joyDir == Joystick::E_POSITION::LEFT && _lastJoystickDirection != Joystick::E_POSITION::LEFT)
    {
        if (m_state.getValue() == static_cast<uint16_t>(PlungerState::IDLE)) { 
            _joystickHoldStartTimeMs = millis(); 
            _joystickFillHoldTimer.once_ms(_settings.fillJoystickHoldDurationMs, &Plunger::_joystickFillHoldTimerRelay, this);
        } 
    }
    else if (_lastJoystickDirection == Joystick::E_POSITION::LEFT && joyDir != Joystick::E_POSITION::LEFT)
    {
        if (_joystickHoldStartTimeMs != 0) { 
            _joystickFillHoldTimer.detach();
            _joystickHoldStartTimeMs = 0; 
        }
    }
    else if (joyDir == Joystick::E_POSITION::RIGHT && _lastJoystickDirection != Joystick::E_POSITION::RIGHT)
    {
        _joystickHoldStartTimeMs = millis(); 
        _joystickRecordHoldTimer.once_ms(_settings.recordHoldDurationMs, &Plunger::_joystickRecordHoldTimerRelay, this);
    }
    else if (_lastJoystickDirection == Joystick::E_POSITION::RIGHT && joyDir != Joystick::E_POSITION::RIGHT) 
    {
        _joystickRecordHoldTimer.detach(); 
        unsigned long heldDuration = (_joystickHoldStartTimeMs > 0) ? (millis() - _joystickHoldStartTimeMs) : 0; 
        _joystickHoldStartTimeMs = 0; 
        if (heldDuration < _settings.recordHoldDurationMs && heldDuration > 50) { 
            if (_recordedPlungeDurationMs > 0) {
                _transitionToState(PlungerState::REPLAY);
            } 
        } 
    }

    if (_vfd->isRunning() && _joystickHoldStartTimeMs == 0 && joyDir == Joystick::E_POSITION::CENTER && m_state.getValue() == static_cast<uint16_t>(PlungerState::IDLE))
    {
        Log.warningln("[%s] IDLE: VFD unexpectedly running. Stopping.", name.c_str());
        _vfdStop();
    }
}

void Plunger::_handleHomingManualState()
{
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
    unsigned long currentTimeMs = millis(); 
    if (joyDir == Joystick::E_POSITION::UP)
    {
        if (_autoModeEnabled && _joystickHoldStartTimeMs > 0 && (currentTimeMs - _joystickHoldStartTimeMs > _settings.autoModeHoldDurationMs)) // Use from _settings
        {
            _transitionToState(PlungerState::HOMING_AUTO);
        }
    }
    else
    {
        _transitionToState(PlungerState::STOPPING);
    }
}

void Plunger::_handleHomingAutoState()
{
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
    Joystick::E_POSITION initialDir = Joystick::E_POSITION::UP; // For homing

    if (!_joystickReleasedSinceAutoStart)
    { // Phase 1: Joystick hasn't been centered yet since auto start
        if (joyDir == initialDir)
        {
            // Still holding initial direction, all good.
        }
        else if (joyDir == Joystick::E_POSITION::CENTER)
        {
            _joystickReleasedSinceAutoStart = true; // First release to center, mark and continue.
        }
        else
        {
            _transitionToState(PlungerState::STOPPING);
        }
    }
    else
    { // Phase 2: Joystick has been centered at least once
        if (joyDir != Joystick::E_POSITION::CENTER)
        {
            // Joystick moved away from CENTER after being released there. This is the "change again". Abort.
            _transitionToState(PlungerState::STOPPING);
        }
        // If joyDir is still CENTER, do nothing, continue auto mode.
    }
}

void Plunger::_handlePlungingManualState()
{
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
    unsigned long currentTimeMs = millis(); 

    if (joyDir == Joystick::E_POSITION::DOWN)
    {
        if (_autoModeEnabled && _joystickHoldStartTimeMs > 0 && (currentTimeMs - _joystickHoldStartTimeMs > _settings.autoModeHoldDurationMs)) // Use from _settings
        {
            _transitionToState(PlungerState::PLUNGING_AUTO);
        }
    }
    else
    {
        _transitionToState(PlungerState::STOPPING);
    }
}

void Plunger::_handlePlungingAutoState()
{
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
    Joystick::E_POSITION initialDir = Joystick::E_POSITION::DOWN; // For plunging

    if (!_joystickReleasedSinceAutoStart)
    { // Phase 1: Joystick hasn't been centered yet since auto start
        if (joyDir == initialDir)
        {
            // Still holding initial direction, all good.
        }
        else if (joyDir == Joystick::E_POSITION::CENTER)
        {
            _joystickReleasedSinceAutoStart = true; // First release to center, mark and continue.
        }
        else
        {
            // Moved from initial direction to something other than CENTER. Abort.
            _transitionToState(PlungerState::STOPPING);
        }
    }
    else
    { // Phase 2: Joystick has been centered at least once
        if (joyDir != Joystick::E_POSITION::CENTER)
        {
            _transitionToState(PlungerState::STOPPING);
        }
        // If joyDir is still CENTER, do nothing, continue auto mode.
    }
}

void Plunger::_handleStoppingState()
{
    _vfdStop();
    _joystickHoldStartTimeMs = 0;
    _transitionToState(PlungerState::IDLE);
}

void Plunger::_handleJammedState()
{
    _vfdResetJam(); 
    _vfdStop();
    _joystickHoldStartTimeMs = 0;
    _transitionToState(PlungerState::RESETTING_JAM);    
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
}

void Plunger::_handleResettingJamState()
{
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
    if (joyDir == Joystick::E_POSITION::UP && _lastJoystickDirection != Joystick::E_POSITION::UP)
    {
        _vfdResetJam();
        _joystickHoldStartTimeMs = millis();
        _vfdStartReverse(static_cast<uint16_t>(_settings.speedSlowHz * 100.0f));
        _transitionToState(PlungerState::HOMING_MANUAL);
    }
    else if (joyDir == Joystick::E_POSITION::CENTER)
    {
        // User is waiting or deciding, do nothing, VFD is stopped from _handleJammedState
    }
    else if (joyDir != Joystick::E_POSITION::UP && _lastJoystickDirection == Joystick::E_POSITION::CENTER)
    {
        // Joystick moved from CENTER to something other than UP (e.g., DOWN, LEFT, RIGHT)
        // This is considered an intentional action to exit the JAMMED/RESETTING_JAM sequence without homing.
        L_INFO("[%s] Resetting Jam: Joystick moved from CENTER not to UP. Returning to IDLE. Manual VFD reset might be needed.", name.c_str());
        _vfdResetJam(); // Attempt reset one last time just in case
        _transitionToState(PlungerState::IDLE);
    }
    // If joystick remains UP, or moves from UP to CENTER then back to UP, it stays in HOMING_MANUAL (or transitions there)
    // If joystick is moved from UP to CENTER and stays CENTER, HOMING_MANUAL will transition to STOPPING then IDLE.
}

void Plunger::_handleRecordState()
{
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
    if (_recordModeStartTimeMs == 0) {
        // L_INFO("[%s] RECORD: Starting plunge.", name.c_str());
        _vfdStartForward(static_cast<uint16_t>(_calculatedPlungingSpeedHz));
        _recordModeStartTimeMs = millis(); // For calculating recorded duration
        _operationStartTimeMs = millis(); // For generic timeout check
        _currentMaxOperationTimeMs = _settings.maxRecordDurationMs; // Use from _settings
    }
    if (joyDir != Joystick::E_POSITION::RIGHT) {
        _vfdStop();
        _recordedPlungeDurationMs = millis() - _recordModeStartTimeMs;
        L_INFO("[%s] RECORD complete. Duration: %lu ms.", name.c_str(), _recordedPlungeDurationMs);
        _recordModeStartTimeMs = 0; 
        _operationStartTimeMs = 0;    
        _currentMaxOperationTimeMs = 0;
        _transitionToState(PlungerState::IDLE);
        return;
    }
}

void Plunger::_handleReplayState()
{
    if (_operationStartTimeMs == 0) { // First entry or re-entry after interruption for some reason
        if (_recordedPlungeDurationMs <= 50) { // Check for a minimal valid duration
            Log.warningln("[%s] REPLAY: Invalid or zero replay duration (%lu ms). -> IDLE", name.c_str(), _recordedPlungeDurationMs);
            _transitionToState(PlungerState::IDLE);
            return;
        }
        L_INFO("[%s] REPLAY: Plunging for %lu ms.", name.c_str(), _recordedPlungeDurationMs);
        _vfdStartForward(static_cast<uint16_t>(_calculatedPlungingSpeedHz));
        _operationStartTimeMs = millis(); // For generic timeout check
        _currentMaxOperationTimeMs = _recordedPlungeDurationMs; // Specific timeout for replay
        _replayPlungeTimer.once_ms(_recordedPlungeDurationMs, &Plunger::_replayPlungeTimerRelay, this);
    }
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
    if (joyDir != Joystick::E_POSITION::CENTER && _operationStartTimeMs != 0) { 
        L_INFO("[%s] REPLAY: Interrupted by joystick.", name.c_str());
        _vfdStop();
        _replayPlungeTimer.detach(); 
        _operationStartTimeMs = 0;   
        _transitionToState(PlungerState::STOPPING); 
    }
}

void Plunger::_handleFillingState()
{
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
    // Log.verboseln("[%s] FILLING: JoyDir=%d, ReleasedSinceStart=%d, FillState=%s", 
    //                name.c_str(), static_cast<int>(joyDir), 
    //                _joystickReleasedSinceAutoStart, 
    //                _fillStateToString(_currentFillState)); // DEBUG REMOVED

    if (m_state.getValue() != static_cast<uint16_t>(PlungerState::FILLING)) { 
        // Log.warningln("[%s] FILLING: Unexpected current state %s! -> IDLE", name.c_str(), _plungerStateToString(_currentState)); // DEBUG REMOVED (can be a regular warning if this case is problematic)
        _currentFillState = FillState::NONE; 
        _fillSubStateTimer.detach();
        _transitionToState(PlungerState::IDLE);
        return;
    }

    if (!_joystickReleasedSinceAutoStart && joyDir == Joystick::E_POSITION::CENTER) {
        // L_INFO("[%s] FILLING: Joystick detected at CENTER. Setting ReleasedSinceAutoStart = true.", name.c_str()); // DEBUG REMOVED
        _joystickReleasedSinceAutoStart = true;
    }

    if (_joystickReleasedSinceAutoStart && joyDir != Joystick::E_POSITION::CENTER) { 
        // L_INFO("[%s] FILLING sequence: Interrupted by joystick (JoyDir=%d after release). Current FillState: %s -> STOPPING", 
        //            name.c_str(), static_cast<int>(joyDir), _fillStateToString(_currentFillState)); // DEBUG REMOVED (original log was info, can be restored if needed)
        _vfdStop();
        _fillSubStateTimer.detach();
        _currentFillState = FillState::NONE; 
        _transitionToState(PlungerState::STOPPING); 
        return;
    }
    // ... (placeholder for total fill operation timeout can remain commented)
}

// Static relay implementations for Ticker callbacks
void Plunger::_joystickRecordHoldTimerRelay(Plunger* pThis) {
    if (pThis) {
        pThis->_onJoystickRecordHoldTimeout();
    }
}

void Plunger::_replayPlungeTimerRelay(Plunger* pThis) {
    if (pThis) {
        pThis->_onReplayPlungeTimeout();
    }
}

void Plunger::_fillSubStateTimerRelay(Plunger* pThis) { 
    if (pThis) {
        pThis->_onFillSubStateTimeout();
    }
}

// Actual timer event handler implementations
void Plunger::_onJoystickRecordHoldTimeout() {
    if (static_cast<Joystick::E_POSITION>(_joystick->getValue()) == Joystick::E_POSITION::RIGHT && m_state.getValue() == static_cast<uint16_t>(PlungerState::IDLE)) {
         L_INFO("[%s] RECORD initiated from joystick.", name.c_str());
         _transitionToState(PlungerState::RECORD);
    } 
    _joystickHoldStartTimeMs = 0;
}

void Plunger::_onReplayPlungeTimeout() {
    this->_vfdStop();
    this->_operationStartTimeMs = 0; 

    if (_settings.enablePostFlow) {
        L_INFO("[%s] Replay plunge finished. Post-flow enabled. -> POST_FLOW", name.c_str());
        this->_transitionToState(PlungerState::POST_FLOW);
    } else {
        L_INFO("[%s] Replay plunge finished. Post-flow disabled. -> IDLE", name.c_str());
        this->_transitionToState(PlungerState::IDLE);
    }
}

void Plunger::_onFillSubStateTimeout() {
    if (m_state.getValue() != static_cast<uint16_t>(PlungerState::FILLING)) {
        Log.warningln("[%s] Fill Sub-State Timeout: Not in FILLING state. Aborting timer logic.", name.c_str());
        _fillSubStateTimer.detach();
        _currentFillState = FillState::NONE; 
        return; 
    }
    switch (_currentFillState) {
        case FillState::PLUNGED: 
            // L_INFO("[%s] Fill Sub-State: PLUNGED wait over. -> HOMING.", name.c_str());
            _currentFillState = FillState::HOMING;
            _vfdStartReverse(static_cast<uint16_t>(_settings.speedFillHomeHz * 100.0f));
            _operationStartTimeMs = millis();
            _currentMaxOperationTimeMs = _settings.defaultMaxOperationDurationMs;
            break;
        case FillState::HOMED: 
            L_INFO("[%s] FILLING sequence complete.", name.c_str());
            _currentFillState = FillState::NONE;
            _transitionToState(PlungerState::IDLE); 
            break;
        default:
            Log.warningln("[%s] Fill Sub-State Timeout: Unhandled FillState: %s in PlungerState: %s. Aborting. -> IDLE", 
                        name.c_str(), _fillStateToString(_currentFillState), _plungerStateToString(static_cast<PlungerState>(m_state.getValue())));
            _fillSubStateTimer.detach(); 
            _currentFillState = FillState::NONE; 
            _transitionToState(PlungerState::IDLE); 
            break;
    }
}

// Ticker relay and handler for Fill joystick hold
void Plunger::_joystickFillHoldTimerRelay(Plunger* pThis) {
    if (pThis) {
        pThis->_onJoystickFillHoldTimeout();
    }
}

void Plunger::_onJoystickFillHoldTimeout() {
    if (static_cast<Joystick::E_POSITION>(_joystick->getValue()) == Joystick::E_POSITION::LEFT && m_state.getValue() == static_cast<uint16_t>(PlungerState::IDLE)) {
        L_INFO("[%s] FILLING sequence initiated from joystick.", name.c_str());
        // _fillOperationStartTimeMs is set in _transitionToState(FILLING)
        _currentFillState = FillState::PLUNGING;
        _vfdStartForward(static_cast<uint16_t>(_settings.speedFillPlungeHz * 100.0f));
        _operationStartTimeMs = millis();
        _currentMaxOperationTimeMs = _settings.defaultMaxOperationDurationMs;
        _joystickReleasedSinceAutoStart = false; 
        _transitionToState(PlungerState::FILLING);
    } 
    _joystickHoldStartTimeMs = 0; 
}

void Plunger::_handlePostFlowState()
{
    // The primary logic is now handled by the timer callbacks and _checkVfdForJam.
    // This handler can remain for joystick interruption logic if needed.
    Joystick::E_POSITION joyDir = static_cast<Joystick::E_POSITION>(_joystick->getValue());
    if (_joystickReleasedSinceAutoStart && joyDir != Joystick::E_POSITION::CENTER) {
        L_INFO("[%s] POST_FLOW interrupted by joystick.", name.c_str());
        _postFlowSubStateTimer.detach();
        _transitionToState(PlungerState::STOPPING);
    }
}

void Plunger::_onPostFlowSubStateTimeout()
{
    if (m_state.getValue() != static_cast<uint16_t>(PlungerState::POST_FLOW)) {
        Log.warningln("[%s] Post-Flow Sub-State Timeout: Not in POST_FLOW state (%s). Aborting timer logic.", name.c_str(), _plungerStateToString(static_cast<PlungerState>(m_state.getValue())));
        _postFlowSubStateTimer.detach();
        _currentPostFlowState = PostFlowState::NONE; 
        return; 
    }

    Log.verboseln("[%s] Post-Flow sub-state timeout. Current sub-state: %d", name.c_str(), static_cast<int>(_currentPostFlowState));

    switch (_currentPostFlowState)
    {
        case PostFlowState::POST_FLOW_STOPPING:
            L_INFO("[%s] Post-Flow: STOPPING wait complete. -> STARTING post-flow press.", name.c_str());
            _currentPostFlowState = PostFlowState::POST_FLOW_STARTING;
            _vfdStartForward(static_cast<uint16_t>(_settings.postFlowSpeedHz * 100.0f));
            _postFlowStartTimeMs = millis();
            _operationStartTimeMs = _postFlowStartTimeMs;
            _currentMaxOperationTimeMs = _settings.postFlowDurationMs;
            _postFlowSubStateTimer.once_ms(_settings.postFlowDurationMs, &Plunger::_postFlowSubStateTimerRelay, this);
            break;

        case PostFlowState::POST_FLOW_STARTING:
            L_INFO("[%s] Post-Flow: STARTING (pressing) complete. -> COMPLETE wait.", name.c_str());
            _vfdStop();
            _currentPostFlowState = PostFlowState::POST_FLOW_COMPLETE;
            _postFlowSubStateTimer.once_ms(_settings.postFlowCompleteWaitMs, &Plunger::_postFlowSubStateTimerRelay, this);
            break;

        case PostFlowState::POST_FLOW_COMPLETE:
            L_INFO("[%s] Post-Flow: COMPLETE wait finished. Post-flow sequence fully complete. -> IDLE", name.c_str());
            _currentPostFlowState = PostFlowState::NONE;
            _transitionToState(PlungerState::IDLE); 
            break;

        case PostFlowState::NONE:
        default:
            Log.warningln("[%s] Post-Flow Sub-State Timeout: Unhandled/NONE PostFlowState: %d. -> IDLE", 
                        name.c_str(), static_cast<int>(_currentPostFlowState));
            _postFlowSubStateTimer.detach(); 
            _currentPostFlowState = PostFlowState::NONE; 
            _transitionToState(PlungerState::IDLE); 
            break;
    }
}

// Static relay for post-flow sub-state timer
void Plunger::_postFlowSubStateTimerRelay(Plunger* pThis) {
    if (pThis) {
        pThis->_onPostFlowSubStateTimeout();
    }
}

