// SPDX-FileCopyrightText: 2010 Jeremy Lainé <jeremy.laine@m4x.org>
// SPDX-FileCopyrightText: 2025 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#ifndef QXMPPDISCOVERY_H
#define QXMPPDISCOVERY_H

#include "QXmppDataFormBase.h"
#include "QXmppGlobal.h"
#include "QXmppIq.h"

#include <QSharedDataPointer>

namespace QXmpp::Disco {

/*!
    \namespace QXmpp::Disco
    \inmodule QXmpp

    Contains types for \xep{0030}{Service Discovery}.
*/

/*!
    \brief Service discovery identity category as registered at
    https://xmpp.org/registrar/disco-categories.html

    \since QXmpp 1.16

    \value Account
    \value Auth
    \value Automation
    \value Client
    \value Collaboration
    \value Component
    \value Conference
    \value Directory
    \value Gateway
    \value Headline
    \value Hierarchy
    \value Proxy
    \value PubSub
    \value Server
    \value Store
*/
enum class Category {
    Account,
    Auth,
    Automation,
    Client,
    Collaboration,
    Component,
    Conference,
    Directory,
    Gateway,
    Headline,
    Hierarchy,
    Proxy,
    PubSub,
    Server,
    Store,
};

/*!
    \brief Service discovery identity type as registered at
    https://xmpp.org/registrar/disco-categories.html

    Note: not all category/type combinations are valid. See the registry for
    valid combinations.

    \since QXmpp 1.16

    \value Bot
    \value Console
    \value Game
    \value Handheld
    \value Pc
    \value Phone
    \value Sms
    \value Tablet
    \value Web
    \value Irc
    \value Mix
    \value Text
    \value Collection
    \value Leaf
    \value Pep
    \value Service
    \value File
*/
enum class Type {
    // client
    Bot,
    Console,
    Game,
    Handheld,
    Pc,
    Phone,
    Sms,
    Tablet,
    Web,
    // conference
    Irc,
    Mix,
    Text,
    // gateway
    // (using generic types that overlap with other categories)
    // pubsub
    Collection,
    Leaf,
    Pep,
    Service,
    // store
    File,
};

}  // namespace QXmpp::Disco

class QXmppDiscoveryIdentityPrivate;
class QXmppDiscoveryItemPrivate;
class QXmppDiscoveryIqPrivate;

class QXMPP_EXPORT QXmppDiscoItem
{
public:
    QXmppDiscoItem() { }
    /*! Default constructor */
    explicit QXmppDiscoItem(const QString &jid, const QString &name = {}, const QString &node = {})
        : m_jid(jid), m_name(name), m_node(node) { }

    /*! Returns the JID of the item. */
    const QString &jid() const { return m_jid; }
    /*!
        Sets the JID of the item.

        \a newJid.
    */
    void setJid(const QString &newJid) { m_jid = newJid; }

    /*! Returns the name of the item. */
    const QString &name() const { return m_name; }
    /*!
        Sets the name of the item.

        \a newName.
    */
    void setName(const QString &newName) { m_name = newName; }

    /*! Returns the node for querying the information. */
    const QString &node() const { return m_node; }
    /*!
        Sets the node for querying the information.

        \a newNode.
    */
    void setNode(const QString &newNode) { m_node = newNode; }

    static constexpr std::tuple XmlTag = { u"item", QXmpp::Private::ns_disco_items };
    static std::optional<QXmppDiscoItem> fromDom(const QDomElement &el);
    void toXml(QXmlStreamWriter *writer) const;

private:
    QString m_jid;
    QString m_name;
    QString m_node;
};

class QXMPP_EXPORT QXmppDiscoItems
{
public:
    QXmppDiscoItems() { }
    /*! Default constructor */
    explicit QXmppDiscoItems(const QString &node, const QList<QXmppDiscoItem> &items = {})
        : m_node(node), m_items(items) { }

    /*! Returns the items. */
    const QList<QXmppDiscoItem> &items() const { return m_items; }
    /*!
        Sets the items.

        \a newItems.
    */
    void setItems(const QList<QXmppDiscoItem> &newItems) { m_items = newItems; }

    /*! Returns the node of the query. */
    const QString &node() const { return m_node; }
    /*!
        Sets the node of the query.

        \a newNode.
    */
    void setNode(const QString &newNode) { m_node = newNode; }

    static constexpr std::tuple XmlTag = { u"query", QXmpp::Private::ns_disco_items };
    static std::optional<QXmppDiscoItems> fromDom(const QDomElement &el);
    void toXml(QXmlStreamWriter *writer) const;

private:
    QList<QXmppDiscoItem> m_items;
    QString m_node;
};

class QXMPP_EXPORT QXmppDiscoIdentity
{
public:
    QXmppDiscoIdentity() { }
    /*! Default constructor */
    explicit QXmppDiscoIdentity(const QString &category, const QString &type = {}, const QString &name = {}, const QString &lang = {})
        : m_category(category), m_type(type), m_name(name), m_language(lang) { }

    /*! Returns the category of the identity. */
    const QString &category() const { return m_category; }
    /*!
        Sets the category of the identity.

        \a newCategory.
    */
    void setCategory(const QString &newCategory) { m_category = newCategory; }

    /*!
        Returns the service type in this category.

        See https://xmpp.org/registrar/disco-categories.html for details.
    */
    const QString &type() const { return m_type; }
    /*!
        Sets the service type in this category.

        See https://xmpp.org/registrar/disco-categories.html for details.

        \a newType.
    */
    void setType(const QString &newType) { m_type = newType; }

    /*! Returns the human-readable name of the service. */
    const QString &name() const { return m_name; }
    /*!
        Sets the human-readable name of the service.

        \a newName.
    */
    void setName(const QString &newName) { m_name = newName; }

    /*!
        Returns the xml:lang code of the name.

        It is possible that the same identity (same type and same category) is
        included multiple times with different languages and localized names.
    */
    const QString &language() const { return m_language; }
    /*!
        Sets the xml:lang code of the name.

        It is possible that the same identity (same type and same category) is
        included multiple times with different languages and localized names.

        \a newLanguage.
    */
    void setLanguage(const QString &newLanguage) { m_language = newLanguage; }

    static constexpr std::tuple XmlTag = { u"identity", QXmpp::Private::ns_disco_info };
    static std::optional<QXmppDiscoIdentity> fromDom(const QDomElement &el);
    void toXml(QXmlStreamWriter *writer) const;

private:
    QString m_category;
    QString m_type;
    QString m_name;
    QString m_language;
};

class QXMPP_EXPORT QXmppDiscoInfo
{
public:
    QXmppDiscoInfo() { }
    /*! Default constructor */
    explicit QXmppDiscoInfo(const QString &node, const QList<QXmppDiscoIdentity> &identities = {}, const QList<QString> &features = {}, const QList<QXmppDataForm> &dataForms = {})
        : m_node(node), m_identities(identities), m_features(features), m_dataForms(dataForms) { }

    /*! Returns the node of the query. */
    const QString &node() const { return m_node; }
    /*!
        Sets the node of the query.

        \a newNode.
    */
    void setNode(const QString &newNode) { m_node = newNode; }

    /*! Returns the identities of the entity. */
    const QList<QXmppDiscoIdentity> &identities() const { return m_identities; }
    /*!
        Sets the identities of the entity.

        \a newIdentities.
    */
    void setIdentities(const QList<QXmppDiscoIdentity> &newIdentities) { m_identities = newIdentities; }

    /*! Returns the features supported by the entity. */
    const QList<QString> &features() const { return m_features; }
    /*!
        Sets the features supported by the entity.

        \a newFeatures.
    */
    void setFeatures(const QList<QString> &newFeatures) { m_features = newFeatures; }

    /*! Returns additional data forms as specified in \xep{0128}{Service Discovery Extensions}. */
    const QList<QXmppDataForm> &dataForms() const { return m_dataForms; }
    /*!
        Sets additional data forms as specified in \xep{0128}{Service Discovery Extensions}.
        Forms MUST have a FORM_TYPE and each FORM_TYPE MUST occur only once.

        \a newDataForms.
    */
    void setDataForms(const QList<QXmppDataForm> &newDataForms) { m_dataForms = newDataForms; }
    std::optional<QXmppDataForm> dataForm(QStringView formType) const;

    /*! Looks for a form with the form type of FormType and parses it if found. */
    template<QXmpp::Private::DataFormConvertible FormType>
    std::optional<FormType> dataForm() const
    {
        if (auto form = dataForm(QXmpp::Private::DataFormType<FormType>)) {
            if (auto result = FormType::fromDataForm(*form)) {
                return *result;
            }
        }
        return {};
    }

    QByteArray calculateEntityCapabilitiesHash() const;

    static constexpr std::tuple XmlTag = { u"query", QXmpp::Private::ns_disco_info };
    static std::optional<QXmppDiscoInfo> fromDom(const QDomElement &el);
    void toXml(QXmlStreamWriter *writer) const;

private:
    QString m_node;
    QList<QXmppDiscoIdentity> m_identities;
    QList<QString> m_features;
    QList<QXmppDataForm> m_dataForms;
};

/*!
    \inmodule QXmpp

    \brief A discovered service with its JID and disco info.

    Returned by QXmppDiscoServicesWatch when discovering server services.

    \since QXmpp 1.16
*/
struct QXMPP_EXPORT QXmppDiscoService {
    /*! JID of the service. */
    QString jid;
    /*! Service discovery information of the service. */
    QXmppDiscoInfo info;
};

#if QXMPP_DEPRECATED_SINCE(1, 12)
class QXMPP_EXPORT QXmppDiscoveryIq : public QXmppIq
{
public:
    /*!
        \typealias QXmppDiscoveryIq::Identity

        Alias for backwards-compatibility.
    */
    using Identity [[deprecated]] = QXmppDiscoIdentity;
    /*!
        \typealias QXmppDiscoveryIq::Item

        Alias for backwards-compatibility.
    */
    using Item [[deprecated]] = QXmppDiscoItem;

    [[deprecated("Use QXmppDiscoInfo/Items")]]
    QXmppDiscoveryIq();
    QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppDiscoveryIq)

    enum QueryType {
        InfoQuery,
        ItemsQuery
    };

    QStringList features() const;
    void setFeatures(const QStringList &features);

    QList<QXmppDiscoIdentity> identities() const;
    void setIdentities(const QList<QXmppDiscoIdentity> &identities);

    QList<QXmppDiscoItem> items() const;
    void setItems(const QList<QXmppDiscoItem> &items);

    [[deprecated("Use dataForms() instead")]]
    QXmppDataForm form() const;
    [[deprecated("Use setDataForms() instead")]]
    void setForm(const QXmppDataForm &form);

    const QList<QXmppDataForm> &dataForms() const;
    void setDataForms(const QList<QXmppDataForm> &dataForms);
    std::optional<QXmppDataForm> dataForm(QStringView formType) const;

    /*!
        Looks for a form with the form type of FormT and parses it if found.
        \since QXmpp 1.12
    */
    template<QXmpp::Private::DataFormConvertible FormT>
    std::optional<FormT> dataForm() const
    {
        if (auto form = dataForm(QXmpp::Private::DataFormType<FormT>)) {
            if (auto result = FormT::fromDataForm(*form)) {
                return *result;
            }
        }
        return {};
    }

    QString queryNode() const;
    void setQueryNode(const QString &node);

    enum QueryType queryType() const;
    void setQueryType(enum QueryType type);

    QByteArray verificationString() const;

    [[deprecated("Use QXmpp::isIqElement()")]]
    static bool isDiscoveryIq(const QDomElement &element);
    static bool checkIqType(const QString &tagName, const QString &xmlNamespace);

protected:
    void parseElementFromChild(const QDomElement &element) override;
    void toXmlElementFromChild(QXmlStreamWriter *writer) const override;

private:
    QSharedDataPointer<QXmppDiscoveryIqPrivate> d;
};
#endif

#endif
