#pragma once
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <imgui.h>
#include "Config.h"
#include "Runtime/DayZRuntimeService.h"
#include "Web/BulletTrackCache.h"

struct ImDrawList;

class GameOverlay {
public:
    // Exposed so static helpers in GameOverlay.cpp can take it by reference.
    struct BoneHistory {
        SkeletonBones prev{};
        SkeletonBones curr{};
        int64_t       prevMs      = 0;
        int64_t       currMs      = 0;      // time of the last positional change (for extrapolation velocity)
        int64_t       lastReadMs  = 0;      // time of the most recent valid read (for staleness gate)
        bool          initialized = false;
    };

    explicit GameOverlay(DayZRuntimeService& service,
                         OverlayConfig       cfg,
                         std::string         cfgPath)
        : m_service(service)
        , m_cfg(std::move(cfg))
        , m_cfgPath(std::move(cfgPath))
    {
        m_showPlayers    = m_cfg.showPlayers;
        m_showAnimals    = m_cfg.showAnimals;
        m_showZombies    = m_cfg.showZombies;
        m_showItems      = m_cfg.showItems;
        m_showBox        = m_cfg.showBox;
        m_showSkeleton   = m_cfg.showSkeleton;
        m_showHeadDot      = m_cfg.showHeadDot;
        m_showCorpses      = m_cfg.showCorpses;
        m_showWeapon         = m_cfg.showWeapon;
        m_showHealthBar      = m_cfg.showHealthBar;
        m_showHealthNumber   = m_cfg.showHealthNumber;
        m_showBallisticDot   = m_cfg.showBallisticDot;
        m_showBulletTrails   = m_cfg.showBulletTrails;
        m_itemCategories = m_cfg.itemCategories;
        m_playerMaxDist  = m_cfg.playerMaxDist;
        m_animalMaxDist  = m_cfg.animalMaxDist;
        m_zombieMaxDist  = m_cfg.zombieMaxDist;
        m_itemMaxDist    = m_cfg.itemMaxDist;
        m_pendingW       = m_cfg.overlayWidth;
        m_pendingH       = m_cfg.overlayHeight;
        m_renderW        = m_cfg.renderWidth;
        m_renderH        = m_cfg.renderHeight;
        m_stretchToFill  = m_cfg.stretchToFill;
        m_pendingRW      = m_cfg.renderWidth;
        m_pendingRH      = m_cfg.renderHeight;
    }

    /// Called once after the web radar server has started so the Radar tab
    /// knows what URL(s) to display.
    void SetWebRadarPort(int port);

    /// Called once after ImGui fonts are built so DrawItems can tier by distance.
    void SetLootFonts(ImFont* close, ImFont* lootFar) {
        m_fontLootClose = close;
        m_fontLootFar   = lootFar;
    }

    /// Register a callback invoked when the user clicks the exit button.
    /// The caller should set its stop flag inside the callback.
    void SetExitCallback(std::function<void()> cb) { m_exitCallback = std::move(cb); }

    /// Register a callback invoked when the user applies a new overlay resolution.
    void SetResizeCallback(std::function<void(int,int)> cb) { m_resizeCallback = std::move(cb); }

    // Called each frame.  w/h a re the overlay window pixel dimensions.
    void Draw(float w, float h);

private:
    DayZRuntimeService& m_service;
    OverlayConfig       m_cfg;
    std::string         m_cfgPath;

    // Cached entity snapshot (updated once per frame via GetLatestUpdate).
    std::shared_ptr<const RuntimeUpdate> m_snapshot;

public:
    bool IsMenuOpen() const { return m_menuOpen; }

private:

    // Live camera — updated every frame directly from the service,
    // bypassing the entity snapshot so it reflects the latest DMA read.
    CameraData m_liveCamera;

    // Web radar
    int                      m_webPort = 7777;
    std::vector<std::string> m_webUrls;  // populated by SetWebRadarPort()

    // Exit callback — invoked when the user presses the exit button.
    std::function<void()>            m_exitCallback;
    // Resize callback — invoked when the user applies a new resolution.
    std::function<void(int, int)>    m_resizeCallback;

    // Menu state
    bool  m_menuOpen    = false;
    float m_menuAlpha   = 0.0f;
    int   m_tab         = 0;
    int   m_subtab      = 0;

    // ESP toggles (initialised from config in constructor)
    bool m_showPlayers    = true;
    bool m_showAnimals    = true;
    bool m_showZombies    = true;
    bool m_showItems      = true;
    bool m_showBox        = true;
    bool m_showSkeleton   = true;
    bool m_showHeadDot      = false;
    bool m_showCorpses      = false;
    bool m_showWeapon         = true;
    bool m_showHealthBar      = true;
    bool m_showHealthNumber   = false;
    bool m_showBallisticDot   = false;
    bool m_showBulletTrails   = false;
    bool m_debugSkeleton      = false;  // draws named bone dots for the closest player

    // Per-category item enabled map (key = filterKey, missing = enabled)
    std::map<std::string, bool> m_itemCategories;

    // Loot fonts — set once via SetLootFonts() after ImGui atlas is built.
    ImFont* m_fontLootClose = nullptr;  // 16 px — used for items within 50 m
    ImFont* m_fontLootFar   = nullptr;  // 11 px — used for items >= 50 m

    // Last-known positions for zombies keyed by entity address.
    // Used to avoid a single failed position read causing a visible blink.
    std::unordered_map<uint64_t, Vector3> m_zombieLastPos;

    // Per-player velocity estimate — updated each frame from position delta.
    struct PlayerVelEntry {
        Vector3 pos;
        int64_t ms  = 0;
        Vector3 vel;  // smoothed m/s, EMA α=0.35
    };
    std::unordered_map<uint64_t, PlayerVelEntry> m_playerVel;

    // Bullet trail tracker — owned by the overlay, fed from each update's bullet list.
    BulletTrackCache m_bulletTracks;

    // ---- Bone interpolation / extrapolation ----
    std::unordered_map<uint64_t, BoneHistory> m_playerBoneHistory;
    std::unordered_map<uint64_t, BoneHistory> m_zombieBoneHistory;
    int64_t m_frameTimeMs = 0;  // set once per Draw() call

    static int64_t NowMs();

    // Pending resolution override — edited in menu, applied on button press.
    int   m_pendingW  = 0;
    int   m_pendingH  = 0;
    int   m_pendingRW = 0;  // render width (stretched res)
    int   m_pendingRH = 0;  // render height (stretched res)

    // Stretched resolution state (active values, applied on "Apply")
    int   m_renderW       = 0;
    int   m_renderH       = 0;
    bool  m_stretchToFill = true;

    // ESP distance limits (initialised from config in constructor)
    float m_playerMaxDist = 1000.0f;
    float m_animalMaxDist = 1000.0f;
    float m_zombieMaxDist = 500.0f;
    float m_itemMaxDist   = 200.0f;

    // Low-level NDC projection helper — maps a world position to overlay pixels.
    // Returns false if the position is behind the camera or off-screen.
    static bool WorldToScreen(const CameraData& cam,
                               const Vector3&    worldPos,
                               float& sx, float& sy,
                               float w, float h);

    // Viewport-aware projection wrapper.  Use this instead of WorldToScreen
    // directly — it applies stretched-resolution / letterbox offsets.
    bool Proj(const CameraData& cam, const Vector3& worldPos,
              float& sx, float& sy, float overlayW, float overlayH) const;

    static float Dist(const Vector3& a, const Vector3& b);

    // Copy live menu state (m_show*, distances, item categories, resolution)
    // back into m_cfg prior to a Save.
    void SyncConfig();

    void DrawESP          (float w, float h, const RuntimeUpdate& u, const CameraData& cam);
    void DrawPlayers      (ImDrawList* dl, const RuntimeUpdate& u, const CameraData& cam, float w, float h);
    void DrawAnimals      (ImDrawList* dl, const RuntimeUpdate& u, const CameraData& cam, float w, float h);
    void DrawZombies      (ImDrawList* dl, const RuntimeUpdate& u, const CameraData& cam, float w, float h);
    void DrawItems        (ImDrawList* dl, const RuntimeUpdate& u, const CameraData& cam, float w, float h);
    void DrawBulletTrails (ImDrawList* dl, const CameraData& cam, float w, float h);

    // Draw skeleton bone segments for a single entity.  color = ImU32 (unsigned int).
    void DrawSkeleton(ImDrawList* dl, const SkeletonBones& bones,
                      const CameraData& cam, float w, float h,
                      unsigned int color, bool isZombie) const;

    // Debug: draw every named bone as a labeled dot for the closest player.
    void DrawSkeletonDebug(ImDrawList* dl, const RuntimeUpdate& u,
                           const CameraData& cam, float w, float h) const;
};
