#include "Lila.hpp"

#include "ClangInterpreter.hpp"
#include "Server.hpp"

#include "Commentator.hpp"

#ifdef MAYAFLUX_PLATFORM_MACOS
#include <CoreFoundation/CoreFoundation.h>
#endif

#ifdef MAYAFLUX_PLATFORM_WINDOWS
#include "MayaFlux/Transitive/Parallel/Dispatch.hpp"
#endif

namespace Lila {

Lila::Lila()
    : m_interpreter(std::make_unique<ClangInterpreter>())
    , m_current_mode(OperationMode::Direct)
    , server_loop_rate(0.1F)
{
    LILA_DEBUG(Emitter::SYSTEM, "Lila instance created");
}

Lila::~Lila()
{
    stop_server();
    LILA_DEBUG(Emitter::SYSTEM, "Lila instance destroyed");
}

bool Lila::initialize(OperationMode mode, int server_port) noexcept
{
    LILA_INFO(Emitter::SYSTEM, "Initializing Lila");
    m_current_mode = mode;

    if (!initialize_interpreter()) {
        LILA_ERROR(Emitter::SYSTEM, "Failed to initialize interpreter");
        return false;
    }

    if (mode == OperationMode::Server || mode == OperationMode::Both) {
        if (!initialize_server(server_port)) {
            LILA_ERROR(Emitter::SYSTEM, "Failed to initialize server");
            return false;
        }
    }

    LILA_INFO(Emitter::SYSTEM, "Lila initialized successfully");
    return true;
}

bool Lila::initialize_interpreter()
{
    LILA_DEBUG(Emitter::SYSTEM, "Initializing interpreter subsystem");
    return m_interpreter->initialize();
}

bool Lila::initialize_server(int port)
{
    if (m_server && m_server->is_running()) {
        LILA_WARN(Emitter::SYSTEM, "Stopping existing server before starting new one");
        stop_server();
    }

    LILA_DEBUG(Emitter::SYSTEM,
        std::string("Initializing server on port ") + std::to_string(port));

    m_server = std::make_unique<Server>(port);

    m_server->set_message_handler([this](std::string_view message) {
        return this->handle_server_message(message);
    });

    return m_server->start();
}

std::expected<std::string, std::string> Lila::handle_server_message(std::string_view message)
{
    if (message.empty()) {
        return R"({"status":"error","message":"Empty message"})";
    }

    auto result = m_interpreter->eval(std::string(message));

    if (result.success) {
        if (m_success_callback) {
            m_success_callback();
        }
        return "";
    }

    if (m_error_callback) {
        m_error_callback(result.error);
    }

    return std::unexpected(escape_json(result.error));
}

bool Lila::eval(const std::string& code)
{
    if (!m_interpreter) {
        LILA_ERROR(Emitter::SYSTEM, "Cannot eval: interpreter not initialized");
        return false;
    }

    auto result = m_interpreter->eval(code);

    if (result.success && m_success_callback) {
        m_success_callback();
    } else if (!result.success && m_error_callback) {
        m_error_callback(result.error);
    }

    return result.success;
}

bool Lila::eval_file(const std::string& filepath)
{
    if (!m_interpreter) {
        LILA_ERROR(Emitter::SYSTEM, "Cannot eval file: interpreter not initialized");
        return false;
    }

    auto result = m_interpreter->eval_file(filepath);
    return result.success;
}

void Lila::start_server(int port)
{
    LILA_INFO(Emitter::SYSTEM,
        std::string("Starting server on port ") + std::to_string(port));

    initialize_server(port);
}

void Lila::stop_server()
{
    if (m_server) {
        LILA_INFO(Emitter::SYSTEM, "Stopping server");
        m_server->stop();
        m_server.reset();
    }
}

bool Lila::is_server_running() const
{
    return m_server && m_server->is_running();
}

void* Lila::get_symbol_address(const std::string& name)
{
    return m_interpreter ? m_interpreter->get_symbol_address(name) : nullptr;
}

std::vector<std::string> Lila::get_defined_symbols()
{
    return m_interpreter ? m_interpreter->get_defined_symbols() : std::vector<std::string> {};
}

void Lila::add_include_path(const std::string& path)
{
    if (m_interpreter) {
        m_interpreter->add_include_path(path);
    }
}

void Lila::add_compile_flag(const std::string& flag)
{
    if (m_interpreter) {
        m_interpreter->add_compile_flag(flag);
    }
}

void Lila::on_success(std::function<void()> callback)
{
    m_success_callback = std::move(callback);
    LILA_DEBUG(Emitter::SYSTEM, "Success callback registered");
}

void Lila::on_error(std::function<void(const std::string&)> callback)
{
    m_error_callback = std::move(callback);
    LILA_DEBUG(Emitter::SYSTEM, "Error callback registered");
}

void Lila::on_server_started(std::function<void()> callback)
{
    if (m_server) {
        m_server->on_server_started(std::move(callback));
        LILA_DEBUG(Emitter::SYSTEM, "Server started callback registered");
    }
}

void Lila::on_server_client_connected(std::function<void(const ClientInfo&)> callback)
{
    if (m_server) {
        m_server->on_client_connected(std::move(callback));
        LILA_DEBUG(Emitter::SYSTEM, "Client connected callback registered");
    }
}

void Lila::on_server_client_disconnected(std::function<void(const ClientInfo&)> callback)
{
    if (m_server) {
        m_server->on_client_disconnected(std::move(callback));
        LILA_DEBUG(Emitter::SYSTEM, "Client disconnected callback registered");
    }
}

std::string Lila::get_last_error() const
{
    return m_interpreter ? m_interpreter->get_last_error() : "Interpreter not initialized";
}

OperationMode Lila::get_current_mode() const
{
    return m_current_mode;
}

void Lila::await_shutdown(const std::atomic<bool>* external_flag)
{
    LILA_INFO(Emitter::SYSTEM, "Entering main event loop");

#ifdef MAYAFLUX_PLATFORM_MACOS
    while (!m_shutdown_requested.load(std::memory_order_acquire) && (!external_flag || external_flag->load(std::memory_order_acquire))) {
        if (!is_server_running()) {
            LILA_ERROR(Emitter::SYSTEM, "Server stopped unexpectedly");
            break;
        }
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, server_loop_rate, false);
    }

#elif defined(MAYAFLUX_PLATFORM_WINDOWS)
    if (MayaFlux::Parallel::g_MainThreadId == 0) {
        MayaFlux::Parallel::g_MainThreadId = GetCurrentThreadId();
    }

    MSG msg;
    PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);

    while (!m_shutdown_requested.load(std::memory_order_acquire)
        && (!external_flag || external_flag->load(std::memory_order_acquire))) {
        if (!is_server_running()) {
            LILA_ERROR(Emitter::SYSTEM, "Server stopped unexpectedly");
            break;
        }

        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) {
                m_shutdown_requested.store(true, std::memory_order_release);
                break;
            }

            if (msg.message == MAYAFLUX_WM_DISPATCH) {
                auto* task = reinterpret_cast<std::function<void()>*>(msg.lParam);
                if (task) {
                    (*task)();
                    delete task;
                }
            } else {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }

        std::this_thread::sleep_for(
            std::chrono::milliseconds(static_cast<int>(server_loop_rate * 1000.0F)));
    }

#else
    while (!m_shutdown_requested.load(std::memory_order_acquire) && (!external_flag || external_flag->load(std::memory_order_acquire))) {
        if (!is_server_running()) {
            LILA_ERROR(Emitter::SYSTEM, "Server stopped unexpectedly");
            break;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(server_loop_rate * 1000)));
    }
#endif

    LILA_INFO(Emitter::SYSTEM, "Exiting main event loop");
}

void Lila::request_shutdown()
{
    m_shutdown_requested.store(true, std::memory_order_release);

#ifdef MAYAFLUX_PLATFORM_MACOS
    CFRunLoopStop(CFRunLoopGetMain());
#elif defined(MAYAFLUX_PLATFORM_WINDOWS)
    PostThreadMessage(MayaFlux::Parallel::g_MainThreadId, WM_QUIT, 0, 0);
#endif
}

std::string Lila::escape_json(const std::string& str)
{
    std::string escaped;
    escaped.reserve(str.size());

    for (char c : str) {
        switch (c) {
        case '"':
            escaped += "\\\"";
            break;
        case '\\':
            escaped += "\\\\";
            break;
        case '\n':
            escaped += "\\n";
            break;
        case '\r':
            escaped += "\\r";
            break;
        case '\t':
            escaped += "\\t";
            break;
        default:
            if (c >= 0x20) {
                escaped += c;
            }
        }
    }
    return escaped;
}

} // namespace Lila
