/**
 * Copyright (c) 2025-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 "arrayLiteralLowering.h"

#include "checker/types/ets/etsTupleType.h"
#include "compiler/lowering/util.h"

namespace ark::es2panda::compiler {

using AstNodePtr = ir::AstNode *;

std::string_view ArrayLiteralLowering::Name() const
{
    return "ArrayLiteralLowering";
}

ArenaVector<ir::Statement *> ArrayLiteralLowering::GenerateDefaultCallToConstructor(ir::Identifier *arraySymbol,
                                                                                    checker::Type *eleType,
                                                                                    checker::Signature *sig)
{
    std::stringstream ss;
    std::vector<ir::AstNode *> newStmts;
    if (!eleType->IsETSUnionType() && !eleType->IsETSAnyType()) {
        auto *indexSymbol = Gensym(Allocator());
        auto *lengthSymbol = Gensym(Allocator());
        auto *typeNode = checker_->AllocNode<ir::OpaqueTypeNode>(eleType, Allocator());
        ES2PANDA_ASSERT(typeNode != nullptr);

        ss << "let @@I1 : int = @@I2.length.toInt();";
        newStmts.emplace_back(lengthSymbol);
        newStmts.emplace_back(arraySymbol->Clone(Allocator(), nullptr));
        ss << "for (let @@I3 = 0; @@I4 < @@E5;  @@I6 = @@I7 + 1) {";
        newStmts.emplace_back(indexSymbol);
        newStmts.emplace_back(indexSymbol->Clone(Allocator(), nullptr));
        newStmts.emplace_back(lengthSymbol->Clone(Allocator(), nullptr));
        newStmts.emplace_back(indexSymbol->Clone(Allocator(), nullptr));
        newStmts.emplace_back(indexSymbol->Clone(Allocator(), nullptr));

        if (!sig->HasRestParameter()) {
            ss << "@@I8[@@I9] = new @@T10();";
        } else {
            ss << "let restArgs = new Array<Any>();";
            ss << "@@I8[@@I9] = new @@T10(restArgs);";
        }
        newStmts.emplace_back(arraySymbol->Clone(Allocator(), nullptr));
        newStmts.emplace_back(indexSymbol->Clone(Allocator(), nullptr));
        ES2PANDA_ASSERT(typeNode != nullptr);
        newStmts.emplace_back(typeNode->Clone(Allocator(), nullptr));
        ss << "}";
    } else {
        ArenaVector<ir::Statement *> emptyStatement(Allocator()->Adapter());
        return emptyStatement;
    }

    return parser_->CreateFormattedStatements(ss.str(), newStmts);
}

static bool IsInAnnotationContext(ir::AstNode *node)
{
    while (node != nullptr && !(node->IsClassDefinition() && node->AsClassDefinition()->IsGlobal())) {
        if (node->IsAnnotationDeclaration() || node->IsAnnotationUsage()) {
            return true;
        }
        node = node->Parent();
    }
    return false;
}
// CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic
ir::AstNode *ArrayLiteralLowering::TryTransformTupleConstructor(ir::ArrayExpression *literalArray,
                                                                checker::ETSTupleType *tupleType)
{
    std::size_t maxTupleTypes = checker_->GetGlobalTypesHolder()->VariadicTupleTypeThreshold();
    if (tupleType->GetTupleSize() <= maxTupleTypes) {
        tupleType->GetWrapperType()->ConstructSignatures();  // Required to instantiate properties of underlying type
        return literalArray;
    }

    // We need to generate special object creation for 'TupleN<...>' class.
    ES2PANDA_ASSERT_POS(literalArray->Elements().size() > maxTupleTypes, literalArray->Start());
    auto const &typeList = tupleType->GetTupleTypesList();
    std::size_t extraParamsNumber = literalArray->Elements().size() - maxTupleTypes;

    std::vector<ir::AstNode *> nodes {};
    auto *extraParams = Gensym(Allocator());
    auto *helperArray = Gensym(Allocator());
    auto *parent = literalArray->Parent();
    auto const range = literalArray->Range();

    std::string code = "let @@I1 : FixedArray<Any> = @@E2; ";
    nodes.emplace_back(helperArray);
    nodes.emplace_back(literalArray);
    literalArray->SetTsType(nullptr);

    code += "let @@I3 : Array<Any> = @@E4;";
    nodes.emplace_back(extraParams);
    nodes.emplace_back(CreateUninitializedResizableArray(
        Context(), parser_->CreateFormattedExpression(std::to_string(extraParamsNumber)),
        checker_->CreateETSResizableArrayType(checker_->GlobalETSAnyType())));

    code += "for (let i = 0; i < " + std::to_string(extraParamsNumber) + "; i = i + 1) { @@I5[i] = @@I6[i+" +
            std::to_string(maxTupleTypes) + "]} ";
    nodes.emplace_back(extraParams->Clone(Allocator(), nullptr));
    nodes.emplace_back(helperArray->Clone(Allocator(), nullptr));

    --maxTupleTypes;
    code += "new TupleN<";
    for (std::size_t i = 0U; i < maxTupleTypes; ++i) {
        code += typeList[i]->ToString() + ", ";
    }
    code += typeList[maxTupleTypes]->ToString() + ">(";
    std::size_t j = 7U;
    for (std::size_t i = 0U; i < maxTupleTypes; ++i, j += 2U) {
        code += "@@I" + std::to_string(j) + '[' + std::to_string(i) + "] as @@T" + std::to_string(j + 1U) + ", ";
        nodes.emplace_back(helperArray->Clone(Allocator(), nullptr));
        nodes.emplace_back(checker_->AllocNode<ir::OpaqueTypeNode>(typeList[i], Allocator()));
    }
    code += "@@I" + std::to_string(j) + '[' + std::to_string(maxTupleTypes) + "] as @@T" + std::to_string(j + 1U) +
            ", @@I" + std::to_string(j + 2U) + ");";
    nodes.emplace_back(helperArray->Clone(Allocator(), nullptr));
    nodes.emplace_back(checker_->AllocNode<ir::OpaqueTypeNode>(typeList[maxTupleTypes], Allocator()));
    nodes.emplace_back(extraParams->Clone(Allocator(), nullptr));

    auto *loweringResult = parser_->CreateFormattedExpression(code, nodes);
    loweringResult->SetParent(parent);
    SetSourceRangesRecursively(loweringResult, range);

    auto *scope = NearestScope(parent);
    auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder_, scope);
    CheckLoweredNode(varbinder_, checker_, loweringResult);
    return loweringResult;
}
// CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic
ir::AstNode *ArrayLiteralLowering::TryTransformLiteralArrayToRefArray(ir::ArrayExpression *literalArray)
{
    if (IsInAnnotationContext(literalArray)) {
        return literalArray;
    }

    auto literalArrayType = literalArray->TsType() != nullptr ? literalArray->TsType() : literalArray->PreferredType();
    if (literalArrayType->IsETSTupleType()) {
        return TryTransformTupleConstructor(literalArray, literalArrayType->AsETSTupleType());
    }

    if (!literalArrayType->IsETSResizableArrayType()) {
        return literalArray;
    }

    auto *arrayType = literalArrayType->AsETSResizableArrayType()->ElementType();
    std::vector<ir::AstNode *> newStmts;
    std::stringstream ss;
    ir::Identifier *genSymIdent = Gensym(Allocator());
    auto *type = checker_->AllocNode<ir::OpaqueTypeNode>(arrayType, Allocator());
    // NOTE(frontend):follow-up #30648
    if (literalArray->Elements().empty()) {
        ss << "let @@I1: Array<@@T2> = new Array<@@T3>(); @@I4;";
        newStmts.emplace_back(genSymIdent);
        newStmts.emplace_back(type);
        newStmts.emplace_back(type->Clone(Allocator(), nullptr));
        newStmts.emplace_back(genSymIdent->Clone(Allocator(), nullptr));
    } else {
        ir::Identifier *genSymIdent2 = Gensym(Allocator());
        bool elementIsUnboxable = arrayType->IsETSObjectType() && arrayType->AsETSObjectType()->IsBoxedPrimitive();
        ss << "let @@I1: " << (elementIsUnboxable ? "ValueArray" : "FixedArray") << "<@@T2> = @@E3;";
        ss << "let @@I4: Array<@@T5> = Array.create<@@T6>(@@I7.length, @@I8[0]);";
        ss << "for (let i = 1; i < @@I9.length; i = i + 1) { @@I10[i] = @@I11[i]} @@I12";
        newStmts.emplace_back(genSymIdent);
        newStmts.emplace_back(type);
        newStmts.emplace_back(literalArray);
        literalArray->SetTsType(nullptr);
        newStmts.emplace_back(genSymIdent2);
        newStmts.emplace_back(type->Clone(Allocator(), nullptr));
        newStmts.emplace_back(type->Clone(Allocator(), nullptr));
        newStmts.emplace_back(genSymIdent->Clone(Allocator(), nullptr));
        newStmts.emplace_back(genSymIdent->Clone(Allocator(), nullptr));
        newStmts.emplace_back(genSymIdent->Clone(Allocator(), nullptr));
        newStmts.emplace_back(genSymIdent2->Clone(Allocator(), nullptr));
        newStmts.emplace_back(genSymIdent->Clone(Allocator(), nullptr));
        newStmts.emplace_back(genSymIdent2->Clone(Allocator(), nullptr));
    }

    auto *parent = literalArray->Parent();
    auto *loweringResult = parser_->CreateFormattedExpression(ss.str(), newStmts);
    ES2PANDA_ASSERT(loweringResult != nullptr);
    loweringResult->SetRange(literalArray->Range());
    loweringResult->SetParent(parent);

    auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder_, NearestScope(parent));
    CheckLoweredNode(varbinder_, checker_, loweringResult);
    return loweringResult;
}

ir::AstNode *ArrayLiteralLowering::TryTransformNewArrayExprToRefArray(ir::ETSNewArrayInstanceExpression *newExpr)
{
    if (newExpr->TsType()->IsETSArrayType()) {
        return newExpr;
    }
    ES2PANDA_ASSERT(newExpr->TsType()->IsETSResizableArrayType());

    auto *arrayType = newExpr->TsType()->AsETSResizableArrayType()->ElementType();
    std::vector<ir::AstNode *> newStmts;
    auto *genSymIdent = Gensym(Allocator());

    std::stringstream ss;
    ss << "let @@I1: Array<@@T2> = @@E3;";
    auto *type = checker_->AllocNode<ir::OpaqueTypeNode>(arrayType, Allocator());
    newStmts.emplace_back(genSymIdent);
    newStmts.emplace_back(type);
    CreateUninitializedResizableArray(Context(), newExpr->Dimension(), newExpr->TsType());

    ArenaVector<ir::Statement *> statements(Allocator()->Adapter());
    auto *newArrStatement = parser_->CreateFormattedStatement(ss.str(), newStmts);
    statements.emplace_back(newArrStatement);
    auto newArrElementStatements = GenerateDefaultCallToConstructor(genSymIdent, arrayType, newExpr->Signature());
    statements.insert(statements.end(), newArrElementStatements.begin(), newArrElementStatements.end());
    auto returnStmt = parser_->CreateFormattedStatement("@@I1", genSymIdent->Clone(Allocator(), nullptr));
    statements.emplace_back(returnStmt);
    auto *loweringResult = checker_->AllocNode<ir::BlockExpression>(std::move(statements));
    loweringResult->SetRange(newExpr->Range());
    loweringResult->SetParent(newExpr->Parent());
    auto *scope = NearestScope(loweringResult->Parent());
    auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder_, scope);
    CheckLoweredNode(varbinder_, checker_, loweringResult);
    return loweringResult;
}

ir::Statement *ArrayLiteralLowering::CreateNestedArrayCreationStatement(ArenaVector<ir::Identifier *> &identDims,
                                                                        size_t currentDim, checker::Type *type,
                                                                        ir::Expression *expr, checker::Signature *sig)
{
    auto *genSymIdent = Gensym(Allocator());
    auto *arraySymbol = Gensym(Allocator());
    auto *lastDimIdent = identDims[currentDim - 1];
    auto *currentDimIdent = identDims[currentDim];
    auto *arrayType = type->AsETSResizableArrayType()->ElementType();
    auto arrayAccessExpr = checker_->AllocNode<ir::MemberExpression>(
        expr->Clone(Allocator(), nullptr)->AsExpression(), genSymIdent->Clone(Allocator(), nullptr),
        ir::MemberExpressionKind::ELEMENT_ACCESS, true, false);

    std::string creationTemplate =
        "for (let @@I1 = 0; @@I2 < @@I3; @@I4 = @@I5 + 1) { let @@I6 : Array<@@T7> = @@E8; "
        "@@E9 = @@I10}";
    ir::Statement *forUpdateStmt = parser_->CreateFormattedStatement(
        creationTemplate, genSymIdent, genSymIdent->Clone(Allocator(), nullptr),
        lastDimIdent->Clone(Allocator(), nullptr), genSymIdent->Clone(Allocator(), nullptr),
        genSymIdent->Clone(Allocator(), nullptr), arraySymbol, arrayType,
        CreateUninitializedResizableArray(Context(), currentDimIdent, type), arrayAccessExpr,
        arraySymbol->Clone(Allocator(), nullptr));
    if (identDims.size() > currentDim + 1) {
        auto consequentStmt =
            CreateNestedArrayCreationStatement(identDims, currentDim + 1, arrayType, arrayAccessExpr, sig);
        forUpdateStmt->AsForUpdateStatement()->Body()->AsBlockStatement()->AddStatement(consequentStmt);
    } else if (identDims.size() == currentDim + 1) {
        // For last dim, initialize the array elements.
        auto newArrElementStatements = GenerateDefaultCallToConstructor(arraySymbol, arrayType, sig);
        forUpdateStmt->AsForUpdateStatement()->Body()->AsBlockStatement()->AddStatements(newArrElementStatements);
    }

    return forUpdateStmt;
}

ArenaVector<ir::Identifier *> ArrayLiteralLowering::TransformDimVectorToIdentVector(
    ArenaVector<ir::Expression *> &dimVector, ArenaVector<ir::Statement *> &stmts)
{
    std::vector<ir::AstNode *> statements;
    ArenaVector<ir::Identifier *> idents(Allocator()->Adapter());
    auto addNode = [&statements](ir::AstNode *node) -> size_t {
        statements.emplace_back(node);
        return statements.size();
    };

    std::stringstream ss;
    for (size_t i = 0; i < dimVector.size(); ++i) {
        idents.emplace_back(Gensym(Allocator()));
        ss << "let @@I" << addNode(idents[i]) << " = @@E" << addNode(dimVector[i]->Clone(Allocator(), nullptr)) << ";";
    }
    auto parsedStatement = parser_->CreateFormattedStatements(ss.str(), statements);
    stmts.insert(stmts.end(), parsedStatement.begin(), parsedStatement.end());
    return idents;
}

ir::AstNode *ArrayLiteralLowering::TryTransformNewMultiDimArrayToRefArray(
    ir::ETSNewMultiDimArrayInstanceExpression *newExpr)
{
    if (newExpr->TsType()->IsETSArrayType()) {
        return newExpr;
    }
    ES2PANDA_ASSERT(newExpr->TsType()->IsETSResizableArrayType());
    ArenaVector<ir::Statement *> statements(Allocator()->Adapter());
    // Create outer forloop
    auto arrayType = newExpr->TsType()->AsETSResizableArrayType()->ElementType();
    auto *genSymIdent = Gensym(Allocator());
    std::string newArray = "let @@I1 : Array<@@T2> = @@E3;";
    auto idents = TransformDimVectorToIdentVector(newExpr->Dimensions(), statements);
    auto newArraystatement = parser_->CreateFormattedStatements(
        newArray, genSymIdent, arrayType,
        CreateUninitializedResizableArray(Context(), idents[0]->Clone(Allocator(), nullptr), newExpr->TsType()));
    auto nestedArrayCreationStmt =
        CreateNestedArrayCreationStatement(idents, 1, arrayType, genSymIdent, newExpr->Signature());
    auto returnStmt = parser_->CreateFormattedStatement("@@I1", genSymIdent->Clone(Allocator(), nullptr));
    statements.insert(statements.end(), newArraystatement.begin(), newArraystatement.end());
    statements.push_back(nestedArrayCreationStmt);
    statements.push_back(returnStmt);
    auto loweringResult = checker_->AllocNode<ir::BlockExpression>(std::move(statements));
    loweringResult->SetRange(newExpr->Range());
    loweringResult->SetParent(newExpr->Parent());
    auto *scope = NearestScope(loweringResult->Parent());
    auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder_, scope);
    CheckLoweredNode(varbinder_, checker_, loweringResult);
    return loweringResult;
}

void ArrayLiteralLowering::Setup()
{
    parser_ = Context()->parser->AsETSParser();
    varbinder_ = Context()->parserProgram->VarBinder()->AsETSBinder();
    checker_ = Context()->GetChecker()->AsETSChecker();
}

bool ArrayLiteralLowering::PerformForProgram(parser::Program *program)
{
    program->Ast()->TransformChildrenRecursively(
        [this](ir::AstNode *ast) -> AstNodePtr {
            if (ast->IsArrayExpression()) {
                return TryTransformLiteralArrayToRefArray(ast->AsArrayExpression());
            }
            if (ast->IsETSNewArrayInstanceExpression()) {
                return TryTransformNewArrayExprToRefArray(ast->AsETSNewArrayInstanceExpression());
            }
            if (ast->IsETSNewMultiDimArrayInstanceExpression()) {
                return TryTransformNewMultiDimArrayToRefArray(ast->AsETSNewMultiDimArrayInstanceExpression());
            }
            return ast;
        },
        Name());

    return true;
}

ArenaAllocator *ArrayLiteralLowering::Allocator()
{
    return checker_->Allocator();
}

}  // namespace ark::es2panda::compiler