#pragma once

#include "data_chunk.h"

#include <library/cpp/yson/node/node_io.h>
#include <saas/library/rtyt/lib/cypress/tree.pb.h>
#include <mapreduce/yt/interface/client_method_options.h>
#include <google/protobuf/descriptor.h>
#include <kernel/multipart_archive/multipart.h>
#include <util/generic/guid.h>
#include <util/generic/overloaded.h>
#include <util/string/cast.h>

namespace NRTYT {

static const TString PathAttr = "@_rtyt_fs_path";
static const TString StorageRootAttr = "@_rtyt_storage_root";
static const TString DescriptorAttr = "@_rtyt_descriptor";

class TCypressClient;

using TListNode = TVector<NYT::TNodeId>;

using TMapNode = TMap<TString, NYT::TNodeId>;

struct TFileNode {
    TDataChunk Chunk;
    TFileNode()
    {
    }
};

struct TTableNode {
    TString DataPath;
    TAtomicSharedPtr<::google::protobuf::DescriptorPool> DescriptorPool;
    TTableNode(const TString& dataPath)
        : DataPath(dataPath)
    {
    }
    void Clear() {
        TArchiveOwner::Remove(DataPath);
    }
};




class TCypressNode {
public:
    using ENodeType = NYT::ENodeType;
    using TMountNode = TCypressClient*; // does not own memory
    using TValue = std::variant<
        bool,
        i64,
        ui64,
        double,
        TString,
        TListNode,
        TMapNode,
        TTableNode,
        TFileNode,
        TMountNode
        >;

private:
    NYT::TNodeId Id;
    TString Name;
    TValue Value;
    NYT::TNode AttrNode;
    NYT::TNode::TMapType& Attributes;
    TCypressNode* Parent = nullptr;


    void SetParent(TCypressNode* node) {
        Parent = node;
    }
    TMapNode& GetMap();
    const TMapNode& GetMap() const;

public:
    TCypressNode(NYT::TNodeId id, const TString& name, TCypressClient* bindedClient, const NYT::TNode& attributes = NYT::TNode::CreateMap())
        : Id(id)
        , Name(name)
        , Value(bindedClient)
        , AttrNode(attributes)
        , Attributes(AttrNode.AsMap()) {
    }

    TCypressNode(NYT::TNodeId id, const TString& name, ENodeType type, const NYT::TNode& attributes = NYT::TNode::CreateMap())
        : Id(id)
        , Name(name)
        , AttrNode(attributes)
        , Attributes(AttrNode.AsMap())
    {
        switch (type) {
        case ENodeType::NT_BOOLEAN:
            Value = TValue(std::in_place_type_t<bool>());
            break;
        case ENodeType::NT_INT64:
            Value = TValue(std::in_place_type_t<i64>());
            break;
        case ENodeType::NT_UINT64:
            Value = TValue(std::in_place_type_t<ui64>());
            break;
        case ENodeType::NT_STRING:
            Value = TValue(std::in_place_type_t<TString>());
            break;
        case ENodeType::NT_LIST:
            Value = TValue(std::in_place_type_t<TListNode>());
            break;
        case ENodeType::NT_MAP:
            Value = TValue(std::in_place_type_t<TMapNode>());
            break;
        case ENodeType::NT_TABLE:
            if (!Attributes.contains(PathAttr)) {
                TGUID tableId;
                CreateGuid(&tableId);
                TString path = GetGuidAsString(tableId);
                Value = TValue(std::in_place_type_t<TTableNode>(), path);
                Attributes[PathAttr] = path;
            } else {
                TString path = Attributes[PathAttr].AsString();
                Value = TValue(std::in_place_type_t<TTableNode>(), path);
            }
            break;
        case ENodeType::NT_FILE:
            Value = TValue(std::in_place_type_t<TFileNode>());
            break;
        default:
            ythrow yexception() << "This type of cypress node is not supported yet " << type;
        }
    }

    TCypressNode(NYT::TNodeId id, const TString& name, TCypressNode* nodeToCopy)
        : Id(id)
        , Name(name)
        , Value(nodeToCopy->Value)
        , AttrNode(nodeToCopy->AttrNode)
        , Attributes(nodeToCopy->Attributes)
    {
    }

    NYT::TNodeId GetId() const {
        return Id;
    }
    const TString& GetName() const {
        return Name;
    }
    ENodeType GetType() const {
        return std::visit(TOverloaded{
            [](bool) { return ENodeType::NT_BOOLEAN; },
            [](i64) { return ENodeType::NT_INT64; },
            [](ui64) { return ENodeType::NT_UINT64; },
            [](double) { return ENodeType::NT_DOUBLE; },
            [](const TListNode&) { return ENodeType::NT_LIST; },
            [](const TMapNode&) { return ENodeType::NT_MAP; },
            [](const TMountNode&) { return ENodeType::NT_MAP; },
            [](const TTableNode&) { return ENodeType::NT_TABLE; },
            [](const TFileNode&) { return ENodeType::NT_FILE; },
        }, Value);
    }

    const NYT::TNode& GetAttributes() const {
        return AttrNode;
    }

    TCypressNode* GetParent() const {
        return Parent;
    }

    bool HasChild(const TString& name) const;
    NYT::TNodeId GetChild(const TString& name) const;
    void AddChild(TCypressNode* node);
    void RemoveChild(const TString& name);
    TVector<NYT::TNodeId> ListChildren() const;

    void ClearData();
    const TTableNode& AsTable() const;
    TTableNode& AsTable();

    bool IsMountPoint() const;

    const TCypressClient* AsMountedClient() const;
    TCypressClient* AsMountedClient();

    void Save(NTree::TNode* out, const TString& pathPrefix) {
        out->SetPath(pathPrefix + GetName());
        out->SetType(ToString<NYT::ENodeType>(GetType()));
        out->SetAttributes(NYT::NodeToYsonString(GetAttributes()));
    }
};

using TCypressNodePtr = TCypressNode*;

} // namespace NRTYT
