#pragma once

#include "source.h"
#include "signal_buffer.h"

#include <QColor>
#include <QPair>
#include <QWidget>
#include <memory>
#include <vector>

#include "qcustomplot.h"

/**
 * A single plot panel backed by QCustomPlot.
 *
 * Rendering pipeline per trace each timer tick:
 *   1. select ring buffer or archive depending on window coverage
 *   2. read_window_decimated → kRawPts (uniform stride)
 *   3. lttb(kRawPts → kDisplayPts) — visually optimal 3000-point line
 *
 * Zoom behaviour:
 *   - Live mode: rolling window updated each tick UNLESS userZoomed_ is set.
 *   - Zoomed (userZoomed_): axis stays at user range; re-queries buffer with
 *     that range → zooming in gives higher resolution from the ring buffer.
 *   - "Reset Zoom" context menu clears userZoomed_ and returns to live mode.
 *
 * x-range adapts to actual available data so the plot never shows empty space
 * on the left when the signal has just started.
 */
class PlotWidget : public QWidget {
    Q_OBJECT
public:
    explicit PlotWidget(int plotId, QWidget* parent = nullptr);

    int plotId() const { return plotId_; }

    // ── Signal management ─────────────────────────────────────────────
    void addSignal(std::shared_ptr<Source> src, int sigIdx);
    void removeSignal(std::shared_ptr<Source> src, int sigIdx);
    bool hasSignal(const std::shared_ptr<Source>& src, int sigIdx) const;

    /** For saving/restoring traces across layout changes. */
    struct SignalId { std::shared_ptr<Source> src; int sigIdx; };
    std::vector<SignalId> getSignals() const;

    // ── View control (called from MainWindow timer) ───────────────────
    void refresh(double viewTMax, double windowSec, bool paused);

    /** Force the visible range (e.g. when a trigger fires). */
    void setViewRange(double tMin, double tMax);

    /** Return to live rolling window. */
    void resetZoom();

    /** Apply a specific x range (for zoom-back; suppresses history emission). */
    void applyZoomRange(double lo, double hi);

    /** Fit Y axis to currently visible data. */
    void fitYAxis();

    /** Current x axis range. */
    QPair<double,double> currentXRange() const;

    /** Sync X range from another plot (cross-plot zoom). */
    void syncXRange(double lo, double hi);

    /** Push current cursor state to display (called from MainWindow). */
    void refreshCursors(double tA, double tB, bool aActive, bool bActive);

signals:
    void signalDropped(int srcIdx, int sigIdx);
    void xRangeChanged(double lo, double hi);   // emitted on user zoom/pan
    void cursorAPlaced(double t);               // emitted on Ctrl+Left click
    void cursorBPlaced(double t);               // emitted on Ctrl+Right click

protected:
    void dragEnterEvent(QDragEnterEvent* e) override;
    void dropEvent(QDropEvent* e) override;
    bool eventFilter(QObject* obj, QEvent* ev) override;

private slots:
    void onLegendDoubleClick(QCPLegend* legend, QCPAbstractLegendItem* item);
    void onContextMenuRequest(QPoint pos);

private:
    struct Trace {
        std::shared_ptr<Source> src;
        int sigIdx;
        QCPGraph* graphMin  = nullptr;
        QCPGraph* graphMax  = nullptr;
        QCPGraph* graphLine = nullptr;
        size_t    lastHead  = 0;
        double    lastTMin  = 0.0;
        double    lastTMax  = 0.0;
        QColor    color;       // persistent user color (invalid = use palette)
        qreal     lineWidth = 1.5;
    };

    void applyDarkTheme();
    void rebuildTrace(Trace& tr);

    void updateCursorItems();  // update QCPItemLine positions + visibility

    int plotId_;
    QCustomPlot* qcp_ = nullptr;
    std::vector<Trace> traces_;

    // Zoom / sync state
    bool userZoomed_ = false;
    bool zoomGuard_  = false;
    bool syncGuard_  = false;   // prevent re-entrant xRangeChanged during syncXRange

    // A/B cursor items (owned by qcp_, positions set via refreshCursors)
    QCPItemLine* cursorA_ = nullptr;
    QCPItemLine* cursorB_ = nullptr;
    double cursorATime_  = 0.0;
    double cursorBTime_  = 0.0;
    bool   cursorAActive_ = false;
    bool   cursorBActive_ = false;

    // Rendering pipeline scratch buffers
    // kFullResPts: full-res read budget (light path). Must be >= 4*kMaxBuckets so
    //   the light-path threshold never exceeds what read_window can return.
    static constexpr size_t kFullResPts  = 65'536;
    // kRawPts: stride-decimated read budget (heavy path, >kModerateThreshold samples)
    static constexpr size_t kRawPts      = 8'192;
    // kMaxBuckets: number of min-max buckets (moderate path + LTTB output)
    static constexpr size_t kMaxBuckets  = 4'096;
    std::vector<double> tmpT_, tmpV_;       // full-res / stride-decimated intermediary
    std::vector<double> tmpMinV_, tmpMaxV_; // min-max decimation intermediary
    std::vector<double> outT_, outV_;       // LTTB output (resized as needed)
};
