#pragma once

#include "components/ffmsep/cpdecoder.hh"
#include "components/ffmsep/tactile/tacdec.hh"
#include <chrono>
#include <cstdint>
#include <functional>
#include <future>
#include <memory>
#include <optional>
#include <serial/serial.h>
#include <string>
#include <vector>
using namespace std::chrono_literals;

namespace ffmsep {

namespace persist {
struct WriteResult;
} // namespace persist

struct DecodedFrame {
    CPFrame frame;
    CPCodecID id = CPCodecID::Unknow;
    std::chrono::steady_clock::time_point received_at{};
    std::int64_t pts = 0;
    std::optional<tactile::TactileFrame> tactile;
    std::vector<std::uint16_t> tactile_pressures;
    std::optional<tactile::MatrixSize> tactile_matrix_size;
};

struct CPStreamConfig {
    std::string port;
    std::uint32_t baudrate = 115200;
    serial::Timeout timeout = serial::Timeout::simpleTimeout(50);
    serial::bytesize_t bytesize = serial::eightbits;
    serial::parity_t parity = serial::parity_none;
    serial::stopbits_t stopbits = serial::stopbits_one;
    serial::flowcontrol_t flowcontrol = serial::flowcontrol_none;
    std::size_t read_chunk_size = 256;
    std::size_t packet_queue_capacity = 128;
    std::size_t frame_queue_capacity = 16;
    CPCodecID codec_id = CPCodecID::Unknow;
    std::string codec_name;
    std::vector<std::uint8_t> slave_request_command{};
    std::chrono::milliseconds slave_request_interval{200ms};
};

class CPStreamCore {
public:
    using FrameCallback = std::function<void(std::shared_ptr<DecodedFrame>)>;

    explicit CPStreamCore(CPStreamConfig config = {});
    ~CPStreamCore();

    CPStreamCore(const CPStreamCore&) = delete;
    CPStreamCore& operator=(const CPStreamCore&) = delete;

    bool open(const CPStreamConfig& config);
    bool open();
    bool reopen(const CPStreamConfig& config);
    void close();

    bool start();
    void stop();

    [[nodiscard]] bool is_open() const noexcept;
    [[nodiscard]] bool is_running() const noexcept;

    bool send(const std::vector<std::uint8_t>& data);
    bool send(const std::uint8_t* data, std::size_t size);

    std::optional<std::shared_ptr<DecodedFrame>> try_pop_frame();
    bool wait_for_frame(std::shared_ptr<DecodedFrame>& frame, std::chrono::milliseconds timeout);
    void clear_frames();
    void set_frame_queue_capacity(std::size_t capacity);
    void clear_recorded_frames();
    [[nodiscard]] std::size_t recorded_frame_count() const;
    std::future<persist::WriteResult> export_recorded_frames(const std::string& path,
                                                             bool clear_after_export = false);

    void set_frame_callback(FrameCallback callback);

    [[nodiscard]] CPStreamConfig config() const;
    [[nodiscard]] std::string last_error() const;

    static std::vector<serial::PortInfo> list_available_ports();



private:
    struct Impl;
    std::unique_ptr<Impl> impl_;
};

} // namespace ffmsep
