#pragma once
#include <memory>
#include <vector>
#include "instruction.hpp"
#include "llvm_context.hpp"
#include "type_env.hpp"

struct ast;
using ast_ptr = std::unique_ptr<ast>;

struct definition {
    virtual ~definition() = default;
    
    virtual void typecheck_first(type_mgr& mgr, type_env& env) = 0;
    virtual void typecheck_second(type_mgr& mgr, const type_env& env) const = 0;
    virtual void resolve(const type_mgr& mgr) = 0;
    virtual void compile() = 0;
    virtual void gen_llvm_first(llvm_context& ctx) = 0;
    virtual void gen_llvm_second(llvm_context& ctx) = 0;
};

using definition_ptr = std::unique_ptr<definition>;

struct constructor {
    std::string name;
    std::vector<std::string> types;
    int8_t tag;

    constructor(std::string n, std::vector<std::string> ts)
        : name(std::move(n)), types(std::move(ts)) {}
};

using constructor_ptr = std::unique_ptr<constructor>;

struct definition_defn : public definition {
    std::string name;
    std::vector<std::string> params;
    ast_ptr body;

    type_ptr return_type;
    std::vector<type_ptr> param_types;

    std::vector<instruction_ptr> instructions;

    llvm::Function* generated_function;

    definition_defn(std::string n, std::vector<std::string> p, ast_ptr b)
        : name(std::move(n)), params(std::move(p)), body(std::move(b)) {

    }

    void typecheck_first(type_mgr& mgr, type_env& env);
    void typecheck_second(type_mgr& mgr, const type_env& env) const;
    void resolve(const type_mgr& mgr);
    void compile();
    void gen_llvm_first(llvm_context& ctx);
    void gen_llvm_second(llvm_context& ctx);
};

struct definition_data : public definition {
    std::string name;
    std::vector<constructor_ptr> constructors;

    definition_data(std::string n, std::vector<constructor_ptr> cs)
        : name(std::move(n)), constructors(std::move(cs)) {}

    void typecheck_first(type_mgr& mgr, type_env& env);
    void typecheck_second(type_mgr& mgr, const type_env& env) const;
    void resolve(const type_mgr& mgr);
    void compile();
    void gen_llvm_first(llvm_context& ctx);
    void gen_llvm_second(llvm_context& ctx);
};
