//
// Created by Lenn on 2025/10/14.
//

#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <memory>
#include <mutex>
#include <QString>
#include <QObject>
#include <QMetaObject>
#include <QStringList>
#include <QtCore/Qt>
#include <optional>
#include <string>
#include <qsize.h>
#include <qsizepolicy.h>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <sstream>
#include "component.hh"
#include "cpstream_core.hh"
#include "modern-qt/utility/theme/theme.hh"
#include "modern-qt/utility/wrapper/layout.hh"
#include "modern-qt/utility/wrapper/widget.hh"
#include "components/charts/heatmap.hh"
#include <modern-qt/layout/flow.hh>
#include <modern-qt/layout/linear.hh>
#include <modern-qt/utility/material-icon.hh>
#include <modern-qt/utility/wrapper/mutable-value.hh>
#include <modern-qt/widget/buttons/icon-button.hh>
#include <modern-qt/widget/cards/filled-card.hh>
#include <modern-qt/widget/cards/outlined-card.hh>
#include <modern-qt/widget/image.hh>
#include <modern-qt/widget/shape/wave-circle.hh>
#include <modern-qt/widget/sliders.hh>
#include <modern-qt/widget/switch.hh>
#include <modern-qt/widget/text-fields.hh>
#include <modern-qt/widget/text.hh>
#include <modern-qt/widget/select.hh>
#include "components/ffmsep/tactile/tacdec.hh"

#define DEBUG       0

using namespace creeper;
namespace capro = card::pro;
namespace lnpro = linear::pro;
namespace impro = image::pro;
namespace ibpro = icon_button::pro;
namespace slpro = select_widget::pro;
namespace pwpro = plot_widget::pro;

namespace {

constexpr std::array<std::uint8_t, 14> kSlaveRequestCommand{
    0x55, 0xAA, 0x09, 0x00, 0x34, 0x00, 0xFB,
    0x00, 0x1C, 0x00, 0x00, 0x18, 0x00, 0x7A
};

QVector<PointData> make_flat_points(const QSize& size, double value = 0.0) {
    const int width = std::max(size.width(), 1);
    const int height = std::max(size.height(), 1);
    QVector<PointData> points;
    points.reserve(static_cast<int>(width * height));
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            points.append(PointData{
                static_cast<double>(x),
                static_cast<double>(y),
                value
            });
        }
    }
    return points;
}

std::once_flag& codec_registration_flag() {
    static std::once_flag flag;
    return flag;
}

class SensorStreamController : public QObject {
public:
    SensorStreamController(std::shared_ptr<MutableValue<QVector<PointData>>> heatmap_data,
                           std::shared_ptr<MutableValue<QSize>> matrix_context,
                           QObject* parent = nullptr)
        : QObject(parent)
        , heatmap_data_(std::move(heatmap_data))
        , matrix_context_(std::move(matrix_context)) {
        std::call_once(codec_registration_flag(), [] {
            ffmsep::tactile::register_tactile_codec();
        });
    }

    ~SensorStreamController() override {
        reset_core();
    }

    bool start(const QString& requested_port, std::uint32_t baudrate) {
        if (is_connected()) {
            return true;
        }

        const auto ports = ffmsep::CPStreamCore::list_available_ports();
        std::string port_utf8;
        if (!requested_port.isEmpty()) {
            port_utf8 = requested_port.toStdString();
            const auto it = std::find_if(
                ports.begin(), ports.end(),
                [&](const serial::PortInfo& info) { return info.port == port_utf8; });
            if (it == ports.end()) {
                if (ports.empty()) {
#if DEBUG
                    std::cerr << "SensorStreamController: requested port '" << port_utf8 << "' not available and no other ports detected.\n";
#endif
                    last_error_ = QString::fromUtf8("未检测到串口");
                    return false;
                }
#if DEBUG
                std::cerr << "SensorStreamController: requested port '" << port_utf8 << "' not available, falling back to first detected port.\n";
#endif
                port_utf8 = ports.front().port;
            }
        } else if (!ports.empty()) {
            port_utf8 = ports.front().port;
        } else {
#if DEBUG
            std::cerr << "SensorStreamController: no serial ports available\n";
#endif
            last_error_ = QString::fromUtf8("未检测到串口");
            return false;
        }

        const std::uint32_t baud = baudrate == 0U ? 115200U : baudrate;

        ffmsep::CPStreamConfig cfg;
        cfg.port = port_utf8;
        cfg.baudrate = baud;
        cfg.codec_id = ffmsep::CPCodecID::Tactile;
        cfg.read_chunk_size = 256;
        cfg.packet_queue_capacity = 128;
        cfg.frame_queue_capacity = 32;
        cfg.slave_request_command.assign(kSlaveRequestCommand.begin(), kSlaveRequestCommand.end());
        cfg.slave_request_interval = std::chrono::milliseconds{200};

        reset_core();
        core_ = std::make_unique<ffmsep::CPStreamCore>();

        if (!core_->open(cfg)) {
            last_error_ = QString::fromStdString(core_->last_error());
            std::cerr << "SensorStreamController: open failed - " << core_->last_error() << "\n";
            reset_core();
            return false;
        }

        core_->set_frame_callback([this](std::shared_ptr<ffmsep::DecodedFrame> frame) {
            handle_frame(frame);
        });

        if (!core_->start()) {
            last_error_ = QString::fromStdString(core_->last_error());
            std::cerr << "SensorStreamController: start failed - " << core_->last_error() << "\n";
            reset_core();
            return false;
        }

        active_port_ = QString::fromStdString(cfg.port);
        last_error_.clear();
        connected_ = true;
        return true;
    }

    void stop() {
        reset_core();
        active_port_.clear();
        if (heatmap_data_ && matrix_context_) {
            heatmap_data_->set(make_flat_points(matrix_context_->get()));
        }
        connected_ = false;
    }

    [[nodiscard]] bool is_running() const noexcept {
        return core_ && core_->is_running();
    }

    [[nodiscard]] bool is_connected() const noexcept {
        return connected_;
    }

    [[nodiscard]] QString active_port() const {
        return active_port_;
    }

    [[nodiscard]] QString last_error() const {
        return last_error_;
    }

private:
    void reset_core() {
        connected_ = false;
        if (!core_) {
            return;
        }
        core_->set_frame_callback({});
        if (core_->is_running()) {
            core_->stop();
        }
        if (core_->is_open()) {
            core_->close();
        }
        core_.reset();
    }

    static QSize to_qsize(const ffmsep::tactile::MatrixSize& m) {
        return QSize{
            static_cast<int>(m.long_edge),
            static_cast<int>(m.short_edge)
        };
    }

    void handle_frame(std::shared_ptr<ffmsep::DecodedFrame> frame) {
        if (!frame->tactile || frame->tactile_pressures.empty()) {
            return;
        }

        auto pressures = frame->tactile_pressures;
        auto size_hint = frame->tactile_matrix_size;
        auto frame_bytes = frame->frame.data;
        std::vector<std::uint8_t> raw_payload;
        if (frame->tactile) {
            raw_payload = frame->tactile->payload;
        }

        QMetaObject::invokeMethod(
            this,
            [this,
             pressures = std::move(pressures),
             size_hint,
             frame_bytes = std::move(frame_bytes),
             raw_payload = std::move(raw_payload)]() {
                const auto format_raw = [](const std::vector<std::uint8_t>& data) -> std::string {
                    if (data.empty()) {
                        return "[]";
                    }
                    std::ostringstream oss;
                    oss << '[';
                    oss << std::uppercase << std::setfill('0');
                    for (std::size_t idx = 0; idx < data.size(); ++idx) {
                        if (idx != 0U) {
                            oss << ' ';
                        }
                        oss << std::setw(2) << std::hex << static_cast<unsigned int>(data[idx]);
                    }
                    oss << ']';
                    return oss.str();
                };

                std::cout << "[Sensor] frame=" << format_raw(frame_bytes);
                std::cout << " payload=" << format_raw(raw_payload);
                std::cout << " received " << pressures.size() << " pressure values";
                if (size_hint) {
                    std::cout << " matrix=" << int(size_hint->long_edge)
                              << "x" << int(size_hint->short_edge);
                }
                const std::size_t preview = std::min<std::size_t>(pressures.size(), 12);
                if (preview > 0) {
                    std::cout << " values=[";
                    for (std::size_t idx = 0; idx < preview; ++idx) {
                        if (idx != 0U) {
                            std::cout << ", ";
                        }
                        std::cout << pressures[idx];
                    }
                    if (preview < pressures.size()) {
                        std::cout << ", ...";
                    }
                    std::cout << "]";
                }
                std::cout << std::endl;

                auto matrix = matrix_context_->get();
                if (size_hint) {
                    matrix = to_qsize(*size_hint);
                }
                matrix = normalize_matrix(matrix, pressures.size());
                if (matrix.isEmpty()) {
                    return;
                }

                QVector<PointData> points;
                points.reserve(matrix.width() * matrix.height());
                for (int y = 0; y < matrix.height(); ++y) {
                    for (int x = 0; x < matrix.width(); ++x) {
                        const int idx = y * matrix.width() + x;
                        if (idx >= static_cast<int>(pressures.size())) {
                            break;
                        }
                        const auto value = static_cast<double>(pressures[static_cast<std::size_t>(idx)]);
                        points.append(PointData{
                            static_cast<double>(x),
                            static_cast<double>(y),
                            value
                        });
                    }
                }
                matrix_context_->set(matrix);
                heatmap_data_->set(std::move(points));
            },
            Qt::QueuedConnection);
    }

    [[nodiscard]] QSize normalize_matrix(QSize candidate, std::size_t value_count) const {
        if (value_count == 0U) {
            return QSize{};
        }

        const auto adapt_from = [value_count](const QSize& hint) -> std::optional<QSize> {
            if (hint.width() <= 0 && hint.height() <= 0) {
                return std::nullopt;
            }

            if (hint.width() > 0 && hint.height() > 0) {
                const auto cells = static_cast<std::size_t>(hint.width()) *
                                   static_cast<std::size_t>(hint.height());
                if (cells == value_count) {
                    return hint;
                }
            }

            if (hint.width() > 0) {
                const auto width = static_cast<std::size_t>(hint.width());
                if (width != 0U && (value_count % width) == 0U) {
                    const auto height = static_cast<int>(value_count / width);
                    return QSize{hint.width(), height};
                }
            }

            if (hint.height() > 0) {
                const auto height = static_cast<std::size_t>(hint.height());
                if (height != 0U && (value_count % height) == 0U) {
                    const auto width = static_cast<int>(value_count / height);
                    return QSize{width, hint.height()};
                }
            }

            return std::nullopt;
        };

        if (auto adjusted = adapt_from(candidate)) {
            return *adjusted;
        }

        if (auto adjusted = adapt_from(matrix_context_->get())) {
            return *adjusted;
        }

        const auto root = static_cast<int>(std::sqrt(static_cast<double>(value_count)));
        for (int width = root; width >= 1; --width) {
            const auto divisor = static_cast<std::size_t>(width);
            if (divisor == 0U) {
                continue;
            }
            if ((value_count % divisor) == 0U) {
                const auto height = static_cast<int>(value_count / divisor);
                return QSize{width, height};
            }
        }

        return QSize{static_cast<int>(value_count), 1};
    }

    std::shared_ptr<MutableValue<QVector<PointData>>> heatmap_data_;
    std::shared_ptr<MutableValue<QSize>> matrix_context_;
    std::unique_ptr<ffmsep::CPStreamCore> core_;
    QString active_port_;
    QString last_error_;
    bool connected_ = false;
};

struct SensorUiState {
    std::shared_ptr<MutableValue<QString>> link_icon =
        std::make_shared<MutableValue<QString>>(QString::fromLatin1(material::icon::kAddLink));
    std::shared_ptr<MutableValue<QVector<PointData>>> heatmap_data =
        std::make_shared<MutableValue<QVector<PointData>>>();
    std::shared_ptr<MutableValue<QSize>> heatmap_matrix =
        std::make_shared<MutableValue<QSize>>();
    std::shared_ptr<MutableValue<QStringList>> port_items =
        std::make_shared<MutableValue<QStringList>>();
    QString selected_port;
    std::uint32_t selected_baud = 115200;
    std::unique_ptr<SensorStreamController> controller;

    SensorUiState() {
        const QSize size{3, 4};
        heatmap_matrix->set_silent(size);
        heatmap_data->set_silent(make_flat_points(size));

        // 初始化串口列表
        QStringList ports_list;
        const auto ports = ffmsep::CPStreamCore::list_available_ports();
        ports_list.reserve(static_cast<qsizetype>(ports.size()));
        for (const auto& info : ports) {
            ports_list.emplace_back(QString::fromStdString(info.port));
        }
        port_items->set_silent(ports_list);
        if (selected_port.isEmpty() && !ports_list.isEmpty()) {
            selected_port = ports_list.front();
        }

        controller = std::make_unique<SensorStreamController>(heatmap_data, heatmap_matrix);
    }
};

SensorUiState& sensor_state() {
    static SensorUiState state;
    return state;
}

} // namespace

static auto ComConfigComponent(ThemeManager& manager) {
    auto& sensor = sensor_state();
    auto link_icon_context = sensor.link_icon;

    // 串口下拉：改为绑定可变数据源，初始值由 SensorUiState 构造时填充
    if (sensor.selected_port.isEmpty() && !sensor.port_items->get().isEmpty()) {
        sensor.selected_port = sensor.port_items->get().front();
    }

    const QStringList baud_items{
        QString::fromLatin1("9600"),
        QString::fromLatin1("115200")
    };
    if (sensor.selected_baud == 0U) {
        sensor.selected_baud = 115200U;
    }

    const auto row = new Row {
        // lnpro::Item<FilledTextField> {
        //     text_field::pro::ThemeManager {manager},
        //     text_field::pro::LeadingIcon {material::icon::kSearch, material::regular::font},
        //     MutableForward {
        //         text_field::pro::LabelText {},
        //         slogen_context,
        //     },
        // },
        lnpro::Item<MatSelect> {
            slpro::ThemeManager {manager},
            slpro::LeadingIcon {material::icon::kArrowDropDown, material::regular::font},
            slpro::IndexChanged {[sensor_ptr = &sensor](auto& self){
                const auto text = self.currentText();
                if (!text.isEmpty()) {
                    sensor_ptr->selected_port = text;
                }
            }},
            slpro::LeadingText {"COM"},
            MutableForward {
                slpro::SelectItems {},
                sensor.port_items,
            },
        },
        lnpro::Item<MatSelect> {
            slpro::ThemeManager {manager },
            slpro::LeadingIcon { material::icon::kArrowDropDown, material::regular::font},
            slpro::IndexChanged {[sensor_ptr = &sensor](auto& self){
                bool ok = false;
                const auto text = self.currentText();
                const auto value = text.toUInt(&ok);
                if (ok && value > 0U) {
                    sensor_ptr->selected_baud = static_cast<std::uint32_t>(value);
                }
            }},
            slpro::LeadingText {"Baud"},
            slpro::SelectItems {baud_items},
        },
        lnpro::SpacingItem {20},
        lnpro::Item<IconButton> {
            ibpro::ThemeManager {manager},
            ibpro::FixedSize {40, 40},
            ibpro::Color { IconButton::Color::TONAL },
            ibpro::Font { material::kRoundSmallFont },
            MutableForward {
                icon_button::pro::FontIcon {},
                link_icon_context,
            },
            ibpro::Clickable { [sensor_ptr = &sensor, link_icon_context]{
                auto& sensor = *sensor_ptr;
                if (!sensor.controller) {
                    return;
                }
                if (sensor.controller->is_connected()) {
                    sensor.controller->stop();
                    link_icon_context->set(QString::fromLatin1(material::icon::kAddLink));
                } else {
                    const auto port = sensor.selected_port;
                    const auto baud = sensor.selected_baud == 0U ? 115200U : sensor.selected_baud;
                    if (sensor.controller->start(port, baud)) {
                        sensor.selected_port = sensor.controller->active_port();
                        link_icon_context->set(QString::fromLatin1(material::icon::kLinkOff));
                    } else {
                        std::cerr << "Failed to start sensor stream: "
                                  << sensor.controller->last_error().toStdString()
                                  << "\n";
                    }
                }
            } }
        },
        lnpro::Item<IconButton> {
            ibpro::ThemeManager { manager },
            ibpro::FixedSize { 40, 40 },
            ibpro::Color { IconButton::Color::TONAL },
            ibpro::Font { material::kRoundSmallFont },
            ibpro::FontIcon { material::icon::kRefresh },
            ibpro::Clickable {[&sensor] {
                // 刷新串口列表
                QStringList ports_list;
                const auto ports = ffmsep::CPStreamCore::list_available_ports();
                ports_list.reserve(static_cast<qsizetype>(ports.size()));
                for (const auto& info : ports) {
                    ports_list.emplace_back(QString::fromStdString(info.port));
                }

                // 保持原选择（若仍然存在）
                if (!sensor.selected_port.isEmpty()) {
                    const bool exists = ports_list.contains(sensor.selected_port);
                    if (!exists) {
                        sensor.selected_port = ports_list.isEmpty() ? QString{} : ports_list.front();
                    }
                } else if (!ports_list.isEmpty()) {
                    sensor.selected_port = ports_list.front();
                }

                sensor.port_items->set(std::move(ports_list));
            }},
        },

    };
    return new Widget {
        widget::pro::Layout {row},
    };
}

static auto DisplayComponent(ThemeManager& /*manager*/, int /*index*/ = 0) noexcept {
    auto& sensor = sensor_state();
    const auto row = new Row{
            lnpro::Item<HeatMapPlot> {
                plot_widget::pro::SizePolicy {
                    QSizePolicy::Expanding,
                },
                MutableForward {
                    plot_widget::pro::PlotData {},
                    sensor.heatmap_data,
                },
                pwpro::MatrixSize {
                    sensor.heatmap_matrix->get()
                },
                MutableTransform {
                    [](auto& widget, const QSize& size) {
                        pwpro::MatrixSize{size}.apply(widget);
                    },
                    sensor.heatmap_matrix
                },
            },
    };
    return new Widget {
        widget::pro::Layout{row},
    };
}

auto ViewComponent(ViewComponentState& state) noexcept -> raw_pointer<QWidget> {
    return new FilledCard {
        capro::ThemeManager { state.manager },
        capro::SizePolicy {QSizePolicy::Expanding},
        capro::Layout<Col> {
            lnpro::Alignment {Qt::AlignTop},
            lnpro::Margin {10},
            lnpro::Spacing {10},

            lnpro::Item {
                ComConfigComponent(state.manager),
            },
            
            lnpro::Item<Row> {
                lnpro::Item {
                    DisplayComponent(state.manager),
                },
                lnpro::Item {
                    DisplayComponent(state.manager),
                },
            },
        },
    };
}
