#pragma once

#include "esphome/core/optional.h"
#include "esphome/core/preferences.h"
#include "esphome/components/mqtt/mqtt_client.h"
#include "ratgdo_uart_esp32.h"

#include "callbacks.h"
#include "common.h"
#include "observable.h"
#include "ratgdo_state.h"

namespace esphome {

class Scheduler;
class InternalGPIOPin;

} // namespace esphome

namespace esphome::ratgdo {
class RATGDOComponent;

namespace secplus2 {

    static const uint8_t PACKET_LENGTH = 19;
    typedef uint8_t WirePacket[PACKET_LENGTH];

    enum class CommandType : uint16_t {
        UNKNOWN = 0x000,
        GET_STATUS = 0x080,
        STATUS = 0x081,
        LOCK = 0x18c,
        DOOR_ACTION = 0x280,
        LIGHT = 0x281,
        GET_OPENINGS = 0x48b,
        OPENINGS = 0x48c // openings = (byte1<<8)+byte2
    };

    inline const char* CommandType_to_string(CommandType e) {
        switch (e) {
            case CommandType::UNKNOWN: return "UNKNOWN";
            case CommandType::GET_STATUS: return "GET_STATUS";
            case CommandType::STATUS: return "STATUS";
            case CommandType::LOCK: return "LOCK";
            case CommandType::DOOR_ACTION: return "DOOR_ACTION";
            case CommandType::LIGHT: return "LIGHT";
            case CommandType::GET_OPENINGS: return "GET_OPENINGS";
            case CommandType::OPENINGS: return "OPENINGS";
            default: return "UNKNOWN";
        }
    }

    inline CommandType to_CommandType(uint16_t t, CommandType unknown) {
        switch (t) {
            case static_cast<uint16_t>(CommandType::UNKNOWN): return CommandType::UNKNOWN;
            case static_cast<uint16_t>(CommandType::GET_STATUS): return CommandType::GET_STATUS;
            case static_cast<uint16_t>(CommandType::STATUS): return CommandType::STATUS;
            case static_cast<uint16_t>(CommandType::LOCK): return CommandType::LOCK;
            case static_cast<uint16_t>(CommandType::DOOR_ACTION): return CommandType::DOOR_ACTION;
            case static_cast<uint16_t>(CommandType::LIGHT): return CommandType::LIGHT;
            case static_cast<uint16_t>(CommandType::GET_OPENINGS): return CommandType::GET_OPENINGS;
            case static_cast<uint16_t>(CommandType::OPENINGS): return CommandType::OPENINGS;
            default: return unknown;
        }
    }

    inline bool operator==(const uint16_t cmd_i, const CommandType& cmd_e) { return cmd_i == static_cast<uint16_t>(cmd_e); }
    inline bool operator==(const CommandType& cmd_e, const uint16_t cmd_i) { return cmd_i == static_cast<uint16_t>(cmd_e); }

    enum class IncrementRollingCode {
        NO,
        YES,
    };

    struct Command {
        CommandType type;
        uint8_t nibble;
        uint8_t byte1;
        uint8_t byte2;

        Command()
            : type(CommandType::UNKNOWN)
        {
        }
        Command(CommandType type_, uint8_t nibble_ = 0, uint8_t byte1_ = 0, uint8_t byte2_ = 0)
            : type(type_)
            , nibble(nibble_)
            , byte1(byte1_)
            , byte2(byte2_)
        {
        }
    };

    class Secplus2 {
    public:
        void setup(RATGDOComponent* ratgdo, Scheduler* scheduler, InternalGPIOPin* rx_pin, InternalGPIOPin* tx_pin);
        void loop();
        void dump_config();
        void on_shutdown();

        void sync();

        void light_action(LightAction action);
        void lock_action(LockAction action);
        void door_action(DoorAction action);

        void query_status();
        void query_openings();

    protected:
        void increment_rolling_code_counter(int delta = 1);
        void set_rolling_code_counter(uint32_t counter);
        void set_client_id(uint64_t client_id);

        optional<Command> read_command();
        void handle_command(const Command& cmd);

        void send_command(Command cmd, IncrementRollingCode increment = IncrementRollingCode::YES);
        template <typename F>
        void send_command(Command cmd, IncrementRollingCode increment, F&& on_sent)
        {
            // Only register the callback if the command will be accepted.
            // If transmit_pending is set the command will be dropped, and
            // a stale callback would fire when the previous pending packet
            // transmits -- executing logic (e.g. the second phase of a
            // door_command) at the wrong time.
            //
            // Register before send_command() because transmit_packet() may
            // succeed immediately and call on_command_sent_.trigger() inline.
            if (this->flags_.transmit_pending) {
                return;
            }
            this->on_command_sent_(std::forward<F>(on_sent));
            this->send_command(cmd, increment);
        }
        void encode_packet(Command cmd, WirePacket& packet);
        bool transmit_packet();

        void door_command(DoorAction action);



        void print_packet(const esphome::LogString* prefix, const WirePacket& packet) const;
        optional<Command> decode_packet(const WirePacket& packet) const;

        void sync_helper(uint32_t start, uint32_t delay, uint8_t tries);

        // 8-byte member first (may require 8-byte alignment on some 32-bit systems)
        uint64_t client_id_ { 0x539 };

        // Pointers (4-byte aligned)
        InternalGPIOPin* tx_pin_;
        InternalGPIOPin* rx_pin_;
        RATGDOComponent* ratgdo_;
        Scheduler* scheduler_;

        // 4-byte members
        uint32_t transmit_pending_start_ { 0 };
        uint32_t rx_msg_start_ { 0 };
        uint32_t rx_last_read_ { 0 };

        // Larger structures
        single_observable<uint32_t> rolling_code_counter_ { 0 };
        ESPPreferenceObject rolling_code_pref_;
        ESPPreferenceObject client_id_pref_;
        std::string mqtt_rolling_code_topic_;
        std::string mqtt_client_id_topic_;
        OnceCallbacks<void()> on_command_sent_;
        RatgdoUART uart_;

        // 19-byte arrays
        WirePacket tx_packet_;
        WirePacket rx_packet_;

        // Small members at the end
        uint16_t rx_byte_count_ { 0 };
        struct {
            uint8_t transmit_pending : 1;
            uint8_t rx_reading_msg : 1;
        } flags_ { 0 };
    };
} // namespace secplus2
} // namespace esphome::ratgdo
