/*
 * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>,
 * released under GNU AGPL v3 license:
 * https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
 */

#ifndef MOONWELL_PLAYER_GUIDE_MGR_H
#define MOONWELL_PLAYER_GUIDE_MGR_H

#include "Define.h"

#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>

class Player;

namespace MoonWell::PlayerGuide
{
    enum : uint32 { PAYLOAD_VERSION = 1 };

    struct Objective
    {
        uint32      id          = 0;
        std::string type;          // level | quest | quest_chain | dungeon |
                                   // raid | boss | achievement |
                                   // reputation | profession | item |
                                   // currency | custom
        std::string title;
        std::string description;
        bool        completed   = false;
        uint32      progress    = 0;
        uint32      required    = 0;
        uint32      targetId    = 0;
        uint32      order       = 0;

        // Optional type-specific helpers. Zero means "not set" and
        // serialiser omits it.
        uint32      questId        = 0;
        uint32      questChainId   = 0;
        // ej_instanceID: key into client's JOURNALINSTANCE table.
        // Consumed by EncounterJournal_DisplayInstance / EJ_SelectInstance.
        uint32      ejInstanceId   = 0;
        // WoW map id (for telemetry / teleport / fallback). NOT used by
        // the client to open EJ.
        uint32      mapId          = 0;
        uint32      encounterId    = 0;
        uint32      achievementId  = 0;
        uint32      factionId      = 0;
        uint32      professionId   = 0;
        uint32      itemId         = 0;
        uint32      currencyId     = 0;
    };

    struct Recommendation
    {
        uint32      id            = 0;
        std::string type;
        std::string title;
        std::string description;
        uint32      ejInstanceId  = 0; // JOURNALINSTANCE key
        uint32      mapId         = 0; // WoW map id (auxiliary)
        uint32      encounterId   = 0;
        uint32      questId       = 0;
        uint32      itemId        = 0;
        uint32      priority      = 0;
    };

    struct Reward
    {
        std::string type;
        uint32      itemId     = 0;
        uint32      count      = 1;
        uint32      currencyId = 0;
        uint32      amount     = 0;
    };

    struct Payload
    {
        uint32      version          = PAYLOAD_VERSION;
        uint64      characterGuid    = 0;

        uint32      stageId          = 0;
        std::string stageName;
        std::string stageDescription;
        uint32      progress         = 0;       // 0..100

        std::vector<Objective>      objectives;
        std::vector<Recommendation> recommendations;
        std::vector<Reward>         rewards;
    };

    class Mgr
    {
    public:
        static Mgr* instance();

        // Reads configuration. Safe to call multiple times.
        void LoadConfig();

        // Builds the per-character guide payload from IPP state.
        // The function is read-only with respect to the player.
        Payload BuildFor(Player* player) const;

        // Serialises a payload to a single JSON string.
        // Validates uniqueness of objective ids and clamps progress.
        std::string SerializeJson(Payload const& payload) const;
        std::string SerializeJson(Objective const& objective) const;

        bool IsEnabled() const  { return _enabled; }
        bool LogPayloads() const { return _logPayloads; }
        uint32 ChunkSize() const { return _chunkSize; }

        // Reloads (map_id -> ej_instance.id/name) cache from
        // custom_ej_instance. Called from LoadConfig() and from the
        // OnLoadCustomDatabaseTable world hook.
        void ReloadEncounterJournalCache();

    private:
        Mgr() = default;

        // Stage helpers based on ProgressionState.
        std::string StageName(uint32 stage) const;
        std::string StageDescription(uint32 stage) const;

        // Filling helpers.
        void AppendLevelObjective(Payload& out, Player* player,
                                  uint32 requiredLevel, uint32 order) const;
        void AppendQuestObjective(Payload& out, Player* player,
                                  uint32 questId, std::string const& title,
                                  uint32 order) const;
        void AppendDungeonObjective(Payload& out, uint32 mapId,
                                    std::string const& title,
                                    uint32 order) const;
        void AppendRaidObjective(Payload& out, uint32 mapId,
                                 std::string const& title,
                                 uint32 order) const;

        void AppendRecommendations(Payload& out, Player* player) const;

        struct EjInstanceRef
        {
            uint32      ejId = 0;     // custom_ej_instance.id
            std::string name;         // custom_ej_instance.name
        };

        // mapId -> EJ instance ref. Filled at config load.
        std::unordered_map<uint32, EjInstanceRef> _ejByMapId;

        bool   _enabled    = true;
        bool   _logPayloads = false;
        uint32 _chunkSize  = 230; // per-frame body capacity (addon channel)
    };
}

#define sPlayerGuideMgr ::MoonWell::PlayerGuide::Mgr::instance()

#endif // MOONWELL_PLAYER_GUIDE_MGR_H
