#pragma once

#include "widgets/basewidget.hpp"
#include "widgets/helper/droppreview.hpp"
#include "widgets/helper/notebooktab.hpp"
#include "widgets/split.hpp"

#include <QDragEnterEvent>
#include <QHBoxLayout>
#include <QRect>
#include <QVBoxLayout>
#include <QVector>
#include <QWidget>

#include <algorithm>
#include <functional>
#include <vector>

#include <pajlada/signals/signal.hpp>
#include <pajlada/signals/signalholder.hpp>

class QJsonObject;

namespace chatterino {
namespace widgets {

//
// Note: This class is a spaghetti container. There is a lot of spaghetti code inside but it doesn't
// expose any of it publicly.
//

class SplitContainer : public BaseWidget, pajlada::Signals::SignalHolder
{
    Q_OBJECT

public:
    struct Node;

    // fourtf: !!! preserve the order of left, up, right and down
    enum Direction { Left, Above, Right, Below };

    struct Position final {
    private:
        Position() = default;
        Position(Node *_relativeNode, Direction _direcion)
            : relativeNode(_relativeNode)
            , direction(_direcion)
        {
        }

        Node *relativeNode;
        Direction direction;

        friend struct Node;
        friend class SplitContainer;
    };

private:
    struct DropRect final {
        QRect rect;
        Position position;

        DropRect(const QRect &_rect, const Position &_position)
            : rect(_rect)
            , position(_position)
        {
        }
    };

    struct ResizeRect final {
        QRect rect;
        Node *node;
        bool vertical;

        ResizeRect(const QRect &_rect, Node *_node, bool _vertical)
            : rect(_rect)
            , node(_node)
            , vertical(_vertical)
        {
        }
    };

public:
    struct Node final {
        enum Type { EmptyRoot, _Split, VerticalContainer, HorizontalContainer };

        Type getType();
        Split *getSplit();
        Node *getParent();
        qreal getHorizontalFlex();
        qreal getVerticalFlex();
        const std::vector<std::unique_ptr<Node>> &getChildren();

    private:
        Type type;
        Split *split;
        Node *preferedFocusTarget;
        Node *parent;
        QRectF geometry;
        qreal flexH = 1;
        qreal flexV = 1;
        std::vector<std::unique_ptr<Node>> children;

        Node();
        Node(Split *_split, Node *_parent);

        bool isOrContainsNode(Node *_node);
        Node *findNodeContainingSplit(Split *_split);
        void insertSplitRelative(Split *_split, Direction _direction);
        void nestSplitIntoCollection(Split *_split, Direction _direction);
        void _insertNextToThis(Split *_split, Direction _direction);
        void setSplit(Split *_split);
        Position releaseSplit();
        qreal getFlex(bool isVertical);
        qreal getSize(bool isVertical);
        qreal getChildrensTotalFlex(bool isVertical);
        void layout(bool addSpacing, float _scale, std::vector<DropRect> &dropRects,
                    std::vector<ResizeRect> &resizeRects);

        static Type toContainerType(Direction _dir);

        friend class SplitContainer;
    };

private:
    class DropOverlay final : public QWidget
    {
    public:
        DropOverlay(SplitContainer *_parent = nullptr);

        void setRects(std::vector<SplitContainer::DropRect> _rects);

        pajlada::Signals::NoArgSignal dragEnded;

    protected:
        void paintEvent(QPaintEvent *event) override;
        void dragEnterEvent(QDragEnterEvent *event) override;
        void dragMoveEvent(QDragMoveEvent *event) override;
        void dragLeaveEvent(QDragLeaveEvent *event) override;
        void dropEvent(QDropEvent *event) override;

    private:
        std::vector<DropRect> rects;
        QPoint mouseOverPoint;
        SplitContainer *parent;
    };

    class ResizeHandle final : public QWidget
    {
    public:
        SplitContainer *parent;
        Node *node;

        void setVertical(bool isVertical);
        ResizeHandle(SplitContainer *_parent = nullptr);
        void paintEvent(QPaintEvent *event) override;
        void mousePressEvent(QMouseEvent *event) override;
        void mouseReleaseEvent(QMouseEvent *event) override;
        void mouseMoveEvent(QMouseEvent *event) override;

        friend class SplitContainer;

    private:
        bool vertical;
        bool isMouseDown = false;
    };

public:
    SplitContainer(Notebook *parent);

    void appendNewSplit(bool openChannelNameDialog);
    void appendSplit(Split *split);
    void insertSplit(Split *split, const Position &position);
    void insertSplit(Split *split, Direction direction, Split *relativeTo);
    void insertSplit(Split *split, Direction direction, Node *relativeTo = nullptr);
    Position releaseSplit(Split *split);
    Position deleteSplit(Split *split);

    void selectNextSplit(Direction direction);

    void decodeFromJson(QJsonObject &obj);

    int getSplitCount();
    const std::vector<Split *> getSplits() const;
    void refreshTabTitle();
    NotebookTab *getTab() const;
    Node *getBaseNode();

    void setTab(NotebookTab *tab);
    void hideResizeHandles();
    void resetMouseStatus();

    static bool isDraggingSplit;
    static Split *draggingSplit;

protected:
    void paintEvent(QPaintEvent *event) override;

    void focusInEvent(QFocusEvent *event) override;
    void leaveEvent(QEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;

    void dragEnterEvent(QDragEnterEvent *event) override;

    void resizeEvent(QResizeEvent *event) override;

private:
    struct DropRegion {
        QRect rect;
        std::pair<int, int> position;

        DropRegion(QRect rect, std::pair<int, int> position)
        {
            this->rect = rect;
            this->position = position;
        }
    };

    void addSplit(Split *split);

    std::vector<DropRect> dropRects;
    std::vector<DropRegion> dropRegions;
    NotebookPageDropPreview dropPreview;
    DropOverlay overlay;
    std::vector<std::unique_ptr<ResizeHandle>> resizeHandles;
    QPoint mouseOverPoint;

    void layout();

    Node baseNode;
    Split *selected;
    void setSelected(Split *selected);
    void selectSplitRecursive(Node *node, Direction direction);
    void focusSplitRecursive(Node *node, Direction direction);
    void setPreferedTargetRecursive(Node *node);

    NotebookTab *tab;
    std::vector<Split *> splits;

    bool isDragging = false;

    void decodeNodeRecusively(QJsonObject &obj, Node *node);
};

}  // namespace widgets
}  // namespace chatterino
