#pragma once

#include <memory>
#include <optional>
#include <shared_mutex>
#include <unordered_map>
#include "./base/events.hpp"
#include "./base/event_bus.hpp"
#include "./detail/symbol/types.hpp"
#include "../protocol/protocol.hpp"

extern "C" {
#include <tree_sitter/api.h>
}


extern "C" const TSLanguage* tree_sitter_tsf(void);

namespace lsp::service
{
    class TreeSitter
    {
    public:
        TreeSitter();
        TreeSitter(const TreeSitter&) = delete;
        TreeSitter& operator=(const TreeSitter&) = delete;
        TreeSitter(TreeSitter&& other) noexcept;
        ~TreeSitter();

        bool SetLanguage(const TSLanguage* language);
        TSTree* Parse(const char* content, size_t length, TSTree* old_tree = nullptr);
        TSParser* GetRawParser() const;

    private:
        TSParser* parser_;
    };

    class SyntaxTree
    {
    public:
        explicit SyntaxTree(TSTree* tree);
        ~SyntaxTree();

        void ApplyEdit(const protocol::TextDocumentContentChangeEvent& change, const protocol::string& content);
        TSTree* Get() const;
        TSNode GetRootNode() const;

    private:
        std::unique_ptr<TSTree, void(*)(TSTree*)> tree_;
    };


    class SyntaxTreeManager
    {
    public:
        void StoreTree(const protocol::DocumentUri& uri, std::unique_ptr<SyntaxTree> tree);
        void RemoveTree(const protocol::DocumentUri& uri);
        SyntaxTree* GetTree(const protocol::DocumentUri& uri);
        const SyntaxTree* GetTree(const protocol::DocumentUri& uri) const;
        size_t GetTreeCount() const;
        void Clear();

    private:
        mutable std::shared_mutex mutex_;
        std::unordered_map<protocol::DocumentUri, std::unique_ptr<SyntaxTree>> trees_;
    };

    class Parser
    {
    public:
        explicit Parser(std::shared_ptr<EventBus> event_bus);
        ~Parser();

        TSTree* GetTree(const protocol::DocumentUri& uri) const;
        TSParser* GetRawParser() const;

        static std::optional<symbol::EditingSymbolTable> ParseTsfFile(const std::string& file_path);

    private:
        struct FileMetadata
        {
            symbol::TsfFileType file_type;
            std::string primary_symbol;
            language::symbol::SymbolKind primary_kind;
        };

        static std::optional<FileMetadata> InferFileMetadata(const language::symbol::SymbolTable& table);

        void OnDocumentOpened(const events::DocumentOpend& event);
        void OnDocumentChanged(const events::DocumentChanged& event);
        void OnDocumentClosed(const events::DocumentClosed& event);

    private:
        TreeSitter parser_;
        SyntaxTreeManager syntax_tree_manager_;
        std::shared_ptr<EventBus> event_bus_;
    };
}
