// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: LGPL-2.1-or-later

#pragma once

#include "quotient_common.h"
#include "uri.h"
#include "avatar.h"

#include <QtCore/QObject>

namespace Quotient {
class Room;
class RoomMemberEvent;

//! \brief Representation of a user state in a room
//!
//! The class is intentionally a read-only data object that is effectively a wrapper around an
//! `m.room.member` event for the desired user. This is designed to provide the data in a format
//! ready for visualizing a user (avatar or name) in the context of the room it was generated in.
//! This means that if a user has set a unique name or avatar for a particular room that is what
//! will be returned.
//!
//! \note The RoomMember class is not intended for interacting with the user's profile.
//!       For that a Quotient::User object should be obtained from a Quotient::Connection as that
//!       has the support functions for modifying profile information.
//! \warning RoomMember is a gadget class and should not be kept between syncs. It does not track
//!          changes of the member state therefore some member changes (i.e. leaving the room) may
//!          render a RoomMember dangling, when calling any of its methods leads to undefined
//!          behaviour.
//! \sa User
class QUOTIENT_API RoomMember {
    Q_GADGET
    Q_PROPERTY(bool isEmpty READ isEmpty CONSTANT)
    Q_PROPERTY(QString id READ id CONSTANT)
    Q_PROPERTY(Uri uri READ uri CONSTANT)
    Q_PROPERTY(bool isLocalMember READ isLocalMember CONSTANT)
    Q_PROPERTY(QString displayName READ displayName CONSTANT)
    Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName CONSTANT)
    Q_PROPERTY(QString fullName READ fullName CONSTANT)
    Q_PROPERTY(QString htmlSafeFullName READ htmlSafeFullName CONSTANT)
    Q_PROPERTY(QString disambiguatedName READ disambiguatedName CONSTANT)
    Q_PROPERTY(QString htmlSafeDisambiguatedName READ htmlSafeDisambiguatedName CONSTANT)
    Q_PROPERTY(int hue READ hue CONSTANT)
    Q_PROPERTY(qreal hueF READ hueF CONSTANT)
    Q_PROPERTY(QColor color READ color CONSTANT)
    Q_PROPERTY(QUrl avatarUrl READ avatarUrl CONSTANT)
    Q_PROPERTY(int powerLevel READ powerLevel CONSTANT)

public:
    RoomMember() = default;

    explicit RoomMember(const Room* room, const RoomMemberEvent* member);

    bool isEmpty() const { return _member == nullptr; }

    bool operator==(const RoomMember& other) const;

    //! @brief Get unique stable user id
    //!
    //! The Matrix user ID is generated by the server and is never changed.
    QString id() const;

    //! @brief The matrix.to URI for the user
    //!
    //! Typically used when you want to visit a user (see
    //! Quotient::UriResolverBase::visitResource()).
    //!
    //! @sa Quotient::UriResolverBase::visitResource()
    Uri uri() const;

    //! Whether this member is the local user
    bool isLocalMember() const;

    //! The membership state of the member
    Membership membershipState() const;

    //! \brief The raw unmodified display name for the user in the given room
    //!
    //! The value will be empty if no display name has been set.
    //!
    //! \warning This value is not sanitized or HTML escape so use appropriately.
    //!          For ready to display values use displayName() or fullName() for
    //!          plain text and htmlSafeDisplayName() or htmlSafeFullName() fo
    //!          rich text.
    //!
    //! \sa displayName(), htmlSafeDisplayName(), fullName(), htmlSafeFullName()
    QString name() const;

    //! \brief Get the user display name ready for display
    //!
    //! This function always aims to return something that can be displayed in a
    //! UI, so if no display name is set the user's Matrix ID will be returned.
    //!
    //! The output is sanitized and suitable for a plain text field. For a rich
    //! field use htmlSafeDisplayName().
    //!
    //! \sa htmlSafeDisplayName()
    QString displayName() const;

    //! \brief Get the user display name ready for display
    //!
    //! This function always aims to return something that can be displayed in a
    //! UI, so if no display name is set the user's Matrix ID will be returned.
    //!
    //! The output is sanitized and html escaped ready for a rich text field. For
    //! a plain field use displayName().
    //!
    //! \sa displayName()
    QString htmlSafeDisplayName() const;

    //! \brief Get user name and id in a single string
    //!
    //! This function always aims to return something that can be displayed in a
    //! UI, so if no display name is set the just user's Matrix ID will be returned.
    //! The constructed string follows the format 'name (id)' which the spec
    //! recommends for users disambiguation in a room context and in other places.
    //!
    //! The output is sanitized and suitable for a plain text field. For a rich
    //! field use htmlSafeFullName().
    //!
    //! \sa htmlSafeFullName()
    QString fullName() const;

    //! \brief Get user name and id in a single string
    //!
    //! This function always aims to return something that can be displayed in a
    //! UI, so if no display name is set the just user's Matrix ID will be returned.
    //! The constructed string follows the format 'name (id)' which the spec
    //! recommends for users disambiguation in a room context and in other places.
    //!
    //! The output is sanitized and html escaped ready for a rich text field. For
    //! a plain field use fullName().
    //!
    //! \sa fullName()
    QString htmlSafeFullName() const;

    //! \brief Get the disambiguated user name
    //!
    //! This function always aims to return something that can be displayed in a
    //! UI, so if no display name is set the just user's Matrix ID will be returned.
    //! The output is equivalent to fullName() if there is another user in the room
    //! with the same name. Otherwise it is equivalent to displayName().
    //!
    //! The output is sanitized and suitable for a plain text field. For a rich
    //! field use htmlSafeDisambiguatedName().
    //!
    //! \sa htmlSafeDisambiguatedName(), fullName(), displayName()
    QString disambiguatedName() const;

    //! \brief Get the disambiguated user name
    //!
    //! This function always aims to return something that can be displayed in a
    //! UI, so if no display name is set the just user's Matrix ID will be returned.
    //! The output is equivalent to htmlSafeFullName() if there is another user in the room
    //! with the same name. Otherwise it is equivalent to htmlSafeDisplayName().
    //!
    //! The output is sanitized and html escaped ready for a rich text field. For
    //! a plain field use disambiguatedName().
    //!
    //! \sa disambiguatedName(), htmlSafeFullName(), htmlSafeDisplayName()
    QString htmlSafeDisambiguatedName() const;

    //! \brief Check whether the name or id of the member contains a substring
    //!
    //! This is useful for a predicate to filter room members.
    //! \sa MemberMatcher
    Q_INVOKABLE bool matches(QStringView substr, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;

    //! \brief Hue color component of this user based on the user's Matrix ID
    //!
    //! The implementation is based on XEP-0392:
    //! https://xmpp.org/extensions/xep-0392.html
    //! Naming and ranges are the same as QColor's hue methods:
    //! https://doc.qt.io/qt-5/qcolor.html#integer-vs-floating-point-precision
    int hue() const;

    //! \brief HueF color component of this user based on the user's Matrix ID
    //!
    //! The implementation is based on XEP-0392:
    //! https://xmpp.org/extensions/xep-0392.html
    //! Naming and ranges are the same as QColor's hue methods:
    //! https://doc.qt.io/qt-5/qcolor.html#integer-vs-floating-point-precision
    qreal hueF() const;

    //! \brief Color based on the user's Matrix ID
    //!
    //! See https://github.com/quotient-im/libQuotient/wiki/User-color-coding-standard-draft-proposal
    //! for the methodology.
    QColor color() const;

    const Avatar& avatarObject() const;

    //! \brief The mxc URL as a string for the user avatar in the room
    //!
    //! This can be empty if none set.
    QString avatarMediaId() const;

    //! \brief The mxc URL for the user avatar in the room
    //!
    //! This can be empty if none set.
    QUrl avatarUrl() const;

    QImage avatar(int width, int height, Avatar::get_callback_t callback) const;

    QImage avatar(int dimension, Avatar::get_callback_t callback) const;

    //! \brief The power level of the member.
    //!
    //! This is in the context of the current room. Will return the default power
    //! level for the room if not specifically set.
    int powerLevel() const;

private:
    const Room* _room = nullptr;
    const RoomMemberEvent* _member = nullptr;

    qreal _hueF = 0;
};

//! \brief A factory to get a functional object matching room members against a substring
//!
//! This is a convenience wrapper to use RoomMember::matches() in standard algorithms.
inline auto memberMatcher(auto substr, Qt::CaseSensitivity cs = Qt::CaseSensitive)
{
#ifdef __cpp_lib_bind_back
    return std::bind_back(&RoomMember::matches, substr, cs);
#else
    return [substr, cs](const RoomMember& m) { return m.matches(substr, cs); };
#endif
}

struct QUOTIENT_API MemberSorter {
    bool operator()(const RoomMember& u1, const RoomMember& u2) const
    {
        return operator()(u1.displayName(), u2.displayName());
    }
    bool operator()(const RoomMember& u1, QStringView u2name) const
    {
        return operator()(u1.displayName(), u2name);
    }
    bool operator()(QStringView u1name, const RoomMember& u2) const
    {
        return operator()(u1name, u2.displayName());
    }
    bool operator()(QStringView u1name, QStringView u2name) const;

#if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR < 10
    template <template <class> class ContT>
    [[deprecated("Use Quotient::lowerBoundIndex() or std::ranges::lower_bound() instead")]] //
    typename ContT<RoomMember>::size_type
    lowerBoundIndex(const ContT<RoomMember>& c, const auto& v) const
    {
        return std::ranges::lower_bound(c, v, *this) - c.begin();
    }
#endif
};

} // namespace Quotient
