#include "builtins/builtins.h"
#include "common.h"

using namespace goose::sema;

namespace goose::builtins
{
    class RegularFuncInvocationRule : public BaseFuncInvocationRule
    {
      public:
        Value invoke( Context& c, LocationId loc, const Value& callee, const Term& args,
            const Term& typeCheckedCallPat, TypeCheckingContext& tcc ) const final
        {
            auto preparedCallee = prepareFunc( c, 0, callee, typeCheckedCallPat, tcc );
            if( preparedCallee.isPoison() )
                return PoisonValue();

            auto callDecomp = Decompose( typeCheckedCallPat, Val< pvec >() );

            const auto& typeCheckedRType = callDecomp->get()->terms().front();
            auto typeCheckedArgs = DropVectorTerm( typeCheckedCallPat, 1 );

            preparedCallee.setLocationId( loc );

            auto argList = BuildArgListForCall( c, preparedCallee, typeCheckedArgs );
            if( !argList )
                return PoisonValue();
            auto argsInstrSeq = BuildArgsInstrSeq( *argList );
            auto argCount = VecSize( *argList );

            auto result = BuildComputedValue(
                typeCheckedRType, argsInstrSeq, preparedCallee, cir::Call( argCount, loc ) )
                              .setLocationId( loc );

            if( IsExternalFunc( callee ) )
            {
                // No dependency handling or eager evaluation on external funcs,
                // so we're done.
                return result;
            }

            bool comptime = false;

            // If we are part of call chain involving a comptime function, we need the call to be
            // comptime as well.
            for( auto&& fi : Func::FuncStack() | views::reverse )
            {
                if( fi.kind == FuncType::Kind::Comptime )
                {
                    comptime = true;
                    break;
                }
            }

            if( !comptime )
                return result;

            if( HaveUnmetDependencies( preparedCallee ) || !EvaluateComptimeCall( c, result ) )
            {
                DiagnosticsManager::GetInstance().emitErrorMessage(
                    loc, "this call couldn't be evaluated at compilation time." );
                return PoisonValue();
            }

            return result;
        }

        Value prepareFunc( const Context& c, LocationId funcValLocation, const Value& callee,
            const Term& typeCheckedCallPat, TypeCheckingContext& tcc ) const final
        {
            // TODO better description with the function's name if possible (we may need to
            // explicitely store it in the func)
            DiagnosticsContext dc( 0, true );
            VerbosityContext vc( Verbosity::Normal, true );

            return CompileFunc( c, callee );
        }
    };

    void SetupRegularFuncInvocationRule( Env& e )
    {
        e.invocationRuleSet()->addRule( ValueToEIR( ValuePattern( ANYTERM( _ ),

                                            ValueToEIR( Value( TypeType(),
                                                VEC( TSID( func ), ANYTERM( _ ), ANYTERM( _ ),
                                                    ANYTERM( _ ), ANYTERM( _ ), ANYTERM( _ ) ) ) ),

                                            ANYTERM( _ ) ) ),
            make_shared< RegularFuncInvocationRule >() );
    }
} // namespace goose::builtins
