#include "sema.h"

namespace goose::sema
{
    ptr< TemplateRule > GetTemplateRuleSet( const Context& c, const Term& tpl )
    {
        const auto& rules = c.env()->templateRuleSet()->rules();

        MatchScore bestScore;
        ptr< TemplateRule > pBestRule;
        bool ambiguous = false;

        for( auto&& [s, rule] : Match( tpl, rules ) )
        {
            auto score = s.score();
            if( !pBestRule || score > bestScore )
            {
                bestScore = score;
                pBestRule = rule;
                ambiguous = false;
                continue;
            }

            if( score < bestScore )
                continue;

            ambiguous = true;
        }

        if( ambiguous )
            G_ERROR( "ambiguous template rule" );

        return pBestRule;
    }

    bool PrepareTemplate( TemplateContext& c, const Term& tpl )
    {
        const auto pTemplateRuleSet = GetTemplateRuleSet( c.semaContext(), tpl );
        if( !pTemplateRuleSet )
            return true;

        return pTemplateRuleSet->prepare( c, tpl );
    }

    optional< Term > BuildTemplateSignature( const TemplateContext& c, const Term& tpl )
    {
        const auto pTemplateRuleSet = GetTemplateRuleSet( c.semaContext(), tpl );
        if( !pTemplateRuleSet )
            return nullopt;

        return pTemplateRuleSet->buildSignature( c, tpl );
    }

    Generator< Value > BuildTemplateParamDecls( const Context& c, const Term& tpl, TermGen& args )
    {
        const auto pTemplateRuleSet = GetTemplateRuleSet( c, tpl );
        if( !pTemplateRuleSet )
            co_return;

        co_yield pTemplateRuleSet->buildParamDecls( c, tpl, args );
    }

    void TemplateSetup( const Context& c, TypeCheckingContext& tcc, const Term& tpl )
    {
        const auto pTemplateRuleSet = GetTemplateRuleSet( c, tpl );
        if( !pTemplateRuleSet )
            return;

        pTemplateRuleSet->setup( c, tcc, tpl );
    }

    uint64_t TemplateHashTypePredicates( const Context& c, const TypeCheckingContext& tcc, const Term& tpl )
    {
        const auto pTemplateRuleSet = GetTemplateRuleSet( c, tpl );
        if( !pTemplateRuleSet )
            return 0;

        return pTemplateRuleSet->hashTypePredicates( c, tcc, tpl );
    }

    extern optional< Term > BuildTemplateArgPattern( const Context& c, const Term& tpl )
    {
        const auto pTemplateRuleSet = GetTemplateRuleSet( c, tpl );
        if( !pTemplateRuleSet )
            return nullopt;

        return pTemplateRuleSet->buildArgPattern( c, tpl );
    }

    extern bool IsTemplatePackExpr( const Context& c, const Term& tpl )
    {
        const auto pTemplateRuleSet = GetTemplateRuleSet( c, tpl );
        if( !pTemplateRuleSet )
            return false;

        return pTemplateRuleSet->isPackExpr( c, tpl );
    }
}
