/**
 * 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.
 */

#ifndef ES2PANDA_IR_EXPRESSION_MEMBER_EXPRESSION_H
#define ES2PANDA_IR_EXPRESSION_MEMBER_EXPRESSION_H

#include "checker/checkerContext.h"
#include "checker/types/ets/etsObjectType.h"
#include "ir/expression.h"
#include "checker/resolveResult.h"

namespace ark::es2panda::compiler {
class JSCompiler;
class ETSCompiler;
}  // namespace ark::es2panda::compiler

namespace ark::es2panda::checker {
class ETSObjectType;
class ETSAnalyzer;
}  // namespace ark::es2panda::checker

namespace ark::es2panda::ir {

// NOLINTBEGIN(modernize-avoid-c-arrays)
inline constexpr char const PREDEFINED_METHOD[] = "The special predefined method '";
// NOLINTEND(modernize-avoid-c-arrays)

using ENUMBITOPS_OPERATORS;

enum class MemberExpressionKind : uint8_t {
    NONE = 0,
    ELEMENT_ACCESS = 1U << 0U,
    PROPERTY_ACCESS = 1U << 1U,
    GETTER = 1U << 2U,
    SETTER = 1U << 3U,
    EXTENSION_ACCESSOR = 1U << 4U,
};

}  // namespace ark::es2panda::ir

template <>
struct enumbitops::IsAllowedType<ark::es2panda::ir::MemberExpressionKind> : std::true_type {
};

namespace ark::es2panda::ir {

class MemberExpression : public MaybeOptionalExpression {
    friend class checker::ETSAnalyzer;

private:
    struct Tag {};

public:
    MemberExpression() = delete;
    ~MemberExpression() override = default;

    MemberExpression &operator=(const MemberExpression &) = delete;
    NO_MOVE_SEMANTIC(MemberExpression);

    using MemberAccessor = std::variant<checker::Signature *, varbinder::LocalVariable *>;
    using ComponentTypeMemberAccessors = ArenaVector<std::pair<checker::Type *, MemberAccessor>>;

    explicit MemberExpression(Expression *object, Expression *property, MemberExpressionKind kind, bool computed,
                              bool optional)
        : MaybeOptionalExpression(AstNodeType::MEMBER_EXPRESSION, optional),
          object_(object),
          property_(property),
          kind_(kind),
          computed_(computed)
    {
    }

    explicit MemberExpression(Tag tag, MemberExpression const &other, ArenaAllocator *allocator);

    // NOTE (csabahurton): these friend relationships can be removed once there are getters for private fields
    friend class compiler::JSCompiler;
    friend class compiler::ETSCompiler;

    [[nodiscard]] Expression *Object() noexcept
    {
        return object_;
    }

    [[nodiscard]] const Expression *Object() const noexcept
    {
        return object_;
    }

    void SetObject(Expression *object) noexcept
    {
        object_ = object;
        object_->SetParent(this);
    }

    void SetProperty(Expression *prop) noexcept
    {
        property_ = prop;
        property_->SetParent(this);
    }

    [[nodiscard]] Expression *Property() noexcept
    {
        return property_;
    }

    [[nodiscard]] const Expression *Property() const noexcept
    {
        return property_;
    }

    [[nodiscard]] varbinder::LocalVariable *PropVar() noexcept
    {
        if (Kind() == MemberExpressionKind::ELEMENT_ACCESS) {
            return nullptr;
        }
        return Property()->Variable() != nullptr ? Property()->Variable()->AsLocalVariable() : nullptr;
    }

    [[nodiscard]] const varbinder::LocalVariable *PropVar() const noexcept
    {
        if (Kind() == MemberExpressionKind::ELEMENT_ACCESS) {
            return nullptr;
        }
        return Property()->Variable() != nullptr ? Property()->Variable()->AsLocalVariable() : nullptr;
    }

    [[nodiscard]] bool IsComputed() const noexcept
    {
        return computed_;
    }

    [[nodiscard]] MemberExpressionKind Kind() const noexcept
    {
        return kind_;
    }

    void AddMemberKind(MemberExpressionKind kind) noexcept
    {
        kind_ |= kind;
    }

    [[nodiscard]] bool HasMemberKind(MemberExpressionKind kind) const noexcept
    {
        return (kind_ & kind) != 0;
    }

    void RemoveMemberKind(MemberExpressionKind const kind) noexcept
    {
        kind_ &= ~kind;
    }

    [[nodiscard]] checker::ETSObjectType *ObjType() const noexcept
    {
        return objType_;
    }

    [[nodiscard]] checker::ETSFunctionType *ExtensionAccessorType() const
    {
        return extensionAccessorType_;
    }

    void SetExtensionAccessorType(checker::ETSFunctionType *eAccessorType)
    {
        ES2PANDA_ASSERT(HasMemberKind(ir::MemberExpressionKind::EXTENSION_ACCESSOR));
        extensionAccessorType_ = eAccessorType;
    }

    void SetPropVar(varbinder::LocalVariable *propVar) noexcept
    {
        ES2PANDA_ASSERT(Property());
        Property()->SetVariable(propVar);
    }

    void SetObjectType(checker::ETSObjectType *objType) noexcept
    {
        objType_ = objType;
    }

    [[nodiscard]] bool IsIgnoreBox() const noexcept
    {
        return ignoreBox_;
    }

    void SetIgnoreBox() noexcept
    {
        ignoreBox_ = true;
    }

    [[nodiscard]] checker::Type *UncheckedType() const noexcept
    {
        return uncheckedType_;
    }

    [[nodiscard]] bool IsPrivateReference() const noexcept;

    [[nodiscard]] MemberExpression *Clone(ArenaAllocator *allocator, AstNode *parent) override;
    std::optional<std::size_t> GetTupleIndexValue(const checker::ETSChecker *checker) const;
    checker::Type *GetTypeOfTupleElement(checker::ETSChecker *checker, checker::Type *baseType);

    void TransformChildren(const NodeTransformer &cb, std::string_view transformationName) override;
    void Iterate(const NodeTraverser &cb) const override;
    void Dump(ir::AstDumper *dumper) const override;
    void Dump(ir::SrcDumper *dumper) const override;
    void Compile(compiler::PandaGen *pg) const override;
    void Compile(compiler::ETSGen *etsg) const override;
    void CompileToReg(compiler::PandaGen *pg, compiler::VReg objReg) const;
    void CompileToRegs(compiler::PandaGen *pg, compiler::VReg object, compiler::VReg property) const;
    checker::Type *Check(checker::TSChecker *checker) override;
    checker::VerifiedType Check(checker::ETSChecker *checker) override;

    void AddComponentTypeMemberAccessor(checker::Type *t, MemberAccessor m);
    const ComponentTypeMemberAccessors &GetComponentTypeMemberAccessors() const;

    std::string ToString() const override;

    void Accept(ASTVisitorT *v) override
    {
        v->Accept(this);
    }

    void CleanUp() override
    {
        AstNode::CleanUp();
        uncheckedType_ = nullptr;
        objType_ = nullptr;
        extensionAccessorType_ = nullptr;
    }

protected:
    MemberExpression(MemberExpression const &other) : MaybeOptionalExpression(other)
    {
        kind_ = other.kind_;
        computed_ = other.computed_;
        ignoreBox_ = other.ignoreBox_;
        // Note! Probably, we need to do 'Instantiate(...)' but we haven't access to 'Relation()' here...
        uncheckedType_ = other.uncheckedType_;
        objType_ = other.objType_;
    }

private:
    std::pair<checker::Type *, varbinder::LocalVariable *> ResolveObjectMember(checker::ETSChecker *checker) const;
    bool CheckRequiredCallError(checker::ETSChecker *checker,
                                const std::vector<checker::ResolveResult *> &resolveRes) const;
    checker::Type *AdjustType(checker::ETSChecker *checker, checker::Type *type);
    checker::Type *SetAndAdjustType(checker::ETSChecker *checker, checker::ETSObjectType *objectType);
    checker::Type *CheckComputed(checker::ETSChecker *checker, checker::Type *baseType);
    checker::Type *CheckUnionMember(checker::ETSChecker *checker, checker::Type *baseType);
    checker::Type *TraverseUnionMember(checker::ETSChecker *checker, checker::ETSUnionType *unionType);

    bool CheckArrayIndexValue(checker::ETSChecker *checker) const;
    checker::Type *CheckIndexAccessMethod(checker::ETSChecker *checker);
    checker::Type *ResolveReturnTypeFromSignature(checker::ETSChecker *checker, bool isSetter,
                                                  ArenaVector<ir::Expression *> &arguments,
                                                  ArenaVector<checker::Signature *> &signatures,
                                                  std::string_view const methodName);

    void LoadRhs(compiler::PandaGen *pg) const;
    void CollectUnionSignatures(checker::ETSChecker *checker, checker::Type *memberType, checker::Type *const type,
                                checker::Type **commonPropType, varbinder::LocalVariable *prop);

    EPtr<Expression> object_ = nullptr;
    EPtr<Expression> property_ = nullptr;
    MemberExpressionKind kind_;
    bool computed_;
    bool ignoreBox_ {false};
    EPtr<checker::Type> uncheckedType_ {};
    EPtr<checker::ETSObjectType> objType_ {};
    EPtr<checker::ETSFunctionType> extensionAccessorType_ {};
    std::optional<ComponentTypeMemberAccessors> componentTypeMemberAccessors_ {};
};
}  // namespace ark::es2panda::ir

#endif