// SPDX-FileCopyrightText: 2025 Prayag Jain <prayagjain2@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <QList>
#include <QPainter>
#include <QRectF>
#include <memory>
#include <unordered_map>

#include "item/item.hpp"
#include "item/itemutils.hpp"
#include "orderedlist.hpp"

/*
 * A QuadTree is a tree data structure which divides the space,
 * or in our case, the canvas, into 4 regions recursively.
 * It will be useful here to detect collisions with existing items
 * And redraw only the items necessary which improves performance.
 */
/*
 * NOTE: This is tightly coupled with the OrderedList data structure present in
 * the same directory and the Item class present in the `item` directory.
 */
class QuadTree
{
public:
    using ItemPtr = std::shared_ptr<Item>;
    using ZorderPosition = ItemOrderPosition<ItemPtr>;

private:
    QList<ItemPtr> m_items;
    QRectF m_boundingBox{};
    int m_capacity{};
    std::unique_ptr<QuadTree> m_topLeft{nullptr};
    std::unique_ptr<QuadTree> m_topRight{nullptr};
    std::unique_ptr<QuadTree> m_bottomRight{nullptr};
    std::unique_ptr<QuadTree> m_bottomLeft{nullptr};
    std::shared_ptr<OrderedList> m_orderedList{nullptr};

public:
    QuadTree(QRectF region, int capacity);
    QuadTree(QRectF region, int capacity, std::shared_ptr<OrderedList> orderedList);

    ~QuadTree();

    [[nodiscard]] int size() const;
    void insertItem(const ItemPtr &item, bool updateOrder = true);
    void deleteItem(const ItemPtr &item, bool updateOrder = true);
    void deleteItems(const QRectF &boundingBox);

    void reorder(QList<ItemPtr> &items) const;

    [[nodiscard]] QList<ItemPtr> getAllItems() const;
    void clear();

    template<typename Shape, typename QueryCondition>
    QList<ItemPtr> queryItems(const Shape &shape, QueryCondition condition) const;

    template<typename Shape>
    QList<ItemPtr> queryItems(const Shape &shape) const;

    void draw(QPainter &painter, const QPointF &offset) const;
    [[nodiscard]] const QRectF &boundingBox() const;

    void changeZorder(ItemUtils::ZorderMove move, const ItemPtr &item);
    [[nodiscard]] int zIndex(const ItemPtr &item) const;
    [[nodiscard]] ZorderPosition zorderPosition(const ItemPtr &item) const;
    void restoreZorderPosition(const ItemPtr &item, const ZorderPosition &position);

    [[nodiscard]] bool canMoveBackwards(const ItemPtr &item) const;
    [[nodiscard]] bool canMoveForwards(const ItemPtr &item) const;

private:
    bool insert(const ItemPtr &item, bool updateOrder);
    void update(const ItemPtr &item, const QRectF &oldBoundingBox, bool inserted);

    template<typename Shape, typename QueryCondition>
    void query(const Shape &shape, QueryCondition condition, QList<ItemPtr> &out, std::unordered_map<ItemPtr, bool> &itemAlreadyPushed) const;

    void subdivide();
    void expand(const QPointF &point);

    [[nodiscard]] QSet<ItemPtr> getAllUniqueItems() const;
};

#include "quadtree.ipp"
