// SPDX-FileCopyrightText: 2026 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "QXmppMucData.h"

#include "QXmppConstants_p.h"
#include "QXmppMucData_p.h"
#include "QXmppUtils.h"
#include "QXmppUtils_p.h"

#include "Algorithms.h"
#include "StringLiterals.h"
#include "XmlWriter.h"

#include <QDomElement>

using namespace QXmpp;
using namespace QXmpp::Private;

template<>
struct Enums::Data<QXmpp::Muc::Affiliation> {
    using enum QXmpp::Muc::Affiliation;
    static constexpr auto Values = makeValues<QXmpp::Muc::Affiliation>({
        { Outcast, u"outcast" },
        { None, u"none" },
        { Member, u"member" },
        { Admin, u"admin" },
        { Owner, u"owner" },
    });
};

template<>
struct Enums::Data<QXmpp::Muc::Role> {
    using enum QXmpp::Muc::Role;
    static constexpr auto Values = makeValues<QXmpp::Muc::Role>({
        { None, u"none" },
        { Visitor, u"visitor" },
        { Participant, u"participant" },
        { Moderator, u"moderator" },
    });
};

namespace QXmpp::Muc {

std::optional<HistoryOptions> HistoryOptions::fromDom(const QDomElement &el)
{
    HistoryOptions opts;
    auto parseInt = [&](const QString &name) -> std::optional<int> {
        if (!el.hasAttribute(name)) {
            return std::nullopt;
        }
        return el.attribute(name).toInt();
    };
    opts.m_maxChars = parseInt(u"maxchars"_s);
    opts.m_maxStanzas = parseInt(u"maxstanzas"_s);
    opts.m_seconds = parseInt(u"seconds"_s);
    if (el.hasAttribute(u"since"_s)) {
        opts.m_since = QXmppUtils::datetimeFromString(el.attribute(u"since"_s));
    }
    return opts;
}

void HistoryOptions::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter(writer).write(Element {
        u"history",
        OptionalAttribute { u"maxchars", m_maxChars },
        OptionalAttribute { u"maxstanzas", m_maxStanzas },
        OptionalAttribute { u"seconds", m_seconds },
        OptionalAttribute { u"since", m_since },
    });
}

std::optional<Destroy> Destroy::fromDom(const QDomElement &el)
{
    Destroy d;
    d.m_alternateRoom = el.attribute(u"jid"_s);
    d.m_reason = el.firstChildElement(u"reason"_s).text();
    return d;
}

void Destroy::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter(writer).write(Element {
        u"destroy",
        OptionalAttribute { u"jid", m_alternateRoom },
        OptionalTextElement { u"reason", m_reason },
    });
}

std::optional<Item> Item::fromDom(const QDomElement &el)
{
    Item item;
    item.m_jid = el.attribute(u"jid"_s);
    item.m_nick = el.attribute(u"nick"_s);
    item.m_affiliation = Enums::fromString<Affiliation>(el.attribute(u"affiliation"_s));
    item.m_role = Enums::fromString<Role>(el.attribute(u"role"_s));
    item.m_reason = el.firstChildElement(u"reason"_s).text();
    item.m_actor = el.firstChildElement(u"actor"_s).attribute(u"jid"_s);
    return item;
}

void Item::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter w(writer);
    w.write(Element {
        u"item",
        OptionalAttribute { u"affiliation", m_affiliation },
        OptionalAttribute { u"jid", m_jid },
        OptionalAttribute { u"nick", m_nick },
        OptionalAttribute { u"role", m_role },
        OptionalContent { !m_actor.isEmpty(), Element { u"actor", Attribute { u"jid", m_actor } } },
        OptionalTextElement { u"reason", m_reason },
    });
}

std::optional<Invite> Invite::fromDom(const QDomElement &el)
{
    Invite invite;
    invite.m_from = el.attribute(u"from"_s);
    invite.m_to = el.attribute(u"to"_s);
    invite.m_reason = el.firstChildElement(u"reason"_s).text();
    return invite;
}

void Invite::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter(writer).write(Element {
        u"invite",
        OptionalAttribute { u"to", m_to },
        OptionalAttribute { u"from", m_from },
        OptionalTextElement { u"reason", m_reason },
    });
}

std::optional<Decline> Decline::fromDom(const QDomElement &el)
{
    Decline decline;
    decline.m_from = el.attribute(u"from"_s);
    decline.m_to = el.attribute(u"to"_s);
    decline.m_reason = el.firstChildElement(u"reason"_s).text();
    return decline;
}

void Decline::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter(writer).write(Element {
        u"decline",
        OptionalAttribute { u"to", m_to },
        OptionalAttribute { u"from", m_from },
        OptionalTextElement { u"reason", m_reason },
    });
}

std::optional<UserQuery> UserQuery::fromDom(const QDomElement &el)
{
    UserQuery ue;
    ue.m_statusCodes = transformFilter<QList<uint32_t>>(parseSingleAttributeElements<QList<QString>>(el, u"status", ns_muc_user, u"code"_s), [](const auto &string) {
        return parseInt<uint32_t>(string);
    });
    ue.m_invite = parseOptionalChildElement<Invite>(el);
    ue.m_password = firstChildElement(el, u"password").text();
    ue.m_decline = parseOptionalChildElement<Decline>(el);
    return ue;
}

void UserQuery::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter w(writer);
    w.write(Element {
        Tag { u"x", ns_muc_user },
        SingleAttributeElements { u"status", u"code", m_statusCodes },
        m_invite,
        OptionalTextElement { u"password", m_password },
        m_decline,
    });
}

///
/// Constructs a direct invitation for the room at \a jid.
///
DirectInvitation::DirectInvitation(QString jid, QString password, QString reason, bool isContinue, QString thread)
    : m_jid(std::move(jid)),
      m_password(std::move(password)),
      m_reason(std::move(reason)),
      m_continue(isContinue),
      m_thread(std::move(thread))
{
}

std::optional<DirectInvitation> DirectInvitation::fromDom(const QDomElement &el)
{
    if (elementXmlTag(el) != XmlTag) {
        return {};
    }
    DirectInvitation inv;
    inv.m_jid = el.attribute(u"jid"_s);
    inv.m_password = el.attribute(u"password"_s);
    inv.m_reason = el.attribute(u"reason"_s);
    inv.m_continue = parseBoolean(el.attribute(u"continue"_s)).value_or(false);
    inv.m_thread = el.attribute(u"thread"_s);
    return inv;
}

void DirectInvitation::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter(writer).write(Element {
        Tag { u"x", ns_conference },
        Attribute { u"jid", m_jid },
        OptionalAttribute { u"password", m_password },
        OptionalAttribute { u"reason", m_reason },
        OptionalAttribute { u"continue", DefaultedBool { m_continue, false } },
        OptionalAttribute { u"thread", m_thread },
    });
}

}  // namespace QXmpp::Muc

namespace QXmpp::Private {

std::optional<MucAdminQuery> MucAdminQuery::fromDom(const QDomElement &el)
{
    if (elementXmlTag(el) != XmlTag) {
        return {};
    }
    return MucAdminQuery { parseChildElements<QList<QXmpp::Muc::Item>>(el) };
}

void MucAdminQuery::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter(writer).write(Element { Tag { u"query", ns_muc_admin }, items });
}

std::optional<MucOwnerQuery> MucOwnerQuery::fromDom(const QDomElement &el)
{
    if (elementXmlTag(el) != XmlTag) {
        return {};
    }
    MucOwnerQuery q;
    q.form = parseOptionalChildElement<QXmppDataForm>(el);
    const auto destroyEl = el.firstChildElement(u"destroy"_s);
    if (!destroyEl.isNull()) {
        q.destroyAlternateJid = destroyEl.attribute(u"jid"_s);
        q.destroyReason = destroyEl.firstChildElement(u"reason"_s).text();
    }
    return q;
}

void MucOwnerQuery::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter w(writer);
    w.write(Element {
        Tag { u"query", ns_muc_owner },
        form,
        OptionalContent {
            !destroyAlternateJid.isEmpty() || !destroyReason.isEmpty(),
            Element {
                u"destroy",
                OptionalAttribute { u"jid", destroyAlternateJid },
                OptionalTextElement { u"reason", destroyReason },
            },
        },
    });
}

std::optional<MucRegisterQuery> MucRegisterQuery::fromDom(const QDomElement &el)
{
    if (elementXmlTag(el) != XmlTag) {
        return {};
    }
    MucRegisterQuery q;
    q.form = parseOptionalChildElement<QXmppDataForm>(el);
    q.isRegistered = !el.firstChildElement(u"registered"_s).isNull();
    return q;
}

void MucRegisterQuery::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter(writer).write(Element { Tag { u"query", ns_register }, form });
}

std::optional<MucUniqueQuery> MucUniqueQuery::fromDom(const QDomElement &el)
{
    if (elementXmlTag(el) != XmlTag) {
        return {};
    }
    return MucUniqueQuery { el.text() };
}

void MucUniqueQuery::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter(writer).write(Element { XmlTag, OptionalCharacters { name } });
}

std::optional<MucModerateQuery> MucModerateQuery::fromDom(const QDomElement &el)
{
    if (elementXmlTag(el) != XmlTag) {
        return {};
    }
    MucModerateQuery q;
    q.stanzaId = el.attribute(u"id"_s);
    q.reason = firstChildElement(el, u"reason", ns_message_moderate).text();
    return q;
}

void MucModerateQuery::toXml(QXmlStreamWriter *writer) const
{
    XmlWriter(writer).write(Element {
        XmlTag,
        Attribute { u"id", stanzaId },
        Element { Tag { u"retract", ns_message_retract } },
        OptionalTextElement { u"reason", reason },
    });
}

}  // namespace QXmpp::Private
