#pragma once
#include <map>
#include <string>
#include <set>
#include <optional>
#include "graph.hpp"
#include "type.hpp"

struct type_env;
using type_env_ptr = std::shared_ptr<type_env>;

enum class visibility { global,local };

class type_env {
    private:
        struct variable_data {
            type_scheme_ptr type;
            visibility vis;
            std::optional<std::string> mangled_name;

            variable_data()
                : variable_data(nullptr, visibility::local, std::nullopt) {}
            variable_data(type_scheme_ptr t, visibility v, std::optional<std::string> n)
                : type(std::move(t)), vis(v), mangled_name(std::move(n)) {}
        };

        type_env_ptr parent;
        std::map<std::string, variable_data> names;
        std::map<std::string, type_ptr> type_names;

    public:
        type_env(type_env_ptr p) : parent(std::move(p)) {}
        type_env() : type_env(nullptr) {}

        void find_free(const type_mgr& mgr, std::set<std::string>& into) const;
        void find_free_except(const type_mgr& mgr, const group& avoid,
                std::set<std::string>& into) const;
        type_scheme_ptr lookup(const std::string& name) const;
        bool is_global(const std::string& name) const;
        void set_mangled_name(const std::string& name, const std::string& mangled);
        const std::string& get_mangled_name(const std::string& name) const;
        type_ptr lookup_type(const std::string& name) const;
        void bind(const std::string& name, type_ptr t,
                visibility v = visibility::local);
        void bind(const std::string& name, type_scheme_ptr t,
                visibility v = visibility::local);
        void bind_type(const std::string& type_name, type_ptr t);
        void generalize(const std::string& name, const group& grp, type_mgr& mgr);
};


type_env_ptr type_scope(type_env_ptr parent);
