#pragma once

/// @file
/// @brief Pushrules and notification settings.

#if __has_include(<nlohmann/json_fwd.hpp>)
#include <nlohmann/json_fwd.hpp>
#else
#include <nlohmann/json.hpp>
#endif

#include <compare>
#include <string>
#include <variant>
#include <vector>

#include "mtx/events/common.hpp"
#include "mtx/events/power_levels.hpp"

namespace mtx {
namespace events {
namespace collections {
struct TimelineEvents;
}
}

//! Namespace for the pushrules specific endpoints.
namespace pushrules {
//! A condition to match pushrules on.
struct PushCondition
{
    //! Required. The kind of condition to apply. See conditions for more information on the
    //! allowed kinds and how they work.
    std::string kind;
    //! Required for event_match conditions. The dot- separated field of the event to match.
    //!
    //! Required for sender_notification_permission conditions. The field in the power level
    //! event the user needs a minimum power level for. Fields must be specified under the
    //! notifications property in the power level event's content.
    std::string key;
    //! Required for event_match conditions. The glob- style pattern to match against. Patterns
    //! with no special glob characters should be treated as having asterisks prepended and
    //! appended when testing the condition.
    std::string pattern;
    //! Required for event_property_is and event_property_contains conditions. A non compound json
    //! value.
    std::optional<std::variant<std::string, std::int64_t, bool, std::nullptr_t>> value;
    //! Required for room_member_count conditions. A decimal integer optionally prefixed by one
    //! of, ==, <, >, >= or <=. A prefix of < matches rooms where the member count is strictly
    //! less than the given number and so forth. If no prefix is present, this parameter
    //! defaults to ==.
    std::string is;

    //! The relation type to match on. Only valid for `im.nheko.msc3664.related_event_match`
    //! conditions.
    mtx::common::RelationType rel_type = mtx::common::RelationType::Unsupported;
    //! Wether to match fallback relations or not.
    bool include_fallback = false;

    friend void to_json(nlohmann::json &obj, const PushCondition &condition);
    friend void from_json(const nlohmann::json &obj, PushCondition &condition);
};
//! Namespace for the different push actions.
namespace actions {
//! Notify the user.
struct notify
{
    bool operator==(const notify &) const noexcept = default;
};
//! Don't notify the user.
struct dont_notify
{
    bool operator==(const dont_notify &) const noexcept = default;
};
/// @brief This enables notifications for matching events but activates homeserver specific
/// behaviour to intelligently coalesce multiple events into a single notification.
///
/// Not all homeservers may support this. Those that do not support it should treat it as the notify
/// action.
struct coalesce
{
    bool operator==(const coalesce &) const noexcept = default;
};
//! Play a sound.
struct set_tweak_sound
{
    //! The sound to play.
    std::string value = "default";

    bool operator==(const set_tweak_sound &) const noexcept = default;
};
//! Highlight the message.
struct set_tweak_highlight
{
    bool value = true;

    bool operator==(const set_tweak_highlight &) const noexcept = default;
};

//! A collection for the different actions.
using Action = std::variant<actions::notify,
                            actions::dont_notify,
                            actions::coalesce,
                            actions::set_tweak_sound,
                            actions::set_tweak_highlight>;

void
to_json(nlohmann::json &obj, const Action &action);

void
from_json(const nlohmann::json &obj, Action &action);

//! A list of actions.
struct Actions
{
    std::vector<Action> actions;

    friend void to_json(nlohmann::json &obj, const Actions &action);
    friend void from_json(const nlohmann::json &obj, Actions &action);

    bool operator==(const Actions &) const noexcept = default;
};
}

//! A pushrule defining the notification behaviour for a message.
struct PushRule
{
    //! Required. Whether this is a default rule, or has been set explicitly.
    bool default_ = false;
    //! Required. Whether the push rule is enabled or not.
    bool enabled = true;
    //! Required. The actions to perform when this rule is matched.
    std::vector<actions::Action> actions;
    //! Required. The ID of this rule.
    std::string rule_id;
    //! The glob-style pattern to match against. Only applicable to content rules.
    std::string pattern;
    //! The conditions that must hold true for an event in order for a rule to be applied to an
    //! event. A rule with no conditions always matches. Only applicable to underride and
    //! override rules.
    std::vector<PushCondition> conditions;

    friend void to_json(nlohmann::json &obj, const PushRule &condition);
    friend void from_json(const nlohmann::json &obj, PushRule &condition);
};

//! All the pushrules to evaluate for events.
struct Ruleset
{
    //! see https://matrix.org/docs/spec/client_server/latest#push-rules
    //
    // A push rule is a single rule that states under what conditions an event should be passed
    // onto a push gateway and how the notification should be presented. There are different
    // "kinds" of push rules and each rule has an associated priority. Every push rule MUST have
    // a kind and rule_id. The rule_id is a unique string within the kind of rule and its'
    // scope: rule_ids do not need to be unique between rules of the same kind on different
    // devices. Rules may have extra keys depending on the value of kind. The different kinds of
    // rule in descending order of priority are:
    std::vector<PushRule> override_;
    std::vector<PushRule> content;
    std::vector<PushRule> room;
    std::vector<PushRule> sender;
    std::vector<PushRule> underride;

    friend void to_json(nlohmann::json &obj, const Ruleset &condition);
    friend void from_json(const nlohmann::json &obj, Ruleset &condition);
};

//! The global ruleset applied to all events.
struct GlobalRuleset
{
    //! The actual ruleset.
    Ruleset global;

    friend void to_json(nlohmann::json &obj, const GlobalRuleset &set);
    friend void from_json(const nlohmann::json &obj, GlobalRuleset &set);
};

//! The response for queries, if a specific ruleset is enabled.
struct Enabled
{
    bool enabled = true;

    friend void to_json(nlohmann::json &obj, const Enabled &enabled);
    friend void from_json(const nlohmann::json &obj, Enabled &enabled);
};

//! An optimized structure to calculate notifications for events.
///
/// You will want to cache this for as long as possible (until the pushrules change), since
/// constructing this is somewhat expensive.
class PushRuleEvaluator
{
public:
    //! Construct a new push evaluator. Pass the current set of pushrules to evaluate.
    PushRuleEvaluator(const Ruleset &rules);
    ~PushRuleEvaluator();

    //! Additional room information needed to evaluate push rules.
    struct RoomContext
    {
        //! The displayname of the user in the room.
        std::string user_display_name;
        //! the membercount of the room
        std::size_t member_count = 0;
        //! The powerlevels event in this room
        mtx::events::state::PowerLevels power_levels;
    };

    //! Evaluate the pushrules for @event .
    ///
    /// You need to have the room_id set for the event.
    /// `relatedEvents` is a mapping of rel_type to event. Pass all the events that are related to
    /// by this event here.
    /// \returns the actions to apply.
    [[nodiscard]] std::vector<actions::Action> evaluate(
      const mtx::events::collections::TimelineEvents &event,
      const RoomContext &ctx,
      const std::vector<std::pair<mtx::common::Relation, mtx::events::collections::TimelineEvents>>
        &relatedEvents) const;

private:
    struct OptimizedRules;
    std::unique_ptr<OptimizedRules> rules;
};
}
}
