/**
 * 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 "objectExpression.h"

#include <compiler/base/literals.h>
#include <compiler/core/pandagen.h>
#include <ir/astDump.h>
#include <ir/base/classDefinition.h>
#include <ir/base/property.h>
#include <ir/base/scriptFunction.h>
#include <ir/base/spreadElement.h>
#include <ir/expressions/arrayExpression.h>
#include <ir/expressions/arrowFunctionExpression.h>
#include <ir/expressions/assignmentExpression.h>
#include <ir/expressions/classExpression.h>
#include <ir/expressions/functionExpression.h>
#include <ir/expressions/literals/nullLiteral.h>
#include <ir/expressions/literals/numberLiteral.h>
#include <ir/expressions/literals/stringLiteral.h>
#include <ir/expressions/literals/taggedLiteral.h>
#include <ir/expressions/unaryExpression.h>

namespace panda::es2panda::ir {

static bool IsAnonClassOrFuncExpr(const ir::Expression *expr)
{
    const ir::Identifier *identifier;
    switch (expr->Type()) {
        case ir::AstNodeType::FUNCTION_EXPRESSION: {
            identifier = expr->AsFunctionExpression()->Function()->Id();
            break;
        }
        case ir::AstNodeType::ARROW_FUNCTION_EXPRESSION: {
            identifier = expr->AsArrowFunctionExpression()->Function()->Id();
            break;
        }
        case ir::AstNodeType::CLASS_EXPRESSION: {
            identifier = expr->AsClassExpression()->Definition()->Ident();
            break;
        }
        default: {
            return false;
        }
    }
    return identifier == nullptr || identifier->Name().Empty();
}

static bool IsLegalNameFormat(const ir::Expression *expr)
{
    util::StringView name;
    if (expr->IsIdentifier()) {
        name = expr->AsIdentifier()->Name();
    } else if (expr->IsStringLiteral()) {
        name = expr->AsStringLiteral()->Str();
    } else if (expr->IsNumberLiteral()) {
        name = expr->AsNumberLiteral()->Str();
    } else {
        UNREACHABLE();
    }
    return name.Find(".") != std::string::npos && name.Find("\\") != std::string::npos;
}

ValidationInfo ObjectExpression::ValidateExpression()
{
    ValidationInfo info;
    bool foundProto = false;

    for (auto *it : properties_) {
        switch (it->Type()) {
            case AstNodeType::OBJECT_EXPRESSION:
            case AstNodeType::ARRAY_EXPRESSION: {
                return {"Unexpected token.", it->Start()};
            }
            case AstNodeType::SPREAD_ELEMENT: {
                info = it->AsSpreadElement()->ValidateExpression();
                break;
            }
            case AstNodeType::PROPERTY: {
                auto *prop = it->AsProperty();
                info = prop->ValidateExpression();

                if (prop->Kind() == PropertyKind::PROTO) {
                    if (foundProto) {
                        return {"Duplicate __proto__ fields are not allowed in object literals", prop->Key()->Start()};
                    }

                    foundProto = true;
                }

                break;
            }
            default: {
                break;
            }
        }

        if (info.Fail()) {
            break;
        }
    }

    return info;
}

bool ObjectExpression::ConvertibleToObjectPattern()
{
    bool restFound = false;
    bool convResult = true;

    for (auto *it : properties_) {
        switch (it->Type()) {
            case AstNodeType::ARRAY_EXPRESSION: {
                convResult = it->AsArrayExpression()->ConvertibleToArrayPattern();
                break;
            }
            case AstNodeType::SPREAD_ELEMENT: {
                if (!restFound && it == properties_.back() && !trailingComma_) {
                    convResult = it->AsSpreadElement()->ConvertibleToRest(isDeclaration_, false);
                } else {
                    convResult = false;
                }

                restFound = true;
                break;
            }
            case AstNodeType::OBJECT_EXPRESSION: {
                convResult = it->AsObjectExpression()->ConvertibleToObjectPattern();
                break;
            }
            case AstNodeType::ASSIGNMENT_EXPRESSION: {
                convResult = it->AsAssignmentExpression()->ConvertibleToAssignmentPattern();
                break;
            }
            case AstNodeType::META_PROPERTY_EXPRESSION:
            case AstNodeType::CHAIN_EXPRESSION:
            case AstNodeType::SEQUENCE_EXPRESSION: {
                convResult = false;
                break;
            }
            case AstNodeType::PROPERTY: {
                convResult = it->AsProperty()->ConventibleToPatternProperty();
                break;
            }
            default: {
                break;
            }
        }

        if (!convResult) {
            break;
        }
    }

    SetType(AstNodeType::OBJECT_PATTERN);
    return convResult;
}

void ObjectExpression::SetDeclaration()
{
    isDeclaration_ = true;
}

void ObjectExpression::SetOptional(bool optional)
{
    optional_ = optional;
}

void ObjectExpression::SetTsTypeAnnotation(Expression *typeAnnotation)
{
    typeAnnotation_ = typeAnnotation;
}

void ObjectExpression::Iterate(const NodeTraverser &cb) const
{
    for (auto *it : properties_) {
        cb(it);
    }

    if (typeAnnotation_) {
        cb(typeAnnotation_);
    }
}

void ObjectExpression::Dump(ir::AstDumper *dumper) const
{
    dumper->Add({{"type", (type_ == AstNodeType::OBJECT_EXPRESSION) ? "ObjectExpression" : "ObjectPattern"},
                 {"properties", properties_},
                 {"typeAnnotation", AstDumper::Optional(typeAnnotation_)},
                 {"optional", AstDumper::Optional(optional_)}});
}

void ObjectExpression::FillInLiteralBuffer(compiler::LiteralBuffer *buf,
                                           std::vector<std::vector<const Literal *>> &tempLiteralBuffer) const
{
    for (size_t i = 0 ; i < tempLiteralBuffer.size(); i++) {
        if (tempLiteralBuffer[i].size() == 0) {
            continue;
        }

        auto propBuf = tempLiteralBuffer[i];
        for (size_t j = 0; j < propBuf.size(); j++) {
            buf->Add(propBuf[j]);
        }
    }
}

void ObjectExpression::EmitCreateObjectWithBuffer(compiler::PandaGen *pg, compiler::LiteralBuffer *buf,
                                                  bool hasMethod) const
{
    if (buf->IsEmpty()) {
        pg->CreateEmptyObject(this);
        return;
    }

    int32_t bufIdx = pg->AddLiteralBuffer(buf);
    pg->CreateObjectWithBuffer(this, static_cast<uint32_t>(bufIdx));
}

static const Literal *CreateLiteral(compiler::PandaGen *pg, const ir::Property *prop, util::BitSet *compiled,
                                    size_t propIndex)
{
    if (util::Helpers::IsConstantExpr(prop->Value())) {
        compiled->Set(propIndex);
        return prop->Value()->AsLiteral();
    }

    if (prop->Kind() != ir::PropertyKind::INIT) {
        ASSERT(prop->IsAccessor());
        return pg->Allocator()->New<TaggedLiteral>(LiteralTag::ACCESSOR);
    }

    if (prop->IsMethod()) {
        const ir::ScriptFunction *method = prop->Value()->AsFunctionExpression()->Function();

        LiteralTag tag = LiteralTag::METHOD;

        if (method->IsGenerator()) {
            tag = LiteralTag::GENERATOR_METHOD;

            if (method->IsAsync()) {
                tag = LiteralTag::ASYNC_GENERATOR_METHOD;
            }
        }

        compiled->Set(propIndex);
        return pg->Allocator()->New<TaggedLiteral>(tag, method->Scope()->InternalName());
    }

    return pg->Allocator()->New<NullLiteral>();
}

void ObjectExpression::CompileStaticProperties(compiler::PandaGen *pg, util::BitSet *compiled) const
{
    bool hasMethod = false;
    auto *buf = pg->NewLiteralBuffer();
    std::vector<std::vector<const Literal *>> tempLiteralBuffer(properties_.size());
    std::unordered_map<util::StringView, size_t> propNameMap;
    std::unordered_map<util::StringView, size_t> getterIndxNameMap;
    std::unordered_map<util::StringView, size_t> setterIndxNameMap;

    for (size_t i = 0; i < properties_.size(); i++) {
        if (properties_[i]->IsSpreadElement()) {
            break;
        }

        const ir::Property *prop = properties_[i]->AsProperty();

        if (!util::Helpers::IsConstantPropertyKey(prop->Key(), prop->IsComputed()) ||
            prop->Kind() == ir::PropertyKind::PROTO) {
            break;
        }

        std::vector<const Literal *> propBuf;
        util::StringView name = util::Helpers::LiteralToPropName(pg->Allocator(), prop->Key());
        size_t propIndex = i;
        auto res = propNameMap.insert({name, propIndex});
        if (!res.second) {    // name is found in map
            propIndex = res.first->second;

            if (prop->Kind() != ir::PropertyKind::SET && getterIndxNameMap.find(name) != getterIndxNameMap.end()) {
                compiled->Set(getterIndxNameMap[name]);
            }

            if (prop->Kind() != ir::PropertyKind::GET && setterIndxNameMap.find(name) != setterIndxNameMap.end()) {
                compiled->Set(setterIndxNameMap[name]);
            }
        }

        if (prop->Kind() == ir::PropertyKind::GET) {
            getterIndxNameMap[name] = i;
        } else if (prop->Kind() == ir::PropertyKind::SET) {
            setterIndxNameMap[name] = i;
        }

        propBuf.push_back(pg->Allocator()->New<StringLiteral>(name));
        propBuf.push_back(CreateLiteral(pg, prop, compiled, i));

        if (prop->IsMethod()) {
            hasMethod = true;
            const ir::FunctionExpression *func = prop->Value()->AsFunctionExpression();
            size_t paramNum = func->Function()->FormalParamsLength();
            Literal *methodAffiliate = pg->Allocator()->New<TaggedLiteral>(LiteralTag::METHODAFFILIATE, paramNum);
            propBuf.push_back(methodAffiliate);
        }

        tempLiteralBuffer[propIndex] = propBuf;
    }

    FillInLiteralBuffer(buf, tempLiteralBuffer);
    EmitCreateObjectWithBuffer(pg, buf, hasMethod);
}

void ObjectExpression::CompilePropertyOfGetterOrSetter(compiler::PandaGen *pg, const ir::Property *prop,
    compiler::VReg objReg) const
{
    compiler::VReg key = pg->LoadPropertyKey(prop->Key(), prop->IsComputed());

    compiler::VReg undef = pg->AllocReg();
    pg->LoadConst(this, compiler::Constant::JS_UNDEFINED);
    pg->StoreAccumulator(this, undef);

    compiler::VReg getter = undef;
    compiler::VReg setter = undef;

    compiler::VReg accessor = pg->AllocReg();
    pg->LoadAccumulator(prop->Value(), objReg);
    prop->Value()->Compile(pg);
    pg->StoreAccumulator(prop->Value(), accessor);

    if (prop->Kind() == ir::PropertyKind::GET) {
        getter = accessor;
    } else {
        setter = accessor;
    }

    pg->DefineGetterSetterByValue(this, objReg, key, getter, setter, prop->IsComputed());
}

void ObjectExpression::CompilePropertyWithInit(compiler::PandaGen *pg, const ir::Property *prop,
    compiler::VReg objReg) const
{
    compiler::Operand key = pg->ToPropertyKey(prop->Key(), prop->IsComputed());
    const auto *value = prop->Value();

    bool nameSetting = false;
    if (prop->IsMethod()) {
        pg->LoadAccumulator(value, objReg);
        if (prop->IsComputed()) {
            nameSetting = true;
        }
    } else {
        if (prop->IsComputed()) {
            nameSetting = IsAnonClassOrFuncExpr(value);
        } else {
            nameSetting = IsAnonClassOrFuncExpr(value) && IsLegalNameFormat(prop->Key());
        }
    }

    // This is for disallowing breakpoint on property with negative number as initializer
    // TODO: remove setting invalid flag after puttting negative number into literal buffer
    bool shouldSetInvalidFlag = value->IsUnaryExpression() && value->AsUnaryExpression()->IsNegativeNumber()
        && !prop->IsComputed();
    if (shouldSetInvalidFlag) {
        pg->SetSourceLocationFlag(lexer::SourceLocationFlag::INVALID_SOURCE_LOCATION);
    }

    value->Compile(pg);
    if (!nameSetting && pg->Binder()->Program()->TargetApiVersion() > 10) {
        pg->DefineOwnProperty(this, objReg, key);
    } else {
        pg->StoreOwnProperty(this, objReg, key, nameSetting);
    }
    pg->SetSourceLocationFlag(lexer::SourceLocationFlag::VALID_SOURCE_LOCATION);
}

void ObjectExpression::CompileRemainingProperties(compiler::PandaGen *pg, const util::BitSet *compiled,
                                                  compiler::VReg objReg) const
{
    for (size_t i = 0; i < properties_.size(); i++) {
        if (compiled->Test(i)) {
            continue;
        }

        compiler::RegScope rs(pg);

        if (properties_[i]->IsSpreadElement()) {
            const ir::SpreadElement *spread = properties_[i]->AsSpreadElement();

            spread->Argument()->Compile(pg);
            // srcObj is now stored in acc
            pg->CopyDataProperties(spread, objReg);
            continue;
        }

        const ir::Property *prop = properties_[i]->AsProperty();

        switch (prop->Kind()) {
            case ir::PropertyKind::GET:
            case ir::PropertyKind::SET: {
                CompilePropertyOfGetterOrSetter(pg, prop, objReg);
                break;
            }
            case ir::PropertyKind::INIT: {
                CompilePropertyWithInit(pg, prop, objReg);
                break;
            }
            case ir::PropertyKind::PROTO: {
                prop->Value()->Compile(pg);
                compiler::VReg proto = pg->AllocReg();
                pg->StoreAccumulator(this, proto);

                pg->SetObjectWithProto(this, proto, objReg);
                break;
            }
            default: {
                UNREACHABLE();
            }
        }
    }

    pg->LoadAccumulator(this, objReg);
}

void ObjectExpression::Compile(compiler::PandaGen *pg) const
{
    if (properties_.empty()) {
        pg->CreateEmptyObject(this);
        return;
    }

    util::BitSet compiled(properties_.size());
    CompileStaticProperties(pg, &compiled);

    compiler::RegScope rs(pg);
    compiler::VReg objReg = pg->AllocReg();

    pg->StoreAccumulator(this, objReg);

    CompileRemainingProperties(pg, &compiled, objReg);
}

const util::StringView &GetPropertyName(const ir::Expression *key)
{
    if (key->IsIdentifier()) {
        return key->AsIdentifier()->Name();
    }

    if (key->IsStringLiteral()) {
        return key->AsStringLiteral()->Str();
    }

    ASSERT(key->IsNumberLiteral());
    return key->AsNumberLiteral()->Str();
}

binder::VariableFlags GetFlagsForProperty(const ir::Property *prop)
{
    if (!prop->IsMethod()) {
        return binder::VariableFlags::PROPERTY;
    }

    binder::VariableFlags propFlags = binder::VariableFlags::METHOD;

    if (prop->IsAccessor() && prop->Kind() == PropertyKind::GET) {
        propFlags |= binder::VariableFlags::READONLY;
    }

    return propFlags;
}

void ObjectExpression::UpdateSelf(const NodeUpdater &cb, [[maybe_unused]] binder::Binder *binder)
{
    for (auto iter = properties_.begin(); iter != properties_.end(); iter++) {
        *iter = std::get<ir::AstNode *>(cb(*iter))->AsExpression();
    }

    if (typeAnnotation_) {
        typeAnnotation_ = std::get<ir::AstNode *>(cb(typeAnnotation_))->AsExpression();
    }
}

}  // namespace panda::es2panda::ir