/**
 * Copyright (c) 2021-2026 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ETSfunction.h"

#include "varbinder/varbinder.h"
#include "varbinder/ETSBinder.h"
#include "util/helpers.h"
#include "varbinder/scope.h"
#include "varbinder/variable.h"
#include "compiler/base/lreference.h"
#include "compiler/core/ETSGen.h"
#include "compiler/core/envScope.h"
#include "ir/base/spreadElement.h"
#include "ir/base/scriptFunction.h"
#include "ir/base/classDefinition.h"
#include "ir/base/classProperty.h"
#include "ir/ets/etsParameterExpression.h"
#include "ir/expressions/callExpression.h"
#include "ir/expressions/identifier.h"
#include "ir/statements/blockStatement.h"
#include "ir/statements/expressionStatement.h"
#include "ir/ts/tsEnumDeclaration.h"
#include "ir/ts/tsEnumMember.h"
namespace ark::es2panda::compiler {

void ETSFunction::CompileConstructorWithExplicitSuper(ETSGen *etsg, const ArenaVector<ir::Statement *> &statements)
{
    bool fieldsInitialized = false;
    for (const auto *stmt : statements) {
        stmt->Compile(etsg);
        if (!fieldsInitialized && stmt->IsExpressionStatement() &&
            stmt->AsExpressionStatement()->GetExpression()->IsCallExpression() &&
            stmt->AsExpressionStatement()->GetExpression()->AsCallExpression()->Callee()->IsSuperExpression()) {
            CompileInstanceFieldInitializers(etsg);
            fieldsInitialized = true;
        }
    }
    ES2PANDA_ASSERT(fieldsInitialized);
}

void ETSFunction::CompileSourceBlock(ETSGen *etsg, const ir::BlockStatement *block)
{
    auto *scriptFunc = etsg->RootNode()->AsScriptFunction();

    if (scriptFunc->IsEnum()) {
        // NOTE: add enum methods
    } else if (scriptFunc->IsStaticBlock()) {
        CompileAsStaticBlock(etsg);
    } else if (scriptFunc->IsConstructor()) {
        CompileAsConstructor(etsg, scriptFunc);
    }

    const auto &statements = block->Statements();

    if (statements.empty()) {
        etsg->SetFirstStmt(block);
        ExtendWithDefaultReturn(etsg, block, scriptFunc);
        return;
    }

    etsg->SetFirstStmt(statements.front());

    if (scriptFunc->IsConstructor() && scriptFunc->IsExplicitSuperCall()) {
        CompileConstructorWithExplicitSuper(etsg, statements);
    } else {
        etsg->CompileStatements(statements);
    }

    if (!statements.back()->IsReturnStatement()) {
        ExtendWithDefaultReturn(etsg, statements.back(), scriptFunc);
    }
}

void ETSFunction::ExtendWithDefaultReturn(ETSGen *etsg, const ir::AstNode *node, const ir::ScriptFunction *scriptFunc)
{
    if (etsg->ReturnType()->IsETSVoidType()) {
        etsg->EmitReturnVoid(node);
        return;
    }

    etsg->LoadDefaultValue(node, scriptFunc->Signature()->ReturnType());
    etsg->ReturnAcc(node);
}

void ETSFunction::CompileAsStaticBlock(ETSGen *etsg)
{
    const auto *classDef = etsg->ContainingObjectType()->GetDeclNode()->AsClassDefinition();

    if (classDef->IsGlobal() && classDef->IsInitInCctor()) {
        return;
    }

    for (const auto *prop : classDef->Body()) {
        if (!prop->IsClassProperty() || !prop->IsStatic()) {
            continue;
        }

        // Don't compile variable initializers if they present in '_$init$_" method
        auto *const item = prop->AsClassProperty();

        if (item->Value() != nullptr) {
            item->Compile(etsg);
        }
    }
}

void ETSFunction::CompileInstanceFieldInitializers(ETSGen *etsg)
{
    if (etsg->RootNode()->AsScriptFunction()->IsExplicitThisCall()) {
        return;
    }

    const auto *classDef = etsg->ContainingObjectType()->GetDeclNode()->AsClassDefinition();

    for (const auto *prop : classDef->Body()) {
        if (prop->IsClassProperty() && !prop->IsStatic()) {
            prop->AsClassProperty()->Compile(etsg);
        }
    }
}

void ETSFunction::CompileAsConstructor(ETSGen *etsg, const ir::ScriptFunction *scriptFunc)
{
    ES2PANDA_ASSERT(!scriptFunc->IsImplicitSuperCallNeeded());
    ES2PANDA_ASSERT(!scriptFunc->IsExplicitThisCall() || !scriptFunc->IsExplicitSuperCall());

    if (scriptFunc->IsExplicitSuperCall()) {
        return;
    }

    CompileInstanceFieldInitializers(etsg);
}

void ETSFunction::CompileFunction(ETSGen *etsg)
{
    const auto *decl = etsg->RootNode()->AsScriptFunction();
    if (decl->IsDeclare() || decl->IsExternal()) {
        return;  // such methods should've been filtered in advance
    }
    if (decl->Signature()->Owner()->GetDeclNode()->IsDeclare()) {
        return;  // AST inconsistency!
    }
    if (auto *const body = decl->Body(); body != nullptr && body->IsBlockStatement()) {
        CompileSourceBlock(etsg, body->AsBlockStatement());
    }
}

void ETSFunction::Compile(ETSGen *etsg)
{
    FunctionRegScope lrs(etsg);
    auto *topScope = etsg->TopScope();

    if (topScope->IsFunctionScope()) {
        CompileFunction(etsg);
    } else {
        ES2PANDA_ASSERT(topScope->IsGlobalScope());
        CompileSourceBlock(etsg, etsg->RootNode()->AsBlockStatement());
    }

    etsg->FinalizeRegAllocation();
    etsg->SortCatchTables();
}

}  // namespace ark::es2panda::compiler