/*
 * SPDX-License-Identifier: AGPL-3.0-or-later
 *
 * mod-taxi-builder: verbose runtime logging of taxi-related events.
 * Hooks creature gossip interactions (catches the moment a player clicks a
 * flight master — useful when the client crashes on dialog open) and polls
 * Player::IsInFlight() to log start/mid/end of taxi flights with positions
 * and current path. Output goes to Server.log via the standard logger; the
 * channel is `server.loading` so it always passes the default Appender
 * thresholds, but the `[taxi-log]` prefix lets you grep cleanly.
 */

#include "Chat.h"
#include "Creature.h"
#include "Log.h"
#include "ObjectGuid.h"
#include "Player.h"
#include "ScriptMgr.h"

#include <unordered_map>

namespace
{
    struct TaxiLogState
    {
        bool   inFlight    = false;
        uint32 lastPath    = 0;
        uint32 nextLogMs   = 0;
        float  lastX       = 0.f;
        float  lastY       = 0.f;
        float  lastZ       = 0.f;
        uint32 lastMapId   = 0;
    };

    std::unordered_map<ObjectGuid, TaxiLogState> g_taxiLog;

    constexpr uint32 PROGRESS_LOG_INTERVAL_MS = 1000;
}

class taxi_builder_gossip_log : public AllCreatureScript
{
public:
    taxi_builder_gossip_log() : AllCreatureScript("taxi_builder_gossip_log") { }

    bool CanCreatureGossipHello(Player* player, Creature* creature) override
    {
        if (!player || !creature)
            return false;

        bool const isFlightMaster = creature->IsTaxi();
        LOG_INFO("server.loading",
            "[taxi-log] GossipHello: player={} (lvl {}) creature={} entry={} guid={} "
            "isFlightMaster={} pos=({:.2f},{:.2f},{:.2f}) map={} npcflag=0x{:X}",
            player->GetName(), uint32(player->GetLevel()),
            creature->GetName(), creature->GetEntry(),
            creature->GetGUID().ToString(),
            isFlightMaster ? "YES" : "no",
            creature->GetPositionX(), creature->GetPositionY(), creature->GetPositionZ(),
            creature->GetMapId(),
            creature->GetUInt32Value(UNIT_NPC_FLAGS));
        // returning false leaves default handling in place (we just observed)
        return false;
    }
};

class taxi_builder_flight_log : public PlayerScript
{
public:
    taxi_builder_flight_log() : PlayerScript("taxi_builder_flight_log") { }

    void OnPlayerLogout(Player* player) override
    {
        if (player)
            g_taxiLog.erase(player->GetGUID());
    }

    void OnPlayerUpdate(Player* player, uint32 diff) override
    {
        if (!player)
            return;

        bool const nowFlying = player->IsInFlight();
        auto it = g_taxiLog.find(player->GetGUID());

        // Fast path: not flying and never started — skip allocation.
        if (!nowFlying && it == g_taxiLog.end())
            return;

        TaxiLogState& state = (it != g_taxiLog.end()) ? it->second : g_taxiLog[player->GetGUID()];

        // START of flight
        if (nowFlying && !state.inFlight)
        {
            uint32 path = player->m_taxi.GetCurrentTaxiPath();
            auto const& nodes = player->m_taxi.GetPath();
            std::string nodeList;
            for (uint32 n : nodes)
            {
                if (!nodeList.empty()) nodeList += ",";
                nodeList += std::to_string(n);
            }
            LOG_INFO("server.loading",
                "[taxi-log] START   player={} path={} nodes=[{}] start=({:.1f},{:.1f},{:.1f}) map={}",
                player->GetName(), path, nodeList,
                player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(),
                player->GetMapId());

            state.inFlight  = true;
            state.lastPath  = path;
            state.lastX     = player->GetPositionX();
            state.lastY     = player->GetPositionY();
            state.lastZ     = player->GetPositionZ();
            state.lastMapId = player->GetMapId();
            state.nextLogMs = PROGRESS_LOG_INTERVAL_MS;
            return;
        }

        // END of flight
        if (!nowFlying && state.inFlight)
        {
            LOG_INFO("server.loading",
                "[taxi-log] END     player={} lastPath={} end=({:.1f},{:.1f},{:.1f}) map={}",
                player->GetName(), state.lastPath,
                player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(),
                player->GetMapId());
            state.inFlight = false;
            return;
        }

        // Mid-flight throttled progress log (~1s)
        if (!nowFlying)
            return;

        if (state.nextLogMs > diff)
        {
            state.nextLogMs -= diff;
            return;
        }
        state.nextLogMs = PROGRESS_LOG_INTERVAL_MS;

        float dx = player->GetPositionX() - state.lastX;
        float dy = player->GetPositionY() - state.lastY;
        float dz = player->GetPositionZ() - state.lastZ;
        float moved = std::sqrt(dx*dx + dy*dy + dz*dz);

        LOG_INFO("server.loading",
            "[taxi-log] MID     player={} path={} pos=({:.1f},{:.1f},{:.1f}) map={} dMoved={:.1f}yd",
            player->GetName(), state.lastPath,
            player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(),
            player->GetMapId(), moved);

        state.lastX     = player->GetPositionX();
        state.lastY     = player->GetPositionY();
        state.lastZ     = player->GetPositionZ();
        state.lastMapId = player->GetMapId();
    }
};

void AddSC_taxi_builder_logger()
{
    new taxi_builder_gossip_log();
    new taxi_builder_flight_log();
}
