#include <memory>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <spdlog/spdlog.h>
#include "./base/event_bus.hpp"
#include "./base/events.hpp"
#include "./document.hpp"

namespace lsp::service
{
    Document::Document(std::shared_ptr<EventBus> event_bus) :
        event_bus_(std::move(event_bus)) {}

    Document::~Document() = default;

    void Document::OpenDocument(const protocol::DidOpenTextDocumentParams& params)
    {
        {
            std::unique_lock<std::shared_mutex> lock(mutex_);
            documents_.emplace(params.textDocument.uri, document::TextDocument(params.textDocument));
        }

        event_bus_->Publish(events::DocumentOpend{ params });

        spdlog::debug("Document opened: {}", params.textDocument.uri);
    }

    void Document::UpdateDocument(const protocol::DidChangeTextDocumentParams& params)
    {
        protocol::string content;
        {
            std::unique_lock<std::shared_mutex> lock(mutex_);
            auto it = documents_.find(params.textDocument.uri);
            if (it == documents_.end())
            {
                spdlog::warn("Attempting to update non-existent document: {}", params.textDocument.uri);
                return;
            }
            for (const auto& change : params.contentChanges)
                it->second.ApplyChange(change);
            if (params.textDocument.version)
                it->second.SetVersion(params.textDocument.version);
            content = it->second.GetContent();
        }

        event_bus_->Publish(events::DocumentChanged{ .uri = params.textDocument.uri, .version = params.textDocument.version, .changes = params.contentChanges, .content = std::move(content) });

        spdlog::debug("Document updated: {}", params.textDocument.uri);
    }

    void Document::CloseDocument(const protocol::DidCloseTextDocumentParams& params)
    {
        {
            std::unique_lock<std::shared_mutex> lock(mutex_);
            documents_.erase(params.textDocument.uri);
        }
        event_bus_->Publish(events::DocumentClosed{ params });

        spdlog::debug("Document closed: {}", params.textDocument.uri);
    }

    std::optional<protocol::string> Document::GetContent(const protocol::DocumentUri& uri) const
    {
        std::shared_lock<std::shared_mutex> lock(mutex_);
        auto it = documents_.find(uri);
        if (it != documents_.end())
            return it->second.GetContent();
        return std::nullopt;
    }

    std::optional<protocol::integer> Document::GetVersion(const protocol::DocumentUri& uri) const
    {
        std::shared_lock<std::shared_mutex> lock(mutex_);
        auto it = documents_.find(uri);
        if (it != documents_.end())
            return it->second.GetVersion();
        return std::nullopt;
    }

    std::vector<protocol::DocumentUri> Document::GetAllDocumentUris() const
    {
        std::shared_lock<std::shared_mutex> lock(mutex_);
        std::vector<protocol::DocumentUri> uris;
        uris.reserve(documents_.size());
        for (const auto& [uri, _] : documents_)
            uris.push_back(uri);
        return uris;
    }
}
