#include <Evaluator/evaluator.hpp>
#include <Evaluator/evaluator_error.hpp>
#include <Evaluator/Core/ExprResult.hpp>

namespace Fig
{
    ExprResult Evaluator::eval(Ast::Expression exp, ContextPtr ctx)
    {
        using Ast::AstType;
        AstType type = exp->getType();
        switch (type)
        {
            case AstType::ValueExpr: {
                auto val = std::static_pointer_cast<Ast::ValueExprAst>(exp);
               
                return val->val;
            }
            case AstType::VarExpr: {
                auto varExpr = std::static_pointer_cast<Ast::VarExprAst>(exp);

                return check_unwrap_lv(evalVarExpr(varExpr, ctx)).get(); // LvObject -> RvObject
            }
            case AstType::BinaryExpr: {
                auto bin = std::static_pointer_cast<Ast::BinaryExprAst>(exp);
               
                return evalBinary(bin, ctx);
            }
            case AstType::UnaryExpr: {
                auto un = std::static_pointer_cast<Ast::UnaryExprAst>(exp);
               
                return evalUnary(un, ctx);
            }
            case AstType::TernaryExpr: {
                auto te = std::static_pointer_cast<Ast::TernaryExprAst>(exp);
               
                return evalTernary(te, ctx);
            }
            case AstType::MemberExpr:
            case AstType::IndexExpr: return check_unwrap_lv(evalLv(exp, ctx)).get();

            case AstType::FunctionCall: {
                auto fnCall = std::static_pointer_cast<Ast::FunctionCallExpr>(exp);
                return evalFunctionCall(fnCall, ctx);
            }
            case AstType::FunctionLiteralExpr: {
                auto fnLiteral = std::static_pointer_cast<Ast::FunctionLiteralExprAst>(exp);
               

                Ast::BlockStatement body = nullptr;
                if (fnLiteral->isExprMode())
                {
                    Ast::Expression exprBody = fnLiteral->getExprBody();
                   

                    const Ast::AstAddressInfo &aai = exprBody->getAAI();
                    Ast::Return st = std::make_shared<Ast::ReturnSt>(exprBody);
                    st->setAAI(aai);

                    body = std::make_shared<Ast::BlockStatementAst>();
                    body->stmts.push_back(st); // convert to Ast::Statement
                    body->setAAI(aai);
                }
                else
                {
                    body = fnLiteral->getBlockBody();
                   
                }
                Function fn(FString(std::format("<LambdaFn>")),fnLiteral->paras, ValueType::Any, body, ctx
                            /*
                                pass the ctx(fnLiteral eval context) as closure context
                            */
                );
                return std::make_shared<Object>(std::move(fn));
            }
            case AstType::InitExpr: {
                auto initExpr = std::static_pointer_cast<Ast::InitExprAst>(exp);
               
                return evalInitExpr(initExpr, ctx);
            }

            case AstType::ListExpr: {
                auto lstExpr = std::static_pointer_cast<Ast::ListExprAst>(exp);
               

                List list;
                for (auto &exp : lstExpr->val) { list.push_back(check_unwrap(eval(exp, ctx))); }
                return std::make_shared<Object>(std::move(list));
            }

            case AstType::MapExpr: {
                auto mapExpr = std::static_pointer_cast<Ast::MapExprAst>(exp);
               

                Map map;
                for (auto &[key, value] : mapExpr->val) {
                    map[check_unwrap(eval(key, ctx))] = check_unwrap(eval(value, ctx));
                }
                return std::make_shared<Object>(std::move(map));
            }

            default: {
                throw RuntimeError(FString(std::format("err type of expr: {}", magic_enum::enum_name(type))));
            }
        }
        return Object::getNullInstance(); // ignore warning
    }
};