#include "Web/BulletTrackCache.h"

#include <algorithm>
#include <cmath>
#include <unordered_set>

// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------

static float Distance3(const Vector3& a, const Vector3& b) {
    float dx = b.x - a.x;
    float dy = b.y - a.y;
    float dz = b.z - a.z;
    return std::sqrt(dx * dx + dy * dy + dz * dz);
}

static float Length3(const Vector3& v) {
    return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}

// -------------------------------------------------------------------------
// BulletTrackCache::Update
// -------------------------------------------------------------------------

void BulletTrackCache::Update(const std::vector<DayZBulletEntry>& bullets,
                              int64_t nowMs)
{
    std::lock_guard<std::mutex> lk(m_mutex);

    // Build set of addresses present in this snapshot.
    std::unordered_set<uint64_t> liveSet;
    liveSet.reserve(bullets.size());
    for (const auto& b : bullets) {
        if (b.address != 0)
            liveSet.insert(b.address);
    }

    // ----------------------------------------------------------------
    // Process each live bullet entry.
    // ----------------------------------------------------------------
    for (const auto& entry : bullets) {
        if (entry.address == 0 || !entry.position.has_value()) continue;

        const Vector3& pos = entry.position.value();
        Vector3 dir = entry.direction.has_value() ? entry.direction.value() : Vector3{};

        auto it = m_tracks.find(entry.address);
        if (it == m_tracks.end()) {
            // New bullet — create track.
            BulletTrack t;
            t.address    = entry.address;
            t.firstSeenMs = nowMs;
            t.lastSeenMs  = nowMs;
            t.currentPos  = pos;
            t.initialPos  = pos;
            t.finalPos    = pos;
            t.initialDir  = dir;
            t.lastDir     = dir;
            t.speedMps    = 0.0f;
            t.isActive    = true;
            t.isPhantom   = false;
            t.isCompleted = false;
            t.points.push_back({ nowMs, pos });
            m_tracks.emplace(entry.address, std::move(t));
        } else {
            BulletTrack& t = it->second;

            // Speed calculation.
            float elapsed = static_cast<float>(nowMs - t.lastSeenMs) / 1000.0f;
            if (elapsed > 0.0f) {
                float dist  = Distance3(t.currentPos, pos);
                float speed = dist / elapsed;
                // Clamp to [0, 900] m/s (well above any DayZ projectile).
                t.speedMps = std::max(0.0f, std::min(900.0f, speed));
            }

            t.lastSeenMs = nowMs;
            t.currentPos = pos;
            t.finalPos   = pos;
            if (Length3(dir) > 0.001f)
                t.lastDir = dir;
            t.isActive  = true;
            t.isPhantom = false;
            t.points.push_back({ nowMs, pos });

            // Finalise check: last 2+ points within kStillM over kStillMs.
            if (!t.isCompleted && t.points.size() >= 2) {
                const auto& prev = t.points[t.points.size() - 2];
                int64_t span = nowMs - prev.first;
                float   dist = Distance3(prev.second, pos);
                if (span >= kStillMs && dist <= kStillM) {
                    t.isCompleted = true;
                    t.finalizedMs = nowMs;
                }
            }
        }
    }

    // ----------------------------------------------------------------
    // Mark tracks not in this snapshot as phantom.
    // ----------------------------------------------------------------
    for (auto& [addr, t] : m_tracks) {
        if (liveSet.find(addr) == liveSet.end()) {
            if (!t.isCompleted) {
                t.isActive  = false;
                t.isPhantom = true;
            }
        }
    }

    // ----------------------------------------------------------------
    // Erase stale tracks.
    // ----------------------------------------------------------------
    for (auto it = m_tracks.begin(); it != m_tracks.end();) {
        const BulletTrack& t = it->second;
        bool erase = false;

        if (t.isCompleted) {
            // Keep for kRetentionMs after finalisation.
            erase = (nowMs - t.finalizedMs) > kRetentionMs;
        } else if (t.isPhantom) {
            erase = (nowMs - t.lastSeenMs) > kPhantomLifetimeMs;
        }

        if (erase)
            it = m_tracks.erase(it);
        else
            ++it;
    }
}

// -------------------------------------------------------------------------
// BulletTrackCache::GetSnapshot
// -------------------------------------------------------------------------

std::vector<BulletTrack> BulletTrackCache::GetSnapshot() const {
    std::lock_guard<std::mutex> lk(m_mutex);
    std::vector<BulletTrack> out;
    out.reserve(m_tracks.size());
    for (const auto& [addr, t] : m_tracks)
        out.push_back(t);
    return out;
}
