#pragma once

#include <unordered_map>

#include <QGraphicsObject>

#include <data/node.h>

namespace Xybrid::UI {
    class PortConnectionObject;
    class PortObject : public QGraphicsObject {
        friend class PortConnectionObject;

        std::shared_ptr<Data::Port> port;
        bool highlighted = false;
        std::unique_ptr<QGraphicsLineItem> dragLine;

        std::unordered_map<PortObject*, PortConnectionObject*> connections;

        void connectTo(PortObject*);
        void setHighlighted(bool, bool hideLabel = false);

    protected:

    public:
        enum { Type = UserType + 101 };
        int type() const override { return Type; }

        static const constexpr qreal portSize = 10;
        static const constexpr qreal portSpacing = 3;

        PortObject(const std::shared_ptr<Data::Port>&);
        ~PortObject() override;

        inline const std::shared_ptr<Data::Port>& getPort() const { return port; }

        void mousePressEvent(QGraphicsSceneMouseEvent*) override;
        void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override;
        void mouseMoveEvent(QGraphicsSceneMouseEvent*) override;

        void hoverEnterEvent(QGraphicsSceneHoverEvent*) override;
        void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;

        void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;

        void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
        QRectF boundingRect() const override;
    };

    class PortConnectionObject : public QGraphicsObject {
        friend class PortObject;

        PortObject* in;
        PortObject* out;
        bool highlighted = false;

        PortConnectionObject(PortObject* in, PortObject* out);

        void updateEnds();
    public:
        ~PortConnectionObject() override;

        void disconnect();

        void hoverEnterEvent(QGraphicsSceneHoverEvent*) override;
        void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;

        void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;

        void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
        QRectF boundingRect() const override;
        QPainterPath shape(qreal width) const;
        QPainterPath shape() const override { return shape(8); }
    };

    class NodeObject : public QGraphicsObject {
        friend class PortObject;

        Q_OBJECT

        std::shared_ptr<Data::Node> node;

        QPointF gadgetSize_{0, 0};

        qreal nameSize() const;

        void onMoved();
        void bringToTop(bool force = false);

        void createPorts();
        void updateGeometry();
    protected:

        void focusInEvent(QFocusEvent*) override;

    public:
        enum { Type = UserType + 100 };
        int type() const override { return Type; }

        std::unique_ptr<QGraphicsItem> inputPortContainer = nullptr;
        std::unique_ptr<QGraphicsItem> outputPortContainer = nullptr;

        bool customChrome = false;
        bool autoPositionPorts = true;
        bool canRename = true;
        bool showName = true;
        bool showPluginName = true;

        QGraphicsRectItem* contents;

        NodeObject(const std::shared_ptr<Data::Node>&);

        inline QPointF gadgetSize() const { return gadgetSize_; }
        void setGadgetSize(QPointF);
        inline void setGadgetSize(QSizeF s) { setGadgetSize(QPointF(s.width(), s.height())); }
        inline void setGadgetSize(qreal w, qreal h) { setGadgetSize(QPointF(w, h)); }
        inline void setGadgetSize() { setGadgetSize(contents->childrenBoundingRect().bottomRight()); }

        void updatePorts() { createPorts(); }

        inline const std::shared_ptr<Data::Node>& getNode() const { return node; }
        void promptDelete();

        void mouseMoveEvent(QGraphicsSceneMouseEvent*) override;

        void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override;
        void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;

        void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
        QRectF boundingRect() const override;

    signals:
        void finalized();
        void postGeometryUpdate();
    };
}
