#pragma once
#include "IR_config.h"
#include "IrTxGateTypes.h"

// TODO: Отложенная передача после завершения приема

enum class IR_SendStatus : uint8_t {
    Success = 0,
    PayloadTooLarge,
    EncoderBusy,
    BufferTooLarge,
    ExternalBackendBusy,
    ExternalStartFailed,
    ExternalNoStream,
    ExternalInvalidConfig,
    BuildGateRunsFailed,
    ScaleGateRunsFailed,
    DmaStartFailed,
    EncoderPinUnavailable,
    BufferedStorageInvalid,
};

const char* irSendStatusToString(IR_SendStatus status);

// Структура для возврата результата отправки
struct IR_SendResult {
    bool success;              // Флаг успешности отправки
    uint32_t sendTimeMs;       // Время отправки пакета в миллисекундах
    IR_SendStatus status;      // Детализированный статус старта передачи

    IR_SendResult(bool success = false,
                  uint32_t sendTimeMs = 0,
                  IR_SendStatus status = IR_SendStatus::ExternalStartFailed)
        : success(success), sendTimeMs(sendTimeMs), status(status) {}
};

class IR_DecoderRaw;
class IrTxIsrBufferedStorageBase;
class IR_Encoder : public IR_FOX
{
    friend IR_DecoderRaw;
    static IR_Encoder *head;
    static IR_Encoder *last;
    IR_Encoder *next;
public:
    static HardwareTimer* IR_Timer;

    using IR_TxGateRun = IrTxGateRun;
    enum class TxIsrMode : uint8_t {
        Legacy = 0,
        Buffered = 1
    };

    using ExternalTxBusyFn = bool (*)(void *ctx);
    using ExternalTxStartFn = IR_SendStatus (*)(void *ctx, IR_Encoder *enc, const uint8_t *packet, uint8_t len);
private:
    // uint16_t id; /// @brief Адрес передатчика
public:
    /// @brief Класс передатчика
    /// @param addr Адрес передатчика
    /// @param pin  Вывод передатчика
    /// @param decPair Если задан, конструктор регистрирует этот один приёмник как blind-decoder
    ///        (аналог setBlindDecoders() для одного RX).
    IR_Encoder(uint8_t pin, uint16_t addr = 0, IR_DecoderRaw *decPair = nullptr, bool autoHandle = true);
    static void isr();
    static void begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)() = nullptr);
    /**
     * Глобальный знаменатель: частота таймера TX = carrierFrec × multiply (слотов на период несущей).
     * По умолчанию multiply=2 (как бывшие carrierFrec×2). Задавать до begin/beginClockOnly либо после
     * изменения вызвать retuneCarrierClock() (не менять multiply во время активной передачи).
     */
    static void setCarrierMultiply(uint16_t multiply);
    static uint16_t carrierMultiply();
    /** Повторно применить carrierFrec×multiply к IR_Timer (pause + setOverflow), ISR не перенавешивает. */
    static void retuneCarrierClock();

    /** Максимальный числитель мощности: ⌊multiply/2⌋ (100% в setPowerPercent). */
    static uint16_t maxPowerNumerator();

    /** Числитель N: при открытой огибающей N из multiply тиков HIGH за период несущей. Clamped к maxPowerNumerator(). */
    void setPowerNumerator(uint16_t n);
    uint16_t powerNumerator() const;
    /** p∈[0,100] → ближайший допустимый числитель; 100% даёт N = maxPowerNumerator(). */
    void setPowerPercent(uint8_t p);

    /** Legacy helper: lenTicks в тактах 2×Fc → физические тики (carrierFrec×multiply). Может разбить сегменты. */
    static bool scaleGateRunsToPhysical(IR_TxGateRun* runs, size_t* ioCount, size_t maxRuns, uint16_t multiply);

    /** Configure timer frequency for TX clock (carrierFrec × multiply) without attaching ISR. */
    static void beginClockOnly(HardwareTimer *timer);
    static HardwareTimer* get_IR_Timer();
    /** Call from main loop/tick: if ISR requested carrier stop, pause timer here (not in ISR). */
    static void tick();

    /**
     * Режим внутреннего TX без DMA: false — BSRR + кольцо (direct physical gate-runs builder);
     * true — FSM «налету» + скважность несущей как у буферного пути (подшаги multiply/2 на шаг FSM).
     * По умолчанию включён legacy=true для обратной совместимости. Вызов меняет default и обновляет
     * все зарегистрированные encoder-объекты. Buffered ISR реально используется только если у encoder
     * привязан storage через attachBufferedIsrStorage()/enableBufferedIsr().
     * Выставить до begin/rawSend. Игнорируется при externalTxStartFn.
     */
    static void setTxIsrLegacyMode(bool legacy);
    static bool txIsrLegacyMode();
    void attachBufferedIsrStorage(IrTxIsrBufferedStorageBase& storage);
    void detachBufferedIsrStorage();
    bool hasBufferedIsrStorage() const;
    void enableBufferedIsr(IrTxIsrBufferedStorageBase& storage);
    void disableBufferedIsr();
    TxIsrMode txIsrMode() const;

    /** Optional: register external TX backend (e.g. DMA driver). */
    static void setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx);

    /** Called by external TX backend on actual end of transmission. */
    void externalFinishSend();

    /** Build RLE runs of carrier gate for a packet in logical 2×Fc ticks (no HW access). */
    static size_t buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns);
    /** Build RLE runs directly in physical carrierFrec×multiply ticks (DMA/buffered ISR path). */
    static size_t buildPhysicalGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns, uint16_t multiply);

    void enable();
    void disable();

    void setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count);
    template <size_t N>
    void setBlindDecoders(IR_DecoderRaw *(&decoders)[N])
    {
        static_assert(N <= IR_PAIR_MUTE_MAX_ENCODERS,
                      "IR_Encoder::setBlindDecoders: array size exceeds IR_PAIR_MUTE_MAX_ENCODERS");
        setBlindDecoders(decoders, static_cast<uint8_t>(N));
    }
    IR_SendStatus rawSend(uint8_t *ptr, uint8_t len);

    IR_SendResult sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept = false);
    IR_SendResult sendData(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);
    IR_SendResult sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);


    IR_SendResult sendAccept(uint16_t addrTo, uint8_t customByte = 0);
    IR_SendResult sendRequest(uint16_t addrTo);

    IR_SendResult sendBack(uint8_t data);
    IR_SendResult sendBack(uint8_t *data = nullptr, uint8_t len = 0);
    IR_SendResult sendBackTo(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0);

    // Функция для тестирования времени отправки без фактической отправки
    uint32_t testSendTime(uint16_t addrTo, uint8_t dataByte, bool needAccept = false) const;
    uint32_t testSendTime(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false) const;
    uint32_t testSendTimeFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false) const;
    uint32_t testSendAccept(uint16_t addrTo, uint8_t customByte = 0) const;
    uint32_t testSendRequest(uint16_t addrTo) const;
    uint32_t testSendBack(uint8_t data) const;
    uint32_t testSendBack(uint8_t *data = nullptr, uint8_t len = 0) const;
    uint32_t testSendBackTo(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0) const;

    inline bool isBusy() const { return isSending;}


    ~IR_Encoder();
    volatile bool ir_out_virtual;

    void _isr();
private:
    static volatile bool carrierStopPending;
    static bool txIsrLegacyMode_;
    static uint16_t s_carrierMultiply;
    static void carrierResume();
    static void carrierPauseIfIdle();

    static ExternalTxStartFn externalTxStartFn;
    static ExternalTxBusyFn externalTxBusyFn;
    static void *externalTxCtx;
    IR_SendResult _sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len);

    void refreshBlindDecoderMuteState();
    void registerWithBlindDecoders();
    void sendByte(uint8_t byte, bool *prev, bool LOW_FIRST);
    void addSync(bool *prev, bool *next);
    uint32_t calculateSendTime(uint8_t packSize) const;
    uint32_t testSendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len) const;
    void send_HIGH(bool = 1);
    void send_LOW();
    void send_EMPTY(uint8_t count);

    enum SignalPart : uint8_t
    {
        noSignal = 0,
        preamb = 1,
        data = 2,
        sync = 3
    };

    struct TxFsmState
    {
        uint8_t sendLen = 0;
        uint8_t toggleCounter = 0;
        uint8_t dataBitCounter = 0;
        uint8_t dataByteCounter = 0;
        uint8_t preambFrontCounter = 0;
        uint8_t dataSequenceCounter = 0;
        uint8_t syncSequenceCounter = 0;
        bool syncLastBit = false;
        bool state = LOW;
        uint8_t *currentBitSequence = nullptr;
        SignalPart signal = noSignal;
    };

    static bool txAdvanceBoundary(TxFsmState &st, const uint8_t *sendBufferLocal);
    static bool txAdvanceAfterOutput(TxFsmState &st, const uint8_t *sendBufferLocal);
    static bool txEmitTick(TxFsmState &st, const uint8_t *sendBufferLocal, bool &gateOut);
    void loadTxFsmFromMembers(TxFsmState &st) const;
    void storeTxFsmToMembers(const TxFsmState &st);
    bool shouldUseBufferedIsr() const;

    /** Снимок на старт TX (буферный и legacy путь). */
    uint16_t txPowerSnap_ = 1;
    uint16_t txMultiplySnap_ = 2;

    /** Legacy: физических тиков на один логический шаг FSM = multiply/2. */
    uint16_t legacyPhysPerLogical_ = 1;
    uint16_t legacyPhysCounter_ = 0;
    uint16_t legacySlotInPeriod_ = 0;

    volatile uint16_t powerNumerator_ = 1;
    IrTxIsrBufferedStorageBase* txBufferedCtx_ = nullptr;
    IrTxIsrBufferedStorageBase* txActiveBufferedCtx_ = nullptr;
    TxIsrMode txIsrMode_ = TxIsrMode::Legacy;
    bool txUseBufferedIsr_ = false;

    IR_DecoderRaw *decPair = nullptr;
    IR_DecoderRaw *singleBlindDecoder = nullptr;
    IR_DecoderRaw **blindDecoders = nullptr;
    uint8_t decodersCount = 0;

    uint8_t sendLen = 0;
    uint8_t sendBuffer[dataByteSizeMax]{0}; /// @brief Буффер данных для отправки

    volatile bool isSending = false;
    volatile bool state = LOW; /// @brief Текущий уровень генерации

    volatile uint8_t dataByteCounter = 0;

    volatile uint8_t toggleCounter = 0; /// @brief Счётчик переключений
    volatile uint8_t dataBitCounter = 0;

    volatile uint8_t preambFrontCounter = 0;
    volatile uint8_t dataSequenceCounter = 0;
    volatile uint8_t syncSequenceCounter = 0;
    volatile bool syncLastBit = false;

    struct BitSequence
    {
        uint8_t low;
        uint8_t high;
    };
    static uint8_t bitHigh[2];
    static uint8_t bitLow[2];
    uint8_t *currentBitSequence = bitLow;
    volatile SignalPart signal = noSignal;
};
