/// \file main.cpp — Pipeline demo Phase 0: MemoryReader(fake) → DecisionEngine →
/// InputWorker. Chứng minh skeleton chạy được: SPSC ring, threading model, logger.
/// Chưa có đọc bộ nhớ thật hay KMBox — sẽ vào Phase 1/2 (docs/07-roadmap.md).

#include "core/log.hpp"
#include "core/spsc_ring.hpp"
#include "core/types.hpp"
#include "core/version.hpp"

#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstring>
#include <memory>
#include <thread>

#ifdef _WIN32
#include <windows.h> // SetConsoleOutputCP — hiển thị UTF-8 đúng trên console Windows
#endif

using namespace std::chrono_literals;

namespace {

constexpr std::string_view kTag = "app";

std::shared_ptr<const ares::GameState> make_fake_snapshot(int tick) {
    auto gs = std::make_shared<ares::GameState>();
    gs->timestamp            = std::chrono::steady_clock::now();
    gs->context              = ares::EncounterKind::Dungeon;
    gs->player.class_id      = 2; // Hunter
    gs->player.max_hp        = 10'000;
    // Giả lập HP rơi dần để kích hoạt nhánh Dodge/Emergency ở tick cuối
    gs->player.hp            = static_cast<std::uint32_t>(10'000 - tick * 150);
    gs->player.pos           = {100.0f + tick, 0.0f, 200.0f};
    gs->player.skill_cooldowns = {0.f, 0.f, 2.0f, 0.f, 0.f, 0.f, 0.f, 0.f};

    ares::Entity boss;
    boss.id       = 1001;
    boss.kind     = ares::EntityKind::Boss;
    boss.hp_pct   = 0.75f;
    boss.pos      = {110.0f + tick, 0.0f, 205.0f};
    boss.state_id = 0x2A; // telegraph cue giả lập
    gs->entities.push_back(boss);
    return gs;
}

ares::ActionCommand decide(const ares::GameState& gs, int& rotation_index) {
    ares::ActionCommand cmd;
    const auto hp_pct = static_cast<float>(gs.player.hp) / static_cast<float>(gs.player.max_hp);
    if (hp_pct < 0.55f) {
        // Emergency dodge — pre-empt mọi thứ (docs/06-decision-engine.md §4.1)
        cmd.type     = ares::ActionType::Dodge;
        cmd.priority = ares::Priority::Emergency;
        return cmd;
    }
    // Rotation đơn giản: bấm tuần tự các slot có cooldown sẵn sàng
    cmd.type     = ares::ActionType::CastSkill;
    cmd.priority = ares::Priority::Combat;
    cmd.payload  = ares::SkillSlot{static_cast<std::uint8_t>(rotation_index % 3 + 1)};
    ++rotation_index;
    return cmd;
}

ares::InputEvent humanize(const ares::ActionCommand& cmd) {
    // Humanizer v0: chỉ cộng reaction delay; path Bezier/jitter vào Phase 2
    ares::InputEvent ev;
    ev.kind = (cmd.type == ares::ActionType::Dodge) ? ares::InputKind::KeyPress
                                                    : ares::InputKind::MouseMoveRel;
    ev.execute_at = std::chrono::steady_clock::now() + 120ms;
    if (auto* slot = std::get_if<ares::SkillSlot>(&cmd.payload)) {
        const auto text = std::format("km.key({},1)", 50 + slot->slot);
        std::ranges::copy(text, ev.payload.begin());
    } else if (cmd.type == ares::ActionType::Dodge) {
        constexpr std::string_view text = "km.key(16,1)"; // Shift = dodge
        std::ranges::copy(text, ev.payload.begin());
    }
    return ev;
}

} // namespace

int main() {
#ifdef _WIN32
    SetConsoleOutputCP(CP_UTF8);
#endif
    ares::log::info(kTag, "{} v{}.{}.{} — pipeline demo (fake memory, fake input)",
                    ares::kProjectName, ares::kVersionMajor, ares::kVersionMinor, ares::kVersionPatch);

    // Thread 1 (producer) → perception ring → Thread 2 (decision) → input ring → Thread 3 (input)
    ares::SpscRing<std::shared_ptr<const ares::GameState>, 256> perception_ring;
    ares::SpscRing<ares::InputEvent, 512> input_ring;
    std::atomic<bool> producer_done{false};
    std::atomic<bool> decision_done{false};

    std::jthread memory_thread([&] {
        ares::log::debug("memory", "fake MemoryReader: 30 ticks @ ~10ms");
        for (int tick = 0; tick < 30; ++tick) {
            while (!perception_ring.push(make_fake_snapshot(tick))) {
                std::this_thread::yield();
            }
            std::this_thread::sleep_for(10ms);
        }
        producer_done.store(true, std::memory_order_release);
        ares::log::debug("memory", "producer done");
    });

    std::jthread decision_thread([&] {
        int rotation_index = 0;
        while (true) {
            std::shared_ptr<const ares::GameState> gs;
            if (!perception_ring.pop(gs)) {
                if (producer_done.load(std::memory_order_acquire)) {
                    break;
                }
                std::this_thread::yield();
                continue;
            }
            const auto cmd = decide(*gs, rotation_index);
            while (!input_ring.push(humanize(cmd))) {
                std::this_thread::yield();
            }
            ares::log::debug("decision", "tick hp={}/{} → {} (prio={})",
                             gs->player.hp, gs->player.max_hp,
                             static_cast<int>(cmd.type), static_cast<int>(cmd.priority));
        }
        decision_done.store(true, std::memory_order_release);
        ares::log::debug("decision", "decision done");
    });

    int executed = 0;
    while (!(decision_done.load(std::memory_order_acquire) &&
             input_ring.size_approx() == 0)) {
        ares::InputEvent ev;
        if (input_ring.pop(ev)) {
            ++executed;
            ares::log::info("input", "[{}] → KMBox (payload='{}')",
                            static_cast<int>(ev.kind),
                            std::string_view(ev.payload.data(),
                                             std::strlen(ev.payload.data())));
        } else {
            std::this_thread::yield();
        }
    }

    ares::log::info(kTag, "pipeline demo hoàn tất: {} input events đã thực thi. OK ✔", executed);
    return 0;
}
