/*
 * Copyright (C) 2019-2025 Dmitriy Purgin <dmitriy.purgin@sequality.at>
 * Copyright (C) 2019-2025 sequality software engineering e.U. <office@sequality.at>
 *
 * This file is part of QtOrm library.
 *
 * QtOrm is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * QtOrm is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with QtOrm.  If not, see <https://www.gnu.org/licenses/>.
 */

#ifndef QORMQUERYBUILDER_H
#define QORMQUERYBUILDER_H

#include <QtOrm/qormfilter.h>
#include <QtOrm/qormfilterexpression.h>
#include <QtOrm/qormglobal.h>
#include <QtOrm/qormquery.h>
#include <QtOrm/qormqueryresult.h>

#include <QtCore/qobject.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qvector.h>

#include <memory>

QT_BEGIN_NAMESPACE

class QOrmAbstractProvider;
class QOrmFilterExpression;
class QOrmMetadataCache;
class QOrmQueryBuilderPrivate;
class QOrmRelation;
class QOrmSession;

namespace QOrmPrivate
{
    class QueryBuilderHelperPrivate;

    class Q_ORM_EXPORT QueryBuilderHelper
    {
    public:
        QueryBuilderHelper(QOrmSession* session, const QOrmRelation& relation);
        QueryBuilderHelper(const QueryBuilderHelper&) = delete;
        QueryBuilderHelper(QueryBuilderHelper&&) noexcept;
        ~QueryBuilderHelper();

        QueryBuilderHelper& operator=(const QueryBuilderHelper&) = delete;
        QueryBuilderHelper& operator=(QueryBuilderHelper&&) noexcept;

        void setInstance(const QMetaObject& qMetaObject, QObject* instance);
        void addFilter(const QOrmFilter& filter);
        void addOrder(const QOrmClassProperty& classProperty, Qt::SortOrder direction);
        void setLimit(int limit);
        void setOffset(int offset);

        Q_REQUIRED_RESULT
        QOrmQuery build(QOrm::Operation operation, QOrm::QueryFlags flags) const;

        [[nodiscard]] QOrmQueryResult<QObject> select(QOrm::QueryFlags flags) const;
        [[nodiscard]] QOrmQueryResult<QObject> remove() const;
        [[nodiscard]] QOrmQueryResult<int> count() const;

    private:
        std::unique_ptr<QueryBuilderHelperPrivate> d;
    };
} // namespace QOrmPrivate

template<typename T>
class QOrmQueryBuilder
{
    template<typename>
    friend class QOrmQueryBuilder;

public:
    using Projection = std::decay_t<T>;
    using UnaryPredicate = std::function<bool(const Projection*)>;

    explicit QOrmQueryBuilder(QOrmSession* session, const QOrmRelation& relation)
        : m_helper{session, relation}
    {
    }

    QOrmQueryBuilder(const QOrmQueryBuilder&) = delete;

    template<typename U,
             typename = std::enable_if_t<std::is_same_v<U, QObject> || std::is_same_v<U, T>>>
    QOrmQueryBuilder(QOrmQueryBuilder<U>&& other)
        : m_helper{std::move(other.m_helper)}
    {
    }

    QOrmQueryBuilder& operator=(const QOrmQueryBuilder&) = delete;
    QOrmQueryBuilder& operator=(QOrmQueryBuilder&&) = default;

    QOrmQueryBuilder& filter(QOrmFilterExpression expression)
    {
        m_helper.addFilter(QOrmFilter{expression});
        return *this;
    }

    QOrmQueryBuilder& filter(UnaryPredicate predicate)
    {
        m_helper.addFilter(QOrmFilter{[predicate](const QObject* value) {
            return predicate(qobject_cast<const Projection*>(value));
        }});
        return *this;
    }

    QOrmQueryBuilder& order(const QOrmClassProperty& classProperty,
                            Qt::SortOrder direction = Qt::AscendingOrder)
    {
        m_helper.addOrder(classProperty, direction);
        return *this;
    }

    QOrmQueryBuilder& instance(const QMetaObject& qMetaObject, QObject* instance)
    {
        m_helper.setInstance(qMetaObject, instance);
        return *this;
    }

    QOrmQueryBuilder& limit(int limit)
    {
        m_helper.setLimit(limit);
        return *this;
    }

    QOrmQueryBuilder& offset(int offset)
    {
        m_helper.setOffset(offset);
        return *this;
    }

    Q_REQUIRED_RESULT
    QOrmQueryResult<Projection> select(QOrm::QueryFlags flags = QOrm::QueryFlags::None) const
    {
        return m_helper.select(flags);
    }

    [[nodiscard]] QOrmQueryResult<Projection> remove() { return m_helper.remove(); }

    [[nodiscard]] QOrmQueryResult<int> count() const { return m_helper.count(); }

    Q_REQUIRED_RESULT
    QOrmQuery build(QOrm::Operation operation, QOrm::QueryFlags flags = QOrm::QueryFlags::None) const { return m_helper.build(operation, flags); }

private:
    QOrmPrivate::QueryBuilderHelper m_helper;
};

QT_END_NAMESPACE

#endif // QORMQUERYBUILDER_H
