#include "StatusLEDModule.h"
#include "MeshService.h"
#include "configuration.h"
#include "mesh/RadioInterface.h"
#include "mesh/Throttle.h"
#include <Arduino.h>

/*
StatusLEDModule manages the device's status LEDs, updating their states based on power and Bluetooth status.
It reflects charging, charged, discharging, and Bluetooth connection states using the appropriate LEDs.
*/
StatusLEDModule *statusLEDModule;

StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule")
{
    bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
    powerStatusObserver.observe(&powerStatus->onNewStatus);
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
    if (inputBroker)
        inputObserver.observe(inputBroker);
#endif
#ifdef LED_LORA
    loraRxObserver.observe(&RadioInterface::loraRxPacketObservable);
#endif
#ifdef NEOPIXEL_STATUS_POWER_PIN
    powerPixel.begin();
    powerPixel.clear();
    powerPixel.show();
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
    pairingPixel.begin();
    pairingPixel.clear();
    pairingPixel.show();
#endif
}

// Helper: write a 1-pixel NeoPixel strand to `color` when stateOn, else clear.
// Kept as a static inline here (rather than a member) so it compiles out
// completely when no NeoPixel status pins are defined.
#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN)
static inline void writeStatusPixel(Adafruit_NeoPixel &pixel, uint32_t color, bool stateOn)
{
    pixel.setPixelColor(0, stateOn ? color : 0);
    pixel.show();
}
#endif

int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
{
    switch (arg->getStatusType()) {
    case STATUS_TYPE_POWER: {
        if (powerStatus->getHasUSB() || powerStatus->getIsCharging()) {
            power_state = charging;
            if (powerStatus->getBatteryChargePercent() >= 100) {
                power_state = charged;
            }
        } else {
            if (powerStatus->getBatteryChargePercent() > 5) {
                power_state = discharging;
            } else {
                power_state = critical;
            }
        }
        break;
    }
    case STATUS_TYPE_BLUETOOTH: {
        switch (bluetoothStatus->getConnectionState()) {
        case meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED: {
            ble_state = unpaired;
            PAIRING_LED_starttime = millis();
            break;
        }
        case meshtastic::BluetoothStatus::ConnectionState::PAIRING: {
            ble_state = pairing;
            PAIRING_LED_starttime = millis();
            break;
        }
        case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: {
            if (ble_state != connected) {
                ble_state = connected;
                PAIRING_LED_starttime = millis();
            }
        }
        }

        break;
    }
    }
    return 0;
};
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
int StatusLEDModule::handleInputEvent(const InputEvent *event)
{
    lastUserbuttonTime = millis();
    return 0;
}
#endif
#ifdef LED_LORA
int StatusLEDModule::handleLoRaRx(uint32_t)
{
    // Briefly flash LED_LORA on each received packet. Turn it on now (we share the main thread with
    // the radio's receive handler, so this is safe) and wake runOnce() at flash end to turn it off.
    digitalWrite(LED_LORA, LED_STATE_ON);
    LORA_LED_state = LED_STATE_ON;
    LORA_LED_starttime = millis();
    setIntervalFromNow(LORA_RX_LED_FLASH_MS);
    return 0;
}
#endif

int32_t StatusLEDModule::runOnce()
{
    my_interval = 1000;

    if (power_state == charging) {
#ifndef POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING
        CHARGE_LED_state = !CHARGE_LED_state;
#endif
    } else if (power_state == charged) {
        CHARGE_LED_state = LED_STATE_ON;
    } else if (power_state == critical) {
        if (Throttle::hasElapsed(POWER_LED_starttime, 30000) && !doing_fast_blink) {
            doing_fast_blink = true;
            POWER_LED_starttime = millis();
        }
        if (doing_fast_blink) {
            PAIRING_LED_state = LED_STATE_OFF;
            CHARGE_LED_state = !CHARGE_LED_state;
            my_interval = 250;
            if (Throttle::hasElapsed(POWER_LED_starttime, 2000)) {
                doing_fast_blink = false;
                CHARGE_LED_state = LED_STATE_OFF;
            }
        }
    } else {
#if defined(LED_HEARTBEAT)
        // If we are using the heartbeat, as in the Thinknode M4, we need to explicitly turn off the charge LED
        // This probably implies that in the future we need to stop re-using this bool for multiple purposes.
        CHARGE_LED_state = LED_STATE_OFF;
#endif
    }
// If we want a LED to be dedicated to the simple hearbeat, we can use that instead of the charge LED
#if defined(LED_HEARTBEAT)
    if (power_state != charging && power_state != charged && !doing_fast_blink && !config.device.led_heartbeat_disabled) {
        if (HEARTBEAT_LED_state == LED_STATE_ON) {
            HEARTBEAT_LED_state = LED_STATE_OFF;
            my_interval = 999;
        } else {
            HEARTBEAT_LED_state = LED_STATE_ON;
            my_interval = 1;
        }
        digitalWrite(LED_HEARTBEAT, HEARTBEAT_LED_state);
    } else {
        HEARTBEAT_LED_state = LED_STATE_OFF;
        digitalWrite(LED_HEARTBEAT, HEARTBEAT_LED_state);
    }
#else
    if (power_state != charging && power_state != charged && !doing_fast_blink && !config.device.led_heartbeat_disabled) {
        if (CHARGE_LED_state == LED_STATE_ON) {
            CHARGE_LED_state = LED_STATE_OFF;
            my_interval = 999;
        } else {
            CHARGE_LED_state = LED_STATE_ON;
            my_interval = 1;
        }
    }
#endif
#ifdef LED_PAIRING
    if (!config.bluetooth.enabled || Throttle::hasElapsed(PAIRING_LED_starttime, 30 * 1000) || doing_fast_blink) {
        PAIRING_LED_state = LED_STATE_OFF;
    } else if (ble_state == unpaired) {
        if (slowTrack) {
            PAIRING_LED_state = !PAIRING_LED_state;
            slowTrack = false;
        } else {
            slowTrack = true;
        }
    } else if (ble_state == pairing) {
        PAIRING_LED_state = !PAIRING_LED_state;
    } else {
        PAIRING_LED_state = LED_STATE_ON;
    }
#endif

    // Override if disabled in config
    if (config.device.led_heartbeat_disabled) {
        CHARGE_LED_state = LED_STATE_OFF;
    }
#ifdef Battery_LED_1
    bool chargeIndicatorLED1 = LED_STATE_OFF;
    bool chargeIndicatorLED2 = LED_STATE_OFF;
    bool chargeIndicatorLED3 = LED_STATE_OFF;
    bool chargeIndicatorLED4 = LED_STATE_OFF;
    if (Throttle::isWithinTimespanMs(lastUserbuttonTime, 10 * 1000) || CHARGE_LED_state == LED_STATE_ON) {
        // should this be off at very low percentages?
        chargeIndicatorLED1 = LED_STATE_ON;
        if (powerStatus && powerStatus->getBatteryChargePercent() >= 25)
            chargeIndicatorLED2 = LED_STATE_ON;
        if (powerStatus && powerStatus->getBatteryChargePercent() >= 50)
            chargeIndicatorLED3 = LED_STATE_ON;
        if (powerStatus && powerStatus->getBatteryChargePercent() >= 75)
            chargeIndicatorLED4 = LED_STATE_ON;
    }
#endif

#if defined(HAS_PMU)
    if (pmu_found && PMU) {
        // blink the axp led
        PMU->setChargingLedMode(CHARGE_LED_state ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF);
    }
#endif

#ifdef PCA_LED_POWER
    io.digitalWrite(PCA_LED_POWER, CHARGE_LED_state);
#endif
#ifdef PCA_LED_ENABLE
    io.digitalWrite(PCA_LED_ENABLE, CHARGE_LED_state);
#endif

#ifdef LED_POWER
#ifdef LED_POWER_CRITICAL
    if (LED_POWER != LED_POWER_CRITICAL) {
        if (power_state == critical) {
            digitalWrite(LED_POWER, 0);
            digitalWrite(LED_POWER_CRITICAL, CHARGE_LED_state);
        } else {
            digitalWrite(LED_POWER, CHARGE_LED_state);
            digitalWrite(LED_POWER_CRITICAL, 0);
        }
    } else {
        digitalWrite(LED_POWER, CHARGE_LED_state);
    }
#else
    digitalWrite(LED_POWER, CHARGE_LED_state);
#endif
#endif

#ifdef LED_PAIRING
    digitalWrite(LED_PAIRING, PAIRING_LED_state);
#endif
#ifdef NEOPIXEL_STATUS_POWER_PIN
    writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, CHARGE_LED_state == LED_STATE_ON);
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
    writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, PAIRING_LED_state == LED_STATE_ON);
#endif

#ifdef RGB_LED_POWER
    if (!config.device.led_heartbeat_disabled) {
        if (CHARGE_LED_state == LED_STATE_ON) {
            ambientLightingThread->setLighting(10, 255, 0, 0);
        } else {
            ambientLightingThread->setLighting(0, 0, 0, 0);
        }
    }
#endif

#ifdef Battery_LED_1
    digitalWrite(Battery_LED_1, chargeIndicatorLED1);
#endif
#ifdef Battery_LED_2
    digitalWrite(Battery_LED_2, chargeIndicatorLED2);
#endif
#ifdef Battery_LED_3
    digitalWrite(Battery_LED_3, chargeIndicatorLED3);
#endif
#ifdef Battery_LED_4
    digitalWrite(Battery_LED_4, chargeIndicatorLED4);
#endif

#ifdef LED_LORA
    // End the LoRa-RX flash once its duration has elapsed; otherwise make sure we come back
    // exactly at flash end (only ever clamp my_interval down, so other LED timing is preserved).
    if (LORA_LED_state == LED_STATE_ON) {
        uint32_t elapsed = millis() - LORA_LED_starttime;
        if (elapsed >= LORA_RX_LED_FLASH_MS) {
            digitalWrite(LED_LORA, LED_STATE_OFF);
            LORA_LED_state = LED_STATE_OFF;
        } else if ((uint32_t)my_interval > LORA_RX_LED_FLASH_MS - elapsed) {
            my_interval = LORA_RX_LED_FLASH_MS - elapsed;
        }
    }
#endif

    return (my_interval);
}

void StatusLEDModule::setPowerLED(bool LEDon)
{

#if defined(HAS_PMU)
    if (pmu_found && PMU) {
        // blink the axp led
        PMU->setChargingLedMode(LEDon ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF);
    }
#endif
    uint8_t ledState = LEDon ? LED_STATE_ON : LED_STATE_OFF;
#ifdef PCA_LED_POWER
    io.digitalWrite(PCA_LED_POWER, ledState);
#endif
#ifdef PCA_LED_ENABLE
    io.digitalWrite(PCA_LED_ENABLE, ledState);
#endif
#ifdef LED_POWER
    digitalWrite(LED_POWER, ledState);
#endif
#ifdef LED_PAIRING
    digitalWrite(LED_PAIRING, ledState);
#endif
#ifdef NEOPIXEL_STATUS_POWER_PIN
    writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, LEDon);
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
    writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, LEDon);
#endif

#ifdef Battery_LED_1
    digitalWrite(Battery_LED_1, ledState);
#endif
#ifdef Battery_LED_2
    digitalWrite(Battery_LED_2, ledState);
#endif
#ifdef Battery_LED_3
    digitalWrite(Battery_LED_3, ledState);
#endif
#ifdef Battery_LED_4
    digitalWrite(Battery_LED_4, ledState);
#endif
}
