// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL

#pragma once

#include <QDateTime>
#include <QString>
#include <Quotient/events/eventcontent.h>

#include "block.h"
#include "fileinfo.h"
#include "neochatdatetime.h"

namespace Quotient
{
namespace EventContent
{
class FileInfo;
}
class RoomEvent;
class RoomMember;
class RoomMessageEvent;
}

class NeoChatRoom;

/**
 * @class EventHandler
 *
 * This class is designed to handle a Quotient::RoomEvent allowing data to be extracted
 * in a form ready for the NeoChat UI.
 *
 * To use this properly both the room and the event should be set (and the event should
 * be from the given room).
 *
 * @note EventHandler will always try to return something even when not properly
 *       initialised, this is usually the best empty value it can create with available
 *       information. This is to minimize warnings from QML especially during startup
 *       and room changes.
 */
class EventHandler
{
public:
    /**
     * @brief Get the display name of the event author.
     *
     * This method is special in that it will return
     * the old display name of the author if the current event is one that caused it
     * to change. This allows for scenarios where the UI wishes to notify that a
     * user's display name has changed and what it changed from.
     *
     * @param isPending whether the event is pending as this cannot be derived from
     *                  just the event object.
     */
    static QString authorDisplayName(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending = false);

    /**
     * @brief Get the display name of the event author but with any newlines removed.
     *
     * Turns out you can put newlines in your display name so we need to handle that
     * primarily for the room list subtitle.
     *
     * @param isPending whether the event is pending as this cannot be derived from
     *                  just the event object.
     */
    static QString singleLineAuthorDisplayname(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending = false);

    /**
     * @brief Return a QDateTime object for the event timestamp.
     */
    static NeoChatDateTime dateTime(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending = false);

    /**
     * @brief Whether the event should be highlighted in the timeline.
     *
     * @note Messages in direct chats are never highlighted.
     */
    static bool isHighlighted(const NeoChatRoom *room, const Quotient::RoomEvent *event);

    /**
     * @brief Whether the event should be hidden in the timeline.
     *
     * This could be for numerous reasons, e.g. if it's a replacement event, if the
     * user has hidden all state events or if the sender has been ignored by the local
     * user.
     */
    static bool isHidden(const NeoChatRoom *room, const Quotient::RoomEvent *event, std::function<bool(const Quotient::RoomEvent *)> filter = {});

    /**
     * @brief The input format of the body in the message.
     *
     * I.e. if the message has only a body the format will be Qt::PlainText, if it
     * has a formatted body it will be Qt::RichText.
     */
    static Qt::TextFormat messageBodyInputFormat(const Quotient::RoomEvent &event);

    /**
     * @brief Output a string for the message content without any formatting.
     *
     * This is the content of the formatted_body key if present or the body key if
     * not.
     */
    static QString rawMessageBody(const Quotient::RoomEvent &event);

    /**
     * @brief Output a string for the message content ready for display in a rich text field.
     *
     * The output string is dependant upon the event type and the desired output format.
     *
     * For most messages this is the body content of the message. For media messages
     * this will be the caption and for state events it will be a string specific
     * to that event with some dynamic details about the event added.
     *
     * E.g. For a room topic state event the text will be:
     *      "set the topic to: <new topic text>"
     *
     * @param stripNewlines whether the output should have new lines in it.
     */
    static QString richBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool stripNewlines = false);

    /**
     * @brief Output a string for the message content ready for display in a plain text field.
     *
     * The output string is dependant upon the event type and the desired output format.
     *
     * For most messages this is the body content of the message. For media messages
     * this will be the caption and for state events it will be a string specific
     * to that event with some dynamic details about the event added.
     *
     * E.g. For a room topic state event the text will be:
     *      "set the topic to: <new topic text>"
     *
     * @param stripNewlines whether the output should have new lines in it.
     */
    static QString plainBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool stripNewlines = false);

    /**
     * @brief Output the original body for the message content, useful for editing the original message.
     *
     * The event type must be a room message event.
     */
    static QString markdownBody(const Quotient::RoomEvent *event);

    /**
     * @brief Output a generic string for the message content ready for display.
     *
     * The output string is dependant upon the event type.
     *
     * Unlike EventHandler::getRichBody or EventHandler::getPlainBody the string
     * is the same for all events of the same type.
     *
     * E.g. For a message the text will be:
     *      "sent a message"
     *
     * @sa richBody(), plainBody()
     */
    static QString genericBody(const NeoChatRoom *room, const Quotient::RoomEvent *event);

    /**
     * @brief Output a string for the event to be used as  a RoomList subtitle.
     *
     * The output includes the username followed by the plain message, all with no
     * line breaks.
     */
    static QString subtitleText(const NeoChatRoom *room, const Quotient::RoomEvent *event);

    /**
     * @brief Whether the event is a media message.
     *
     * This means it contains either an image or video.
     */
    static bool isMediaMessage(const Quotient::RoomEvent *event);

    /**
     * @brief Return an array of Blocks::Block to represent the content of the given event.
     */
    static Blocks::BlockPtrs blocksForEvent(NeoChatRoom *room, const Quotient::RoomEvent *event, QObject *parent);

    /**
     * @brief Return the media info for the event.
     *
     * An empty QVariantMap will be returned for any event that doesn't have any
     * media info.
     *
     * @return This should consist of the following:
     *  - source - The mxc URL for the media.
     *  - mimeType - The MIME type of the media (should be image/xxx for this delegate).
     *  - mimeIcon - The MIME icon name (should be image-xxx).
     *  - size - The file size in bytes.
     *  - width - The width in pixels of the audio media.
     *  - height - The height in pixels of the audio media.
     *  - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads.
     *  - isSticker - Whether the image is a sticker or not
     */
    static Blocks::Block *blockForMediaEvent(NeoChatRoom *room, const Quotient::RoomEvent *event, QObject *parent = nullptr);

    /**
     * @brief Get the author of the event replied to in context of the room.
     *
     * An empty Quotient::RoomMember will be returned if the EventHandler hasn't had
     * the room or event initialised.
     *
     * @param isPending if the event is pending, i.e. has not been confirmed by
     *                  the server.
     *
     * @return a Quotient::RoomMember object for the user.
     *
     * @sa Quotient::RoomMember
     */
    static Quotient::RoomMember replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event);

    /**
     * @brief Return the latitude for the event.
     *
     * Returns -100.0 if the event doesn't have a location (latitudes are in the
     * range -90deg to +90deg so -100 is out of range).
     */
    static float latitude(const Quotient::RoomEvent *event);

    /**
     * @brief Return the longitude for the event.
     *
     * Returns -200.0 if the event doesn't have a location (latitudes are in the
     * range -180deg to +180deg so -200 is out of range).
     */
    static float longitude(const Quotient::RoomEvent *event);

    /**
     * @brief Return the type of location marker for the event.
     */
    static QString locationAssetType(const Quotient::RoomEvent *event);

private:
    static QString getBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines);
    static QString getMessageBody(const NeoChatRoom *room, const Quotient::RoomMessageEvent &event, Qt::TextFormat format, bool stripNewlines);

    static Blocks::BlockPtrs blocksForEventType(NeoChatRoom *room, const Quotient::RoomEvent *event, QObject *parent);

    static Blocks::Block *fileBlockFromFileContent(QObject *parent,
                                                   NeoChatRoom *room,
                                                   const Quotient::EventContent::FileContentBase *fileContent,
                                                   const QString &eventId,
                                                   const QString &filename,
                                                   bool isSticker = false);
    static Blocks::ImageInfo getTumbnailInfo(const Quotient::EventContent::Thumbnail &thumbnail);
};
