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

#include "util/helpers.h"
#include "varbinder/scope.h"
#include "ir/typeNode.h"
#include "ir/expressions/identifier.h"
#include "ir/expressions/objectExpression.h"
#include "ir/expressions/assignmentExpression.h"
#include "ir/expressions/arrayExpression.h"
#include "ir/base/spreadElement.h"
#include "ir/base/property.h"
#include "ir/expression.h"

namespace ark::es2panda::checker {
void DestructuringContext::Prepare(ir::TypeNode *typeAnnotation, ir::Expression *initializer,
                                   const lexer::SourcePosition &loc)
{
    if (typeAnnotation != nullptr) {
        typeAnnotation->Check(checker_);
        Type *annotationType = typeAnnotation->GetType(checker_);

        if (initializer != nullptr) {
            checker_->ElaborateElementwise(annotationType, initializer, loc);
        }

        validateTypeAnnotation_ = true;
        inferredType_ = annotationType;
        return;
    }

    if (initializer != nullptr) {
        if (!initializer->IsObjectExpression()) {
            validateObjectPatternInitializer_ = false;
        }

        inferredType_ = initializer->Check(checker_);
    }
}

void DestructuringContext::HandleDestructuringAssignment(ir::Identifier *ident, Type *inferredType, Type *defaultType)
{
    if (ident->Variable() == nullptr) {
        checker_->ThrowTypeError({"Cannot find name '", ident->Name(), "'."}, ident->Start());
    }

    varbinder::Variable *variable = ident->Variable();
    ES2PANDA_ASSERT(variable->TsType());

    if (defaultType != nullptr && !checker_->IsTypeAssignableTo(defaultType, variable->TsType())) {
        checker_->ThrowAssignmentError(defaultType, variable->TsType(), ident->Start());
    }

    if (inferredType != nullptr && !checker_->IsTypeAssignableTo(inferredType, variable->TsType())) {
        checker_->ThrowAssignmentError(inferredType, variable->TsType(), ident->Start());
    }
}

void DestructuringContext::SetInferredTypeForVariable(varbinder::Variable *var, Type *inferredType,
                                                      const lexer::SourcePosition &loc)
{
    ES2PANDA_ASSERT(var);

    if (!checker_->HasStatus(CheckerStatus::IN_CONST_CONTEXT)) {
        inferredType = checker_->GetBaseTypeOfLiteralType(inferredType);
    }

    if (var->TsType() != nullptr) {
        checker_->IsTypeIdenticalTo(var->TsType(), inferredType, diagnostic::DIFFERENT_SUBSEQ_DECL,
                                    {var->Name(), var->TsType(), inferredType}, loc);
        return;
    }

    if (signatureInfo_ != nullptr) {
        signatureInfo_->params.push_back(var->AsLocalVariable());
        signatureInfo_->minArgCount++;
    }

    var->SetTsType(inferredType);
}

void DestructuringContext::ValidateObjectLiteralType(ObjectType *objType, ir::ObjectExpression *objPattern)
{
    for (const auto *sourceProp : objType->Properties()) {
        const util::StringView &sourceName = sourceProp->Name();
        bool found = false;

        for (const auto *targetProp : objPattern->Properties()) {
            if (targetProp->IsRestElement()) {
                continue;
            }

            ES2PANDA_ASSERT(targetProp->IsProperty());
            const util::StringView &targetName = targetProp->AsProperty()->Key()->AsIdentifier()->Name();

            if (sourceName == targetName) {
                found = true;
                break;
            }
        }

        if (!found) {
            checker_->ThrowTypeError({"Object literal may only specify known properties, and property '", sourceName,
                                      "' does not exist in the pattern."},
                                     objPattern->Start());
        }
    }
}

// Helper function to reduce HandleAssignmentPattern and pass code checker
void DestructuringContext::HandleAssignmentPatternArrayPattern(ir::AssignmentExpression *assignmentPattern,
                                                               Type *inferredType)
{
    ArrayDestructuringContext nextContext = ArrayDestructuringContext(
        {checker_, assignmentPattern->Left(), inAssignment_, convertTupleToArray_, nullptr, nullptr});
    nextContext.SetInferredType(inferredType);
    nextContext.Start();
}

// Helper function to reduce HandleAssignmentPattern and pass code checker
void DestructuringContext::HandleAssignmentPatternIdentifier(ir::AssignmentExpression *assignmentPattern,
                                                             Type *defaultType, Type *inferredType)
{
    if (validateTypeAnnotation_ && !checker_->IsTypeAssignableTo(defaultType, inferredType)) {
        checker_->ThrowAssignmentError(defaultType, inferredType, assignmentPattern->Left()->Start());
    }

    SetInferredTypeForVariable(assignmentPattern->Left()->AsIdentifier()->Variable(), inferredType,
                               assignmentPattern->Start());
}

void DestructuringContext::HandleAssignmentPattern(ir::AssignmentExpression *assignmentPattern, Type *inferredType,
                                                   bool validateDefault)
{
    if (!assignmentPattern->Left()->IsArrayPattern()) {
        checker_->RemoveStatus(CheckerStatus::FORCE_TUPLE);
    }

    Type *defaultType = assignmentPattern->Right()->Check(checker_);
    if (!checker_->HasStatus(CheckerStatus::IN_CONST_CONTEXT)) {
        defaultType = checker_->GetBaseTypeOfLiteralType(defaultType);
    }
    ES2PANDA_ASSERT(defaultType != nullptr);
    if (validateDefault && assignmentPattern->Right()->IsObjectExpression() &&
        assignmentPattern->Left()->IsObjectPattern()) {
        ES2PANDA_ASSERT(defaultType != nullptr);
        ValidateObjectLiteralType(defaultType->AsObjectType(), assignmentPattern->Left()->AsObjectPattern());
    }

    Type *initType = inferredType;
    checker_->AddStatus(CheckerStatus::FORCE_TUPLE);

    if (validateTypeAnnotation_) {
        if (inferredType == nullptr) {
            inferredType = checker_->GlobalUndefinedType();
        }
    } else {
        if (inferredType == nullptr) {
            inferredType = defaultType;
        } else if (inferredType->IsUnionType()) {
            inferredType->AsUnionType()->AddConstituentType(defaultType, checker_->Relation());
        } else {
            inferredType = checker_->CreateUnionType({inferredType, defaultType});
        }
    }

    if (assignmentPattern->Left()->IsIdentifier()) {
        if (inAssignment_) {
            HandleDestructuringAssignment(assignmentPattern->Left()->AsIdentifier(), initType, defaultType);
            return;
        }

        HandleAssignmentPatternIdentifier(assignmentPattern, defaultType, inferredType);
        return;
    }

    if (assignmentPattern->Left()->IsArrayPattern()) {
        HandleAssignmentPatternArrayPattern(assignmentPattern, inferredType);
        return;
    }

    ES2PANDA_ASSERT(assignmentPattern->Left()->IsObjectPattern());
    ObjectDestructuringContext nextContext = ObjectDestructuringContext(
        {checker_, assignmentPattern->Left(), inAssignment_, convertTupleToArray_, nullptr, nullptr});
    nextContext.SetInferredType(inferredType);
    nextContext.Start();
}

void ArrayDestructuringContext::ValidateInferredType()
{
    ES2PANDA_ASSERT(inferredType_ != nullptr);
    if (!inferredType_->IsArrayType() && !inferredType_->IsUnionType() &&
        (!inferredType_->IsObjectType() || !inferredType_->AsObjectType()->IsTupleType())) {
        checker_->ThrowTypeError(
            {"Type ", inferredType_, " must have a '[Symbol.iterator]()' method that returns an iterator."},
            id_->Start());
    }

    if (inferredType_->IsUnionType()) {
        for (auto *it : inferredType_->AsUnionType()->ConstituentTypes()) {
            if (!it->IsArrayType() && (!it->IsObjectType() || !it->AsObjectType()->IsTupleType())) {
                checker_->ThrowTypeError(
                    {"Type ", inferredType_, " must have a '[Symbol.iterator]()' method that returns an iterator."},
                    id_->Start());
            }
        }
    }
}

Type *ArrayDestructuringContext::GetTypeFromTupleByIndex(TupleType *tuple)
{
    util::StringView memberIndex = util::Helpers::ToStringView(checker_->Allocator(), index_);
    varbinder::Variable *memberVar = tuple->GetProperty(memberIndex, false);

    if (memberVar == nullptr) {
        return nullptr;
    }

    return memberVar->TsType();
}

Type *ArrayDestructuringContext::NextInferredType([[maybe_unused]] const util::StringView &searchName, bool throwError)
{
    if (inferredType_->IsArrayType()) {
        return inferredType_->AsArrayType()->ElementType();
    }

    if (inferredType_->IsObjectType()) {
        ES2PANDA_ASSERT(inferredType_->AsObjectType()->IsTupleType());
        Type *returnType = GetTypeFromTupleByIndex(inferredType_->AsObjectType()->AsTupleType());

        if (returnType == nullptr && throwError) {
            if (!validateTypeAnnotation_ && checker_->HasStatus(CheckerStatus::IN_PARAMETER)) {
                return returnType;
            }

            checker_->ThrowTypeError({"Tuple type ", inferredType_, " of length ",
                                      inferredType_->AsObjectType()->AsTupleType()->FixedLength(),
                                      " has no element at index ", index_, "."},
                                     id_->Start());
        }

        return returnType;
    }

    ES2PANDA_ASSERT(inferredType_->IsUnionType());

    ArenaVector<Type *> unionTypes(checker_->Allocator()->Adapter());

    for (auto *type : inferredType_->AsUnionType()->ConstituentTypes()) {
        if (type->IsArrayType()) {
            unionTypes.push_back(type->AsArrayType()->ElementType());
            continue;
        }

        ES2PANDA_ASSERT(type->IsObjectType() && type->AsObjectType()->IsTupleType());
        Type *elementType = GetTypeFromTupleByIndex(type->AsObjectType()->AsTupleType());

        if (elementType == nullptr) {
            continue;
        }

        unionTypes.push_back(elementType);
    }

    if (unionTypes.empty()) {
        if (throwError) {
            checker_->ThrowTypeError({"Property ", index_, " does not exist on type ", inferredType_, "."},
                                     id_->Start());
        }

        return nullptr;
    }

    return checker_->CreateUnionType(std::move(unionTypes));
}

Type *ArrayDestructuringContext::CreateArrayTypeForRest(UnionType *inferredType)
{
    ArenaVector<Type *> unionTypes(checker_->Allocator()->Adapter());
    uint32_t savedIdx = index_;

    for (auto *it : inferredType->ConstituentTypes()) {
        if (it->IsArrayType()) {
            unionTypes.push_back(it->AsArrayType()->ElementType());
            continue;
        }

        ES2PANDA_ASSERT(it->IsObjectType() && it->AsObjectType()->IsTupleType());
        Type *tupleElementType = GetTypeFromTupleByIndex(it->AsObjectType()->AsTupleType());

        while (tupleElementType != nullptr) {
            unionTypes.push_back(tupleElementType);
            index_++;
            tupleElementType = GetTypeFromTupleByIndex(it->AsObjectType()->AsTupleType());
        }

        index_ = savedIdx;
    }

    Type *restArrayElementType = checker_->CreateUnionType(std::move(unionTypes));
    return checker_->Allocator()->New<ArrayType>(restArrayElementType);
}

Type *ArrayDestructuringContext::CreateTupleTypeForRest(TupleType *tuple)
{
    ObjectDescriptor *desc = checker_->Allocator()->New<ObjectDescriptor>(checker_->Allocator());
    ArenaVector<ElementFlags> elementFlags(checker_->Allocator()->Adapter());
    uint32_t savedIdx = index_;
    uint32_t iterIndex = 0;

    Type *tupleElementType = GetTypeFromTupleByIndex(tuple);
    ES2PANDA_ASSERT(desc != nullptr);
    while (tupleElementType != nullptr) {
        ElementFlags memberFlag = ElementFlags::REQUIRED;
        util::StringView memberIndex = util::Helpers::ToStringView(checker_->Allocator(), iterIndex);
        auto *memberVar = varbinder::Scope::CreateVar(checker_->Allocator(), memberIndex,
                                                      varbinder::VariableFlags::PROPERTY, nullptr);
        ES2PANDA_ASSERT(memberVar != nullptr);
        memberVar->SetTsType(tupleElementType);
        elementFlags.push_back(memberFlag);
        ES2PANDA_ASSERT(desc != nullptr);
        desc->properties.push_back(memberVar);

        index_++;
        iterIndex++;

        tupleElementType = GetTypeFromTupleByIndex(tuple);
    }

    index_ = savedIdx;
    const checker::TupleTypeInfo tupleTypeInfo = {ElementFlags::REQUIRED, iterIndex, iterIndex, false};
    return checker_->CreateTupleType(desc, std::move(elementFlags), tupleTypeInfo);
}

Type *ArrayDestructuringContext::GetRestType([[maybe_unused]] const lexer::SourcePosition &loc)
{
    if (inferredType_->IsArrayType()) {
        return inferredType_;
    }

    if (inferredType_->IsObjectType() && inferredType_->AsObjectType()->IsTupleType()) {
        return CreateTupleTypeForRest(inferredType_->AsObjectType()->AsTupleType());
    }

    ES2PANDA_ASSERT(inferredType_->IsUnionType());
    bool createArrayType = false;

    for (auto *it : inferredType_->AsUnionType()->ConstituentTypes()) {
        if (it->IsArrayType()) {
            createArrayType = true;
            break;
        }
    }

    if (createArrayType) {
        return CreateArrayTypeForRest(inferredType_->AsUnionType());
    }

    ArenaVector<Type *> tupleUnion(checker_->Allocator()->Adapter());

    for (auto *it : inferredType_->AsUnionType()->ConstituentTypes()) {
        ES2PANDA_ASSERT(it->IsObjectType() && it->AsObjectType()->IsTupleType());
        Type *newTuple = CreateTupleTypeForRest(it->AsObjectType()->AsTupleType());
        tupleUnion.push_back(newTuple);
    }

    return checker_->CreateUnionType(std::move(tupleUnion));
}

void ArrayDestructuringContext::HandleRest(ir::SpreadElement *rest)
{
    Type *inferredRestType = GetRestType(rest->Start());

    if (rest->Argument()->IsIdentifier()) {
        if (inAssignment_) {
            HandleDestructuringAssignment(rest->Argument()->AsIdentifier(), inferredRestType, nullptr);
            return;
        }

        SetInferredTypeForVariable(rest->Argument()->AsIdentifier()->Variable(), inferredRestType, rest->Start());
        return;
    }

    if (rest->Argument()->IsArrayPattern()) {
        ArrayDestructuringContext nextContext = ArrayDestructuringContext(
            {checker_, rest->Argument(), inAssignment_, convertTupleToArray_, nullptr, nullptr});
        nextContext.SetInferredType(inferredRestType);
        nextContext.Start();
        return;
    }

    ES2PANDA_ASSERT(rest->Argument()->IsObjectPattern());
    ObjectDestructuringContext nextContext =
        ObjectDestructuringContext({checker_, rest->Argument(), inAssignment_, convertTupleToArray_, nullptr, nullptr});
    nextContext.SetInferredType(inferredRestType);
    nextContext.Start();
}

Type *ArrayDestructuringContext::ConvertTupleTypeToArrayTypeIfNecessary(ir::AstNode *node, Type *type)
{
    if (!convertTupleToArray_) {
        return type;
    }

    if (type == nullptr) {
        return type;
    }

    if (node->IsArrayPattern() ||
        (node->IsAssignmentPattern() && node->AsAssignmentPattern()->Left()->IsArrayPattern())) {
        return type;
    }

    if (type->IsObjectType() && type->AsObjectType()->IsTupleType()) {
        return type->AsObjectType()->AsTupleType()->ConvertToArrayType(checker_);
    }

    return type;
}

static void SetParameterType(ir::AstNode *parent, Type *type)
{
    parent->Iterate([type](ir::AstNode *childNode) -> void {
        if (childNode->IsIdentifier() && childNode->AsIdentifier()->Variable() != nullptr) {
            childNode->AsIdentifier()->Variable()->SetTsType(type);
            return;
        }

        SetParameterType(childNode, type);
    });
}

void ArrayDestructuringContext::SetRemainingParameterTypes()
{
    do {
        auto *it = id_->AsArrayPattern()->Elements()[index_];
        ES2PANDA_ASSERT(it);
        SetParameterType(it, checker_->GlobalAnyType());
    } while (++index_ != id_->AsArrayPattern()->Elements().size());
}

void ArrayDestructuringContext::HandleElement(ir::Expression *element, Type *nextInferredType)
{
    switch (element->Type()) {
        case ir::AstNodeType::IDENTIFIER: {
            if (inAssignment_) {
                HandleDestructuringAssignment(element->AsIdentifier(), nextInferredType, nullptr);
                break;
            }

            SetInferredTypeForVariable(element->AsIdentifier()->Variable(), nextInferredType, element->Start());
            break;
        }
        case ir::AstNodeType::ARRAY_PATTERN: {
            ArrayDestructuringContext nextContext =
                ArrayDestructuringContext({checker_, element, inAssignment_, convertTupleToArray_, nullptr, nullptr});
            nextContext.SetInferredType(nextInferredType);
            nextContext.Start();
            break;
        }
        case ir::AstNodeType::OBJECT_PATTERN: {
            ObjectDestructuringContext nextContext =
                ObjectDestructuringContext({checker_, element, inAssignment_, convertTupleToArray_, nullptr, nullptr});
            nextContext.SetInferredType(nextInferredType);
            nextContext.Start();
            break;
        }
        case ir::AstNodeType::ASSIGNMENT_PATTERN: {
            HandleAssignmentPattern(element->AsAssignmentPattern(), nextInferredType, false);
            break;
        }
        case ir::AstNodeType::OMITTED_EXPRESSION: {
            break;
        }
        default: {
            ES2PANDA_UNREACHABLE();
        }
    }
}

void ArrayDestructuringContext::Start()
{
    ES2PANDA_ASSERT(id_->IsArrayPattern());

    ValidateInferredType();

    util::StringView name = util::Helpers::ToStringView(checker_->Allocator(), 0);

    for (auto *it : id_->AsArrayPattern()->Elements()) {
        if (it->IsRestElement()) {
            HandleRest(it->AsRestElement());
            break;
        }

        Type *nextInferredType =
            ConvertTupleTypeToArrayTypeIfNecessary(it, NextInferredType(name, !it->IsAssignmentPattern()));

        if (nextInferredType == nullptr && checker_->HasStatus(CheckerStatus::IN_PARAMETER)) {
            SetRemainingParameterTypes();
            return;
        }

        if (convertTupleToArray_ && nextInferredType != nullptr && inferredType_->IsObjectType()) {
            ES2PANDA_ASSERT(inferredType_->AsObjectType()->IsTupleType());

            varbinder::Variable *currentTupleElement = inferredType_->AsObjectType()->Properties()[index_];

            if (currentTupleElement != nullptr) {
                currentTupleElement->SetTsType(nextInferredType);
            }
        }

        HandleElement(it, nextInferredType);
        index_++;
    }
}

void ObjectDestructuringContext::ValidateInferredType()
{
    ES2PANDA_ASSERT(inferredType_ != nullptr);
    // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
    if (!inferredType_->IsObjectType()) {
        return;
    }

    ValidateObjectLiteralType(inferredType_->AsObjectType(), id_->AsObjectPattern());
}

void ObjectDestructuringContext::HandleRest(ir::SpreadElement *rest)
{
    Type *inferredRestType = GetRestType(rest->Start());
    ES2PANDA_ASSERT(rest->Argument()->IsIdentifier());

    if (inAssignment_) {
        HandleDestructuringAssignment(rest->Argument()->AsIdentifier(), inferredRestType, nullptr);
        return;
    }

    SetInferredTypeForVariable(rest->Argument()->AsIdentifier()->Variable(), inferredRestType, rest->Start());
}

Type *ObjectDestructuringContext::CreateObjectTypeForRest(ObjectType *objType)
{
    ObjectDescriptor *desc = checker_->Allocator()->New<ObjectDescriptor>(checker_->Allocator());

    for (auto *it : objType->AsObjectType()->Properties()) {
        if (!it->HasFlag(varbinder::VariableFlags::INFERRED_IN_PATTERN)) {
            auto *memberVar =
                varbinder::Scope::CreateVar(checker_->Allocator(), it->Name(), varbinder::VariableFlags::NONE, nullptr);
            ES2PANDA_ASSERT(memberVar != nullptr);
            memberVar->SetTsType(it->TsType());
            memberVar->AddFlag(it->Flags());
            ES2PANDA_ASSERT(desc != nullptr);
            desc->properties.push_back(memberVar);
        }
    }

    Type *returnType = checker_->Allocator()->New<ObjectLiteralType>(desc);
    ES2PANDA_ASSERT(returnType != nullptr);
    returnType->AsObjectType()->AddObjectFlag(ObjectFlags::RESOLVED_MEMBERS);
    return returnType;
}

Type *ObjectDestructuringContext::GetRestType([[maybe_unused]] const lexer::SourcePosition &loc)
{
    if (inferredType_->IsUnionType()) {
        ArenaVector<Type *> unionTypes(checker_->Allocator()->Adapter());

        for (auto *it : inferredType_->AsUnionType()->ConstituentTypes()) {
            if (it->IsObjectType()) {
                unionTypes.push_back(CreateObjectTypeForRest(it->AsObjectType()));
                continue;
            }

            checker_->ThrowTypeError("Rest types may only be created from object types.", loc);
        }

        return checker_->CreateUnionType(std::move(unionTypes));
    }

    if (inferredType_->IsObjectType()) {
        return CreateObjectTypeForRest(inferredType_->AsObjectType());
    }

    checker_->ThrowTypeError("Rest types may only be created from object types.", loc);
}

Type *ObjectDestructuringContext::ConvertTupleTypeToArrayTypeIfNecessary(ir::AstNode *node, Type *type)
{
    if (!convertTupleToArray_) {
        return type;
    }

    if (type == nullptr) {
        return type;
    }

    ES2PANDA_ASSERT(node->IsProperty());

    ir::Property *property = node->AsProperty();

    if (property->Value()->IsArrayPattern()) {
        return type;
    }

    if (property->Value()->IsAssignmentPattern() &&
        property->Value()->AsAssignmentPattern()->Left()->IsArrayPattern()) {
        return type;
    }

    if (type->IsObjectType() && type->AsObjectType()->IsTupleType()) {
        return type->AsObjectType()->AsTupleType()->ConvertToArrayType(checker_);
    }

    return type;
}

Type *ObjectDestructuringContext::NextInferredType([[maybe_unused]] const util::StringView &searchName, bool throwError)
{
    varbinder::Variable *prop = checker_->GetPropertyOfType(inferredType_, searchName, !throwError,
                                                            varbinder::VariableFlags::INFERRED_IN_PATTERN);

    if (prop != nullptr) {
        prop->AddFlag(varbinder::VariableFlags::INFERRED_IN_PATTERN);
        return prop->TsType();
    }

    if (inferredType_->IsObjectType()) {
        checker::ObjectType *objType = inferredType_->AsObjectType();

        if (objType->StringIndexInfo() != nullptr) {
            return objType->StringIndexInfo()->GetType();
        }
    }

    if (throwError) {
        checker_->ThrowTypeError({"Property ", searchName, " does not exist on type ", inferredType_, "."},
                                 id_->Start());
    }

    return nullptr;
}

void ObjectDestructuringContext::StartPropertyHelper(ir::Expression *it)
{
    ir::Property *property = it->AsProperty();

    Type *nextInferredType = ConvertTupleTypeToArrayTypeIfNecessary(
        it->AsProperty(), NextInferredType(property->Key()->AsIdentifier()->Name(),
                                           (!property->Value()->IsAssignmentPattern() || validateTypeAnnotation_)));

    if (property->Value()->IsIdentifier()) {
        if (inAssignment_) {
            HandleDestructuringAssignment(property->Value()->AsIdentifier(), nextInferredType, nullptr);
            return;
        }

        SetInferredTypeForVariable(property->Value()->AsIdentifier()->Variable(), nextInferredType, it->Start());
        return;
    }

    if (property->Value()->IsArrayPattern()) {
        ArrayDestructuringContext nextContext = ArrayDestructuringContext(
            {checker_, property->Value()->AsArrayPattern(), inAssignment_, convertTupleToArray_, nullptr, nullptr});
        nextContext.SetInferredType(nextInferredType);
        nextContext.Start();
        return;
    }

    if (property->Value()->IsObjectPattern()) {
        ObjectDestructuringContext nextContext = ObjectDestructuringContext(
            {checker_, property->Value()->AsObjectPattern(), inAssignment_, convertTupleToArray_, nullptr, nullptr});
        nextContext.SetInferredType(nextInferredType);
        nextContext.Start();
        return;
    }

    ES2PANDA_ASSERT(property->Value()->IsAssignmentPattern());
    HandleAssignmentPattern(property->Value()->AsAssignmentPattern(), nextInferredType, true);
}

void ObjectDestructuringContext::Start()
{
    ES2PANDA_ASSERT(id_->IsObjectPattern());

    if (!id_->AsObjectPattern()->Properties().back()->IsRestElement() && validateObjectPatternInitializer_) {
        ValidateInferredType();
    }

    for (auto *it : id_->AsObjectPattern()->Properties()) {
        const auto itType = it->Type();
        if (itType == ir::AstNodeType::PROPERTY) {
            ir::Property *property = it->AsProperty();
            if (property->IsComputed()) {
                // NOTE: aszilagyi.
                return;
            }
            StartPropertyHelper(it);
        } else if (itType == ir::AstNodeType::REST_ELEMENT) {
            HandleRest(it->AsRestElement());
        } else {
            ES2PANDA_UNREACHABLE();
        }
    }
}
}  // namespace ark::es2panda::checker