#include "IR_Encoder.h"
#include "IR_DecoderRaw.h"
#include "IrTxIsrBufferedStorage.h"
#include <string.h>

#if defined(_MSC_VER)
#define IRPROTO_PRAGMA_MESSAGE(text) __pragma(message(text))
#else
#define IRPROTO_PRAGMA_MESSAGE(text) _Pragma(#text)
#endif

#if defined(ARDUINO_ARCH_STM32)
    #if defined(STM32G4xx)
IRPROTO_PRAGMA_MESSAGE(message("[IR-protocol] TX backends: ISR + built-in DMA"))
    #elif defined(STM32F4xx)
IRPROTO_PRAGMA_MESSAGE(message("[IR-protocol] TX backends: ISR only"))
    #else
IRPROTO_PRAGMA_MESSAGE(message("[IR-protocol] TX backends: ISR"))
    #endif
#endif

#define LoopOut 12
#define ISR_Out 10
#define TestOut 13

IR_Encoder *IR_Encoder::head = nullptr;
IR_Encoder *IR_Encoder::last = nullptr;
volatile bool IR_Encoder::carrierStopPending = false;

IR_Encoder::IR_Encoder(uint8_t pin, uint16_t addr, IR_DecoderRaw *decPair, bool autoHandle)
{
    setPin(pin);
    id = addr;
    txIsrMode_ = txIsrLegacyMode_ ? TxIsrMode::Legacy : TxIsrMode::Buffered;
    this->decPair = decPair;
    if (decPair != nullptr)
    {
        singleBlindDecoder = decPair;
        blindDecoders = &singleBlindDecoder;
        decodersCount = 1;
        decPair->encoder = this;
    }
    registerWithBlindDecoders();

    if (autoHandle)
    {
        if (IR_Encoder::head == nullptr)
        {
        IR_Encoder::head = this;
        }
        if (last != nullptr)
        {
        last->next = this;
        }
        last = this;
        
        pinMode(pin, OUTPUT);
    }
    powerNumerator_ = 1;
};
 
HardwareTimer* IR_Encoder::IR_Timer = nullptr;
IR_Encoder::ExternalTxStartFn IR_Encoder::externalTxStartFn = nullptr;
IR_Encoder::ExternalTxBusyFn IR_Encoder::externalTxBusyFn = nullptr;
void *IR_Encoder::externalTxCtx = nullptr;
bool IR_Encoder::txIsrLegacyMode_ = true;
uint16_t IR_Encoder::s_carrierMultiply = 2;

const char* irSendStatusToString(IR_SendStatus status)
{
    switch (status)
    {
    case IR_SendStatus::Success:
        return "Success";
    case IR_SendStatus::PayloadTooLarge:
        return "PayloadTooLarge";
    case IR_SendStatus::EncoderBusy:
        return "EncoderBusy";
    case IR_SendStatus::BufferTooLarge:
        return "BufferTooLarge";
    case IR_SendStatus::ExternalBackendBusy:
        return "ExternalBackendBusy";
    case IR_SendStatus::ExternalStartFailed:
        return "ExternalStartFailed";
    case IR_SendStatus::ExternalNoStream:
        return "ExternalNoStream";
    case IR_SendStatus::ExternalInvalidConfig:
        return "ExternalInvalidConfig";
    case IR_SendStatus::BuildGateRunsFailed:
        return "BuildGateRunsFailed";
    case IR_SendStatus::ScaleGateRunsFailed:
        return "ScaleGateRunsFailed";
    case IR_SendStatus::DmaStartFailed:
        return "DmaStartFailed";
    case IR_SendStatus::EncoderPinUnavailable:
        return "EncoderPinUnavailable";
    case IR_SendStatus::BufferedStorageInvalid:
        return "BufferedStorageInvalid";
    default:
        return "Unknown";
    }
}

void IR_Encoder::setCarrierMultiply(uint16_t multiply)
{
    if (multiply < 2)
    {
        multiply = 2;
    }
    s_carrierMultiply = multiply;
}

uint16_t IR_Encoder::carrierMultiply()
{
    return s_carrierMultiply;
}

void IR_Encoder::retuneCarrierClock()
{
    if (IR_Timer == nullptr)
    {
        return;
    }
    IR_Timer->pause();
    IR_Timer->setOverflow((uint32_t)carrierFrec * (uint32_t)s_carrierMultiply, HERTZ_FORMAT);
    IR_Timer->pause();
}

uint16_t IR_Encoder::maxPowerNumerator()
{
    return static_cast<uint16_t>(s_carrierMultiply / 2U);
}

void IR_Encoder::setPowerNumerator(uint16_t n)
{
    const uint16_t cap = maxPowerNumerator();
    powerNumerator_ = (n > cap) ? cap : n;
}

void IR_Encoder::setPowerPercent(uint8_t p)
{
    if (p > 100U)
    {
        p = 100U;
    }
    const uint16_t cap = maxPowerNumerator();
    const uint32_t n = ((uint32_t)p * (uint32_t)cap + 50U) / 100U;
    powerNumerator_ = static_cast<uint16_t>(n);
}

uint16_t IR_Encoder::powerNumerator() const
{
    return powerNumerator_;
}

bool IR_Encoder::scaleGateRunsToPhysical(IR_TxGateRun* runs, size_t* ioCount, size_t maxRuns, uint16_t multiply)
{
    if (runs == nullptr || ioCount == nullptr || maxRuns == 0)
    {
        return false;
    }
    if (multiply < 2)
    {
        multiply = 2;
    }
    const size_t nIn = *ioCount;
    if (nIn > irproto::kIsrTxMaxGateRuns)
    {
        return false;
    }
    IrTxGateRun copy[irproto::kIsrTxMaxGateRuns];
    memcpy(copy, runs, nIn * sizeof(IrTxGateRun));
    size_t w = 0;
    for (size_t r = 0; r < nIn; r++)
    {
        uint32_t phys = (uint32_t)copy[r].lenTicks * (uint32_t)multiply / 2U;
        if (copy[r].lenTicks > 0 && phys == 0)
        {
            phys = 1;
        }
        const bool g = copy[r].gate;
        while (phys > 0)
        {
            if (w >= maxRuns)
            {
                return false;
            }
            const uint32_t chunk = phys > 65535U ? 65535U : phys;
            runs[w].lenTicks = static_cast<uint16_t>(chunk);
            runs[w].gate = g;
            w++;
            phys -= chunk;
        }
    }
    *ioCount = w;
    return true;
}

void IR_Encoder::setTxIsrLegacyMode(bool legacy)
{
    txIsrLegacyMode_ = legacy;
    const TxIsrMode mode = legacy ? TxIsrMode::Legacy : TxIsrMode::Buffered;
    for (IR_Encoder *p = head; p != nullptr; p = p->next)
    {
        p->txIsrMode_ = mode;
    }
}

bool IR_Encoder::txIsrLegacyMode()
{
    return txIsrLegacyMode_;
}

void IR_Encoder::attachBufferedIsrStorage(IrTxIsrBufferedStorageBase& storage)
{
    txBufferedCtx_ = &storage;
}

void IR_Encoder::detachBufferedIsrStorage()
{
    txBufferedCtx_ = nullptr;
    if (!isSending)
    {
        txActiveBufferedCtx_ = nullptr;
        txUseBufferedIsr_ = false;
    }
}

bool IR_Encoder::hasBufferedIsrStorage() const
{
    return txBufferedCtx_ != nullptr && txBufferedCtx_->isValid();
}

void IR_Encoder::enableBufferedIsr(IrTxIsrBufferedStorageBase& storage)
{
    attachBufferedIsrStorage(storage);
    txIsrMode_ = TxIsrMode::Buffered;
}

void IR_Encoder::disableBufferedIsr()
{
    txIsrMode_ = TxIsrMode::Legacy;
    if (!isSending)
    {
        txActiveBufferedCtx_ = nullptr;
        txUseBufferedIsr_ = false;
    }
}

IR_Encoder::TxIsrMode IR_Encoder::txIsrMode() const
{
    return txIsrMode_;
}

bool IR_Encoder::shouldUseBufferedIsr() const
{
    return txIsrMode_ == TxIsrMode::Buffered &&
           txBufferedCtx_ != nullptr &&
           txBufferedCtx_->isValid();
}

bool IR_Encoder::txAdvanceBoundary(TxFsmState &st, const uint8_t *sendBufferLocal)
{
    while (true)
    {
        switch (st.signal)
        {
        case noSignal:
            st.signal = preamb;
            return false;

        case preamb:
            if (st.preambFrontCounter)
            {
                st.preambFrontCounter--;
                st.toggleCounter = preambToggle;
                st.state = !st.state;
                return true;
            }
            st.signal = data;
            st.state = !LOW;
            continue;

        case data:
            if (st.dataSequenceCounter)
            {
                if (!(st.dataSequenceCounter & 1U))
                {
                    st.currentBitSequence =
                        ((sendBufferLocal[st.dataByteCounter] >> st.dataBitCounter) & 1U) ? bitHigh : bitLow;
                    st.dataBitCounter--;
                }
                st.toggleCounter = st.currentBitSequence[!st.state];
                st.dataSequenceCounter--;
                st.state = !st.state;
                return true;
            }
            st.syncLastBit = ((sendBufferLocal[st.dataByteCounter]) & 1U);
            st.dataByteCounter++;
            st.dataBitCounter = bitPerByte - 1;
            st.dataSequenceCounter = bitPerByte * 2;
            st.signal = sync;
            continue;

        case sync:
            if (st.syncSequenceCounter)
            {
                if (!(st.syncSequenceCounter & 1U))
                {
                    if (st.syncSequenceCounter == 2)
                    {
                        st.currentBitSequence = ((sendBufferLocal[st.dataByteCounter]) & 0b10000000) ? bitLow : bitHigh;
                    }
                    else
                    {
                        st.currentBitSequence = st.syncLastBit ? bitLow : bitHigh;
                        st.syncLastBit = !st.syncLastBit;
                    }
                }
                st.toggleCounter = st.currentBitSequence[!st.state];
                st.syncSequenceCounter--;
                st.state = !st.state;
                return true;
            }
            st.signal = data;
            st.syncSequenceCounter = syncBits * 2;
            if (st.dataByteCounter >= st.sendLen)
            {
                st.signal = noSignal;
            }
            continue;

        default:
            return false;
        }
    }
}

bool IR_Encoder::txAdvanceAfterOutput(TxFsmState &st, const uint8_t *sendBufferLocal)
{
    if (st.toggleCounter)
    {
        st.toggleCounter--;
        return true;
    }
    return txAdvanceBoundary(st, sendBufferLocal);
}

bool IR_Encoder::txEmitTick(TxFsmState &st, const uint8_t *sendBufferLocal, bool &gateOut)
{
    gateOut = st.state;
    return txAdvanceAfterOutput(st, sendBufferLocal);
}

void IR_Encoder::loadTxFsmFromMembers(TxFsmState &st) const
{
    st.sendLen = sendLen;
    st.toggleCounter = toggleCounter;
    st.dataBitCounter = dataBitCounter;
    st.dataByteCounter = dataByteCounter;
    st.preambFrontCounter = preambFrontCounter;
    st.dataSequenceCounter = dataSequenceCounter;
    st.syncSequenceCounter = syncSequenceCounter;
    st.syncLastBit = syncLastBit;
    st.state = state;
    st.currentBitSequence = currentBitSequence;
    st.signal = signal;
}

void IR_Encoder::storeTxFsmToMembers(const TxFsmState &st)
{
    sendLen = st.sendLen;
    toggleCounter = st.toggleCounter;
    dataBitCounter = st.dataBitCounter;
    dataByteCounter = st.dataByteCounter;
    preambFrontCounter = st.preambFrontCounter;
    dataSequenceCounter = st.dataSequenceCounter;
    syncSequenceCounter = st.syncSequenceCounter;
    syncLastBit = st.syncLastBit;
    state = st.state;
    currentBitSequence = st.currentBitSequence;
    signal = st.signal;
}

inline HardwareTimer* IR_Encoder::get_IR_Timer(){return IR_Encoder::IR_Timer;}

void IR_Encoder::carrierResume() {
    if (IR_Timer != nullptr)
        IR_Timer->resume();
}

void IR_Encoder::carrierPauseIfIdle() {
    for (IR_Encoder *p = head; p != nullptr; p = p->next)
        if (p->isSending)
            return;
    if (IR_Timer != nullptr)
        IR_Timer->pause();
}

void IR_Encoder::tick() {
    if (!carrierStopPending)
        return;
    carrierStopPending = false;
    carrierPauseIfIdle();
}

void IR_Encoder::begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)()){
    IR_Timer = timer;
    if(IR_Timer == nullptr) return;
    IR_Timer->pause();
    IR_Timer->setOverflow((uint32_t)carrierFrec * (uint32_t)s_carrierMultiply, HERTZ_FORMAT);
    IR_Timer->attachInterrupt(channel, (isrCallback == nullptr ? IR_Encoder::isr : isrCallback));
    NVIC_SetPriority(IRQn, priority);
    IR_Timer->pause();
}

void IR_Encoder::beginClockOnly(HardwareTimer *timer)
{
    IR_Timer = timer;
    if (IR_Timer == nullptr)
        return;
    IR_Timer->pause();
    IR_Timer->setOverflow((uint32_t)carrierFrec * (uint32_t)s_carrierMultiply, HERTZ_FORMAT);
    IR_Timer->pause();
}

void IR_Encoder::setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx)
{
    externalTxStartFn = startFn;
    externalTxBusyFn = busyFn;
    externalTxCtx = ctx;
}

void IR_Encoder::externalFinishSend()
{
    if (!isSending)
        return;

    // Force output low.
    if (port != nullptr) {
        port->BSRR = ((uint32_t)mask) << 16;
    }

    isSending = false;
    txUseBufferedIsr_ = false;
    txActiveBufferedCtx_ = nullptr;
    refreshBlindDecoderMuteState();
}

size_t IR_Encoder::buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns)
{
    if (packet == nullptr || outRuns == nullptr || maxRuns == 0)
    {
        return 0;
    }
    if (len == 0 || len > dataByteSizeMax)
    {
        return 0;
    }

    // Copy into fixed-size buffer to match original encoder behavior (safe reads past sendLen).
    uint8_t sendBufferLocal[dataByteSizeMax] = {0};
    memcpy(sendBufferLocal, packet, len);

    TxFsmState st{};
    st.sendLen = len;
    st.toggleCounter = preambToggle;
    st.dataBitCounter = bitPerByte - 1;
    st.dataByteCounter = 0;
    st.preambFrontCounter = preambPulse * 2 - 1;
    st.dataSequenceCounter = bitPerByte * 2;
    st.syncSequenceCounter = syncBits * 2;
    st.syncLastBit = false;
    st.signal = preamb;
    st.state = HIGH;
    st.currentBitSequence = bitHigh;

    size_t runCount = 0;
    bool isActive = true;
    while (isActive)
    {
        bool gate = false;
        isActive = txEmitTick(st, sendBufferLocal, gate);

        if (runCount > 0 && outRuns[runCount - 1].gate == gate)
        {
            outRuns[runCount - 1].lenTicks = (uint16_t)(outRuns[runCount - 1].lenTicks + 1U);
        }
        else
        {
            if (runCount >= maxRuns)
            {
                return 0;
            }
            outRuns[runCount].gate = gate;
            outRuns[runCount].lenTicks = 1U;
            runCount++;
        }
    }
    return runCount;
}

size_t IR_Encoder::buildPhysicalGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns, uint16_t multiply)
{
    if (packet == nullptr || outRuns == nullptr || maxRuns == 0)
    {
        return 0;
    }
    if (len == 0 || len > dataByteSizeMax)
    {
        return 0;
    }
    if (multiply < 2)
    {
        multiply = 2;
    }

    // Copy into fixed-size buffer to match original encoder behavior (safe reads past sendLen).
    uint8_t sendBufferLocal[dataByteSizeMax] = {0};
    memcpy(sendBufferLocal, packet, len);

    TxFsmState st{};
    st.sendLen = len;
    st.toggleCounter = preambToggle;
    st.dataBitCounter = bitPerByte - 1;
    st.dataByteCounter = 0;
    st.preambFrontCounter = preambPulse * 2 - 1;
    st.dataSequenceCounter = bitPerByte * 2;
    st.syncSequenceCounter = syncBits * 2;
    st.syncLastBit = false;
    st.signal = preamb;
    st.state = HIGH;
    st.currentBitSequence = bitHigh;

    auto appendPhysicalRun = [&](bool gate, uint32_t logicalLen, size_t& runCount) -> bool {
        if (logicalLen == 0)
        {
            return true;
        }

        uint32_t phys = (logicalLen * (uint32_t)multiply) / 2U;
        if (logicalLen > 0 && phys == 0)
        {
            phys = 1;
        }

        while (phys > 0)
        {
            if (runCount >= maxRuns)
            {
                return false;
            }

            const uint32_t chunk = phys > 65535U ? 65535U : phys;
            outRuns[runCount].gate = gate;
            outRuns[runCount].lenTicks = static_cast<uint16_t>(chunk);
            runCount++;
            phys -= chunk;
        }
        return true;
    };

    size_t runCount = 0;
    bool currentGate = false;
    uint32_t currentLogicalLen = 0;
    bool havePendingRun = false;
    bool isActive = true;
    while (isActive)
    {
        bool gate = false;
        isActive = txEmitTick(st, sendBufferLocal, gate);

        if (!havePendingRun)
        {
            currentGate = gate;
            currentLogicalLen = 1U;
            havePendingRun = true;
            continue;
        }

        if (currentGate == gate)
        {
            currentLogicalLen++;
            continue;
        }

        if (!appendPhysicalRun(currentGate, currentLogicalLen, runCount))
        {
            return 0;
        }

        currentGate = gate;
        currentLogicalLen = 1U;
    }

    if (havePendingRun && !appendPhysicalRun(currentGate, currentLogicalLen, runCount))
    {
        return 0;
    }

    return runCount;
}


void IR_Encoder::enable()
{
    bool exist = false;
    IR_Encoder *current = IR_Encoder::head;
    while (current != nullptr)
    {
        exist = (current == this);
        if (exist) break;
        current = current->next;
    }
    if (!exist)
    {
        if (IR_Encoder::head == nullptr)
        {
            IR_Encoder::head = this;
            last = this;
        }
        else
        {
            last->next = this;
            last = this;
        }
        this->next = nullptr; // Указываем, что следующий за этим элементом — nullptr
    }
    pinMode(pin, OUTPUT);
}

void IR_Encoder::disable()
{
    IR_Encoder *current = IR_Encoder::head;
    IR_Encoder *prev = nullptr;

    while (current != nullptr)
    {
        if (current == this) break;
        prev = current;
        current = current->next;
    }

    if (current != nullptr) // Элемент найден в списке
    {
        if (prev != nullptr)
        {
            prev->next = current->next; // Убираем текущий элемент из списка
        }
        else
        {
            IR_Encoder::head = current->next; // Удаляемый элемент был первым
        }

        if (current == last)
        {
            last = prev; // Если удаляется последний элемент, обновляем last
        }
    }

    pinMode(pin, INPUT);
}

void IR_Encoder::setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count)
{
    if (count > IR_PAIR_MUTE_MAX_ENCODERS)
    {
        decodersCount = 0;
        blindDecoders = nullptr;
        return;
    }
    decodersCount = count;
    blindDecoders = decoders;
    registerWithBlindDecoders();
    refreshBlindDecoderMuteState();
}

IR_Encoder::~IR_Encoder(){};

IR_SendResult IR_Encoder::sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept)
{
    return sendData(addrTo, &dataByte, 1, needAccept);
}

IR_SendResult IR_Encoder::sendData(uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept){
    return sendDataFULL(id, addrTo, data, len, needAccept);
}

IR_SendResult IR_Encoder::sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept)
{
    if (len > bytePerPack)
    {
        Serial.println("IR Pack to big");
        return IR_SendResult(false, 0, IR_SendStatus::PayloadTooLarge);
    }
    constexpr uint8_t dataStart = msgBytes + addrBytes + addrBytes;
    memset(sendBuffer, 0x00, dataByteSizeMax);
    uint8_t packSize = msgBytes + addrBytes + addrBytes + len + crcBytes;
    uint8_t msgType =
        ((needAccept ? IR_MSG_DATA_ACCEPT : IR_MSG_DATA_NOACCEPT) << 5) | (packSize & IR_MASK_MSG_INFO);

    // формирование массива
    // msg_type
    sendBuffer[0] = msgType;

    // addr_self
    sendBuffer[1] = addrFrom >> 8 & 0xFF;
    sendBuffer[2] = addrFrom & 0xFF;

    // addr_to
    sendBuffer[3] = addrTo >> 8 & 0xFF;
    sendBuffer[4] = addrTo & 0xFF;

    for (uint16_t i = dataStart; (i < dataStart + len) && (data != nullptr); i++)
    {
        sendBuffer[i] = ((uint8_t *)data)[i - dataStart];
    }

    // data crc
    sendBuffer[packSize - crcBytes] = crc8(sendBuffer, 0, packSize - crcBytes, poly1) & 0xFF;
    sendBuffer[packSize - crcBytes + 1] = crc8(sendBuffer, 0, packSize - crcBytes + 1, poly2) & 0xFF;

    //* вывод итогового буфера
    // Serial.print("IR SEND [len=");
    // Serial.print(packSize);
    // Serial.print("] : ");
    // for (uint8_t i = 0; i < packSize; i++)
    // {
    //     if (sendBuffer[i] < 0x10)
    //         Serial.print('0');
    //     Serial.print(sendBuffer[i], HEX);
    //     Serial.print(' ');
    // }
    // Serial.println();

    // if (decPair != nullptr) {
    //     decPair->isWaitingAccept = ((msgType >> 5) & IR_MASK_MSG_TYPE == IR_MSG_DATA_ACCEPT);
    //     if (decPair->isWaitingAccept) {
    //         decPair->addrWaitingFrom = addrTo;
    //     }
    // }

    // отправка
    const IR_SendStatus status = rawSend(sendBuffer, packSize);
    if (status != IR_SendStatus::Success)
    {
        return IR_SendResult(false, 0, status);
    }

    // Возвращаем результат отправки
    uint32_t sendTime = calculateSendTime(packSize);
    return IR_SendResult(true, sendTime, status);
}


IR_SendResult IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte)
{
    constexpr uint8_t packsize = msgBytes + addrBytes + 1U + crcBytes;
    memset(sendBuffer, 0x00, dataByteSizeMax);
    sendBuffer[0] = IR_MSG_ACCEPT << 5;
    sendBuffer[0] |= packsize & IR_MASK_MSG_INFO; // размер пакета

    // addr_self
    sendBuffer[1] = id >> 8 & 0xFF;
    sendBuffer[2] = id & 0xFF;

    // Serial.print("\nRAW Accept to ");
    // Serial.println(addrTo);

    sendBuffer[3] = customByte;

    // data crc
    sendBuffer[4] = crc8(sendBuffer, 0, 4, poly1) & 0xFF;
    sendBuffer[5] = crc8(sendBuffer, 0, 5, poly2) & 0xFF;

    const IR_SendStatus status = rawSend(sendBuffer, packsize);
    if (status != IR_SendStatus::Success)
    {
        return IR_SendResult(false, 0, status);
    }

    // Возвращаем результат отправки
    uint32_t sendTime = calculateSendTime(packsize);
    return IR_SendResult(true, sendTime, status);
}

IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo)
{
    constexpr uint8_t packsize = msgBytes + addrBytes + addrBytes + crcBytes;
    memset(sendBuffer, 0x00, dataByteSizeMax);
    sendBuffer[0] = IR_MSG_REQUEST << 5;
    sendBuffer[0] |= packsize & IR_MASK_MSG_INFO;

    // addr_self
    sendBuffer[1] = id >> 8 & 0xFF;
    sendBuffer[2] = id & 0xFF;

    // addr_to
    sendBuffer[3] = addrTo >> 8 & 0xFF;
    sendBuffer[4] = addrTo & 0xFF;

    // data crc
    sendBuffer[5] = crc8(sendBuffer, 0, 5, poly1) & 0xFF;
    sendBuffer[6] = crc8(sendBuffer, 0, 6, poly2) & 0xFF;

    const IR_SendStatus status = rawSend(sendBuffer, packsize);
    if (status != IR_SendStatus::Success)
    {
        return IR_SendResult(false, 0, status);
    }

    // Возвращаем результат отправки
    uint32_t sendTime = calculateSendTime(packsize);
    return IR_SendResult(true, sendTime, status);
}

IR_SendResult IR_Encoder::sendBack(uint8_t data)
{
    return _sendBack(false, 0, &data, 1);
}

IR_SendResult IR_Encoder::sendBack(uint8_t *data, uint8_t len)
{
    return _sendBack(false, 0, data, len);
}

IR_SendResult IR_Encoder::sendBackTo(uint16_t addrTo, uint8_t *data, uint8_t len)
{
    return _sendBack(true, addrTo, data, len);
}

IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len)
{
    if (len > bytePerPack)
    {
        return IR_SendResult(false, 0, IR_SendStatus::PayloadTooLarge);
    }
    memset(sendBuffer, 0x00, dataByteSizeMax);
    uint8_t dataStart = msgBytes + addrBytes + (isAdressed ? addrBytes : 0);

    uint8_t packSize = msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + min(uint8_t(1), len) + crcBytes;
    uint8_t msgType =
        ((isAdressed ? IR_MSG_BACK_TO : IR_MSG_BACK) << 5) | ((packSize) & IR_MASK_MSG_INFO);

    // формирование массива
    // msg_type
    sendBuffer[0] = msgType;

    // addr_from or data
    sendBuffer[1] = id >> 8 & 0xFF;
    sendBuffer[2] = id & 0xFF;

    // addr_to
    sendBuffer[3] = addrTo >> 8 & 0xFF;
    sendBuffer[4] = addrTo & 0xFF;

    for (uint16_t i = dataStart; i < dataStart + len; i++)
    {
        sendBuffer[i] = ((uint8_t *)data)[i - dataStart];
    }

    // data crc
    sendBuffer[packSize - crcBytes] = crc8(sendBuffer, 0, packSize - crcBytes, poly1) & 0xFF;
    sendBuffer[packSize - crcBytes + 1] = crc8(sendBuffer, 0, packSize - crcBytes + 1, poly2) & 0xFF;

    // отправка
    const IR_SendStatus status = rawSend(sendBuffer, packSize);
    if (status != IR_SendStatus::Success)
    {
        return IR_SendResult(false, 0, status);
    }

    // Возвращаем результат отправки
    uint32_t sendTime = calculateSendTime(packSize);
    return IR_SendResult(true, sendTime, status);
}

void IR_Encoder::registerWithBlindDecoders()
{
    if (!decodersCount || blindDecoders == nullptr)
        return;

    for (uint8_t i = 0; i < decodersCount; i++)
    {
        if (blindDecoders[i] != nullptr)
            blindDecoders[i]->registerPairMuteEncoder(this);
    }
}

void IR_Encoder::refreshBlindDecoderMuteState()
{
    if (!decodersCount || blindDecoders == nullptr)
        return;

    for (uint8_t i = 0; i < decodersCount; i++)
    {
        if (blindDecoders[i] != nullptr)
            blindDecoders[i]->refreshPairMuteState();
    }
}

IR_SendStatus IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
{
    if (isSending)
    {
        // TODO: Обработка повторной отправки
        return IR_SendStatus::EncoderBusy;
    }
    
    // Проверка на переполнение буфера
    if (len > dataByteSizeMax)
    {
        return IR_SendStatus::BufferTooLarge;
    }

    // Serial.print("IR tx hex: ");
    // for (uint8_t i = 0; i < len; i++)
    // {
    //     if (ptr[i] < 0x10) Serial.print("0");
    //     Serial.print(ptr[i], HEX);
    // }
    // Serial.println();

    if (externalTxStartFn != nullptr)
    {
        if (externalTxBusyFn != nullptr && externalTxBusyFn(externalTxCtx))
        {
            return IR_SendStatus::ExternalBackendBusy;
        }

        sendLen = len;
        txUseBufferedIsr_ = false;
        txActiveBufferedCtx_ = nullptr;
        isSending = true;
        refreshBlindDecoderMuteState();

        const IR_SendStatus status = externalTxStartFn(externalTxCtx, this, ptr, len);
        if (status != IR_SendStatus::Success)
        {
            isSending = false;
            refreshBlindDecoderMuteState();
        }
        return status;
    }

    if (port == nullptr || mask == 0)
    {
        return IR_SendStatus::EncoderPinUnavailable;
    }

    if (ptr != sendBuffer)
    {
        memcpy(sendBuffer, ptr, len);
    }
    sendLen = len;

    const bool useBufferedIsr = shouldUseBufferedIsr();
    txUseBufferedIsr_ = useBufferedIsr;
    txActiveBufferedCtx_ = useBufferedIsr ? txBufferedCtx_ : nullptr;

    if (!useBufferedIsr)
    {
        toggleCounter = preambToggle;
        dataBitCounter = bitPerByte - 1;
        dataByteCounter = 0;
        preambFrontCounter = preambPulse * 2 - 1;
        dataSequenceCounter = bitPerByte * 2;
        syncSequenceCounter = syncBits * 2;
        signal = preamb;
        state = HIGH;
        currentBitSequence = bitHigh;
        txMultiplySnap_ = carrierMultiply();
        {
            const uint16_t cap = maxPowerNumerator();
            txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_;
        }
        legacyPhysPerLogical_ = static_cast<uint16_t>(txMultiplySnap_ / 2U);
        if (legacyPhysPerLogical_ == 0)
        {
            legacyPhysPerLogical_ = 1;
        }
        legacyPhysCounter_ = 0;
        legacySlotInPeriod_ = 0;
        isSending = true;
        refreshBlindDecoderMuteState();
        IR_Encoder::carrierResume();
        return IR_SendStatus::Success;
    }

    IrTxIsrBufferedStorageBase* buf = txActiveBufferedCtx_;
    if (buf == nullptr || !buf->isValid())
    {
        txUseBufferedIsr_ = false;
        txActiveBufferedCtx_ = nullptr;
        return IR_SendStatus::BufferedStorageInvalid;
    }

    buf->resetRuntimeState();

    txMultiplySnap_ = carrierMultiply();
    size_t nRuns = buildPhysicalGateRuns(sendBuffer, len, buf->gateRuns, buf->maxGateRuns, txMultiplySnap_);
    if (nRuns == 0U)
    {
        txUseBufferedIsr_ = false;
        txActiveBufferedCtx_ = nullptr;
        return IR_SendStatus::BuildGateRunsFailed;
    }

    uint32_t total = 0;
    for (size_t i = 0; i < nRuns; i++)
    {
        total += buf->gateRuns[i].lenTicks;
    }
    buf->totalTicks = total;

    const uint32_t setW = (uint32_t)mask;
    const uint32_t resetW = ((uint32_t)mask) << 16U;
    {
        const uint16_t cap = maxPowerNumerator();
        txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_;
    }
    buf->wave.configure(setW, resetW, buf->gateRuns, nRuns, txMultiplySnap_, txPowerSnap_);
    buf->wave.fill(buf->bsrrWords, buf->wordCount);

    isSending = true;
    refreshBlindDecoderMuteState();
    if (port != nullptr)
    {
        port->BSRR = resetW;
    }
    IR_Encoder::carrierResume();
    return IR_SendStatus::Success;
}

void IR_Encoder::isr()
{
    IR_Encoder *current = IR_Encoder::head;
    while (current != nullptr)
    {
      current->_isr();
      current = current->next;
    }
}

void IR_Encoder::_isr()
{
    if (!isSending)
        return;

    if (port == nullptr)
        return;

    if (!txUseBufferedIsr_)
    {
        const uint32_t setW = (uint32_t)mask;
        const uint32_t resetW = ((uint32_t)mask) << 16U;
        if (!state)
        {
            port->BSRR = resetW;
            legacySlotInPeriod_ = 0;
        }
        else
        {
            port->BSRR = (legacySlotInPeriod_ < txPowerSnap_) ? setW : resetW;
            legacySlotInPeriod_++;
            if (legacySlotInPeriod_ >= txMultiplySnap_)
            {
                legacySlotInPeriod_ = 0;
            }
        }

        legacyPhysCounter_++;
        if (legacyPhysCounter_ < legacyPhysPerLogical_)
        {
            return;
        }
        legacyPhysCounter_ = 0;

        TxFsmState st{};
        loadTxFsmFromMembers(st);
        const bool active = txAdvanceAfterOutput(st, sendBuffer);
        storeTxFsmToMembers(st);

        if (!active)
        {
            port->BSRR = resetW;
            isSending = false;
            txUseBufferedIsr_ = false;
            txActiveBufferedCtx_ = nullptr;
            refreshBlindDecoderMuteState();
            carrierStopPending = true;
        }
        return;
    }

    IrTxIsrBufferedStorageBase* buf = txActiveBufferedCtx_;
    if (buf == nullptr || !buf->isValid())
    {
        port->BSRR = ((uint32_t)mask) << 16U;
        isSending = false;
        txUseBufferedIsr_ = false;
        txActiveBufferedCtx_ = nullptr;
        refreshBlindDecoderMuteState();
        carrierStopPending = true;
        return;
    }

    port->BSRR = buf->bsrrWords[buf->readIdx];
    buf->readIdx++;
    buf->ticksSent++;

    if (buf->ticksSent >= buf->totalTicks)
    {
        port->BSRR = ((uint32_t)mask) << 16U;
        isSending = false;
        txUseBufferedIsr_ = false;
        txActiveBufferedCtx_ = nullptr;
        refreshBlindDecoderMuteState();
        carrierStopPending = true;
        return;
    }

    if (buf->readIdx == buf->halfLen)
    {
        buf->wave.fill(&buf->bsrrWords[0], buf->halfLen);
    }
    else if (buf->readIdx >= buf->wordCount)
    {
        buf->readIdx = 0;
        buf->wave.fill(&buf->bsrrWords[buf->halfLen], buf->halfLen);
    }
}

void IR_Encoder::sendByte(uint8_t byte, bool *prev, bool LOW_FIRST)
{
    uint8_t mask = LOW_FIRST ? 0b00000001 : 0b10000000;
    for (uint8_t bitShift = 8; bitShift; bitShift--)
    {
        // digitalWrite(9, HIGH);
        // digitalWrite(9, LOW);
        byte &mask ? send_HIGH(prev) : send_LOW();
        *prev = byte & mask;
        LOW_FIRST ? mask <<= 1 : mask >>= 1;
        // digitalWrite(9, HIGH);
        // digitalWrite(9, LOW);
    }
}

void IR_Encoder::addSync(bool *prev, bool *next)
{
    switch (syncBits)
    {
    case 0:
        break;
    case 1:
        *prev ? send_LOW() : send_HIGH();
        *prev = !*prev;
        break;
    default:
        for (int16_t i = 0; i < syncBits - 1; i++)
        {
            *prev ? send_LOW() : send_HIGH();
            *prev = !*prev;
        }
        *next ? send_LOW() : send_HIGH(0);
        *prev = !*next;
        break;
    }
}

uint8_t IR_Encoder::bitHigh[2] = {
    (bitPauseTakts) * 2 - 1,
    (bitActiveTakts) * 2 - 1};
uint8_t IR_Encoder::bitLow[2] = {
    (bitPauseTakts / 2 + bitActiveTakts) * 2 - 1,
    (bitPauseTakts)-1};

uint32_t IR_Encoder::calculateSendTime(uint8_t packSize) const
{
    // Расчет времени отправки пакета в миллисекундах
    
    // Время преамбулы: preambPulse * 2 фронта * bitTakts тактов
    uint32_t preambTime = preambPulse * 2 * bitTakts;
    
    // Время данных: количество бит * bitTakts тактов
    uint32_t dataTime = packSize * 8 * bitTakts;
    
    // Время синхронизации: syncBits * 2 фронта * bitTakts тактов
    uint32_t syncTime = syncBits * 2 * bitTakts;
    
    // Общее время в тактах
    uint32_t totalTakts = preambTime + dataTime + syncTime;
    
    // Конвертируем в миллисекунды
    // carrierPeriod - период несущей в микросекундах
    // totalTakts * carrierPeriod / 1000 = время в миллисекундах
    uint32_t sendTimeMs = (totalTakts * carrierPeriod) / 1000;
    
    return sendTimeMs;
}

// Функции для тестирования времени отправки без фактической отправки

uint32_t IR_Encoder::testSendTime(uint16_t addrTo, uint8_t dataByte, bool needAccept) const
{
    return testSendTime(addrTo, &dataByte, 1, needAccept);
}

uint32_t IR_Encoder::testSendTime(uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept) const
{
    return testSendTimeFULL(id, addrTo, data, len, needAccept);
}

uint32_t IR_Encoder::testSendTimeFULL(uint16_t addrFrom, uint16_t addrTo, uint8_t *data, uint8_t len, bool needAccept) const
{
    if (len > bytePerPack)
    {
        return 0; // Возвращаем 0 для недопустимого размера
    }
    
    uint8_t packSize = msgBytes + addrBytes + addrBytes + len + crcBytes;
    return calculateSendTime(packSize);
}

uint32_t IR_Encoder::testSendAccept(uint16_t addrTo, uint8_t customByte) const
{
    constexpr uint8_t packsize = msgBytes + addrBytes + 1U + crcBytes;
    return calculateSendTime(packsize);
}

uint32_t IR_Encoder::testSendRequest(uint16_t addrTo) const
{
    constexpr uint8_t packsize = msgBytes + addrBytes + addrBytes + crcBytes;
    return calculateSendTime(packsize);
}

uint32_t IR_Encoder::testSendBack(uint8_t data) const
{
    return testSendBack(false, 0, &data, 1);
}

uint32_t IR_Encoder::testSendBack(uint8_t *data, uint8_t len) const
{
    return testSendBack(false, 0, data, len);
}

uint32_t IR_Encoder::testSendBackTo(uint16_t addrTo, uint8_t *data, uint8_t len) const
{
    return testSendBack(true, addrTo, data, len);
}

uint32_t IR_Encoder::testSendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len) const
{
    if (len > bytePerPack)
    {
        return 0; // Возвращаем 0 для недопустимого размера
    }
    
    uint8_t packSize = msgBytes + addrBytes + (isAdressed ? addrBytes : 0) + min(uint8_t(1), len) + crcBytes;
    return calculateSendTime(packSize);
}

// uint8_t* IR_Encoder::bitHigh = new uint8_t[2]{
//         (bitPauseTakts) * 2 - 0,
//         (bitActiveTakts) * 2 - 0};
// uint8_t* IR_Encoder::bitLow = new uint8_t[2]{
//         (bitPauseTakts/2 + bitActiveTakts) * 2 - 0,
//         (bitPauseTakts) - 0};
