/**
 * Copyright (c) 2022-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_PARSER_TRANSFORMER_TRANSFORMER_H
#define ES2PANDA_PARSER_TRANSFORMER_TRANSFORMER_H

#include <macros.h>

#include "binder/binder.h"
#include "binder/scope.h"
#include "lexer/token/sourceLocation.h"
#include "ir/astNode.h"
#include "ir/base/classDefinition.h"
#include "ir/base/methodDefinition.h"
#include "parser/module/sourceTextModuleRecord.h"
#include "parser/parserFlags.h"
#include "parser/program/program.h"

namespace panda::es2panda::parser {

struct TsModuleInfo {
    util::StringView name;
    binder::Scope *scope;
};

struct TsEnumInfo {
    util::StringView name;
    binder::Scope *scope;
};

using PrivateElementMap = std::unordered_map<util::StringView, util::StringView>;
using ComputedPropertyMap = std::unordered_map<ir::Statement *, util::StringView>;

struct ClassInfo {
    util::StringView name;
    util::StringView aliasName;
    const ir::ClassDefinition *node;
    size_t propertyIndex;
    PrivateElementMap *bindNameMap;
    ComputedPropertyMap *computedPropertyMap;
};

struct MethodInfo {
    ir::Expression *key;
    util::StringView backupName; // For computed property.
    ir::MethodDefinitionKind kind;
    ir::ModifierFlags modifiers;
    bool isComputed;
};

class DuringClass {
public:
    explicit DuringClass(ArenaVector<ClassInfo> *classList, util::StringView name,
                         util::StringView aliasName, ir::ClassDefinition *node)
    {
        classList_ = classList;
        classList_->push_back({name, aliasName, node, 0, &bindNameMap_, &computedPropertyMap_});
    }

    ~DuringClass()
    {
        classList_->pop_back();
    }

private:
    PrivateElementMap bindNameMap_ {};
    ComputedPropertyMap computedPropertyMap_ {};
    ArenaVector<ClassInfo> *classList_ {nullptr};
};

class Transformer {
public:
    explicit Transformer(panda::ArenaAllocator *allocator)
        : program_(nullptr),
          tsModuleList_(allocator->Adapter()),
          tsEnumList_(allocator->Adapter()),
          classList_(allocator->Adapter())
    {
    }
    NO_COPY_SEMANTIC(Transformer);
    ~Transformer() = default;

    void Transform(Program *program);
    void CheckTransformedAstStructure(const Program *program) const;

private:
    static constexpr std::string_view PRIVATE_PROPERTY_SIGN = "#";
    static constexpr std::string_view NEW_VAR_PREFIX = "##";
    static constexpr std::string_view NEW_VAR_HEAD = "var_";
    static constexpr std::string_view INDEX_DIVISION = "_";
    static constexpr std::string_view CONSTRUCTOR_NAME = "undefined";
    static constexpr std::string_view CLASS_PROTOTYPE = "prototype";
    static constexpr std::string_view OBJECT_VAR_NAME = "Object";
    static constexpr std::string_view FUNC_NAME_OF_DEFINE_PROPERTY = "defineProperty";
    static constexpr std::string_view FUNC_NAME_OF_GET_OWN_PROPERTY_DESCRIPTOR = "getOwnPropertyDescriptor";
    static constexpr std::string_view AUTO_ACCESSOR_STORAGE_NAME = "auto_accessor_storage";

    void TransformFromTS();

    void AddVariableToNearestStatements(util::StringView name);
    void PushVariablesToNearestStatements(ir::BlockStatement *ast);

    ir::AstNode *VisitTSNodes(ir::AstNode *parent);
    ir::UpdateNodes VisitTSNode(ir::AstNode *childNode);
    ir::UpdateNodes VisitTsModuleDeclaration(ir::TSModuleDeclaration *childNode, bool isExport = false);
    std::vector<ir::AstNode *> VisitExportNamedVariable(ir::Statement *decl);
    ir::AstNode *VisitTsImportEqualsDeclaration(ir::TSImportEqualsDeclaration *node);
    ir::UpdateNodes VisitClassDeclaration(ir::ClassDeclaration *node);
    ir::UpdateNodes VisitClassExpression(ir::ClassExpression *node);
    void VisitTSParameterProperty(ir::ClassDefinition *node);
    void VisitAutoAccessorProperty(ir::ClassDefinition *node);
    ir::Expression *CopyClassKeyExpression(ir::Expression *orginalExpr);
    void ProcessAutoAccessorProperty(ir::ClassProperty *node, ir::ClassDefinition *classDefinition);
    ir::MethodDefinition* AddMethodToClass(ir::ClassDefinition *classDefinition,
                                           const MethodInfo &methodInfo,
                                           ArenaVector<ir::Expression *> &params,
                                           ArenaVector<ir::Statement *> &statements);
    /*
     * Only support for adding generated set&get method to class.
     */
    ir::MethodDefinition* AddGeneratedMethodToClass(ir::ClassDefinition *classDefinition,
                                                    const MethodInfo &methodInfo,
                                                    util::StringView propName);
    void AddGeneratedSetOrGetMethodToClass(ir::ClassDefinition *classDefinition,
                                           ir::ClassProperty *propertyNode,
                                           const MethodInfo &methodInfo);
    std::vector<ir::ExpressionStatement *> VisitInstanceProperty(ir::ClassDefinition *node);
    std::vector<ir::ExpressionStatement *> VisitStaticProperty(ir::ClassDefinition *node,
                                                               util::StringView name);
    void VisitPrivateElement(ir::ClassDefinition *node);
    void VisitComputedProperty(ir::ClassDefinition *node);
    util::StringView CreateClassAliasName(ir::ClassDeclaration *node);
    const ir::ClassDefinition *GetClassReference(util::StringView name) const;
    size_t GetInsertPosForConstructor(ir::ClassDefinition *node);
    void FindSuperCall(const ir::AstNode *parent, bool *hasSuperCall);
    void FindSuperCallInCtorChildNode(const ir::AstNode *childNode, bool *hasSuperCall);

    ir::VariableDeclaration *CreateVariableDeclarationWithIdentify(util::StringView name,
                                                                   VariableParsingFlags flags,
                                                                   ir::AstNode *node,
                                                                   bool isExport,
                                                                   ir::Expression *init = nullptr,
                                                                   bool needBinding = true);
    ir::CallExpression *CreateCallExpressionForTsModule(ir::TSModuleDeclaration *node,
                                                        util::StringView name,
                                                        bool isExport = false);
    ir::Expression *CreateTsModuleParam(util::StringView paramName, bool isExport);
    ir::ExpressionStatement *CreateTsModuleAssignment(util::StringView name);
    ir::Expression *CreateMemberExpressionFromQualified(ir::Expression *node);
    std::vector<ir::AstNode *> CreateClassDecorators(ir::ClassDeclaration *node,
                                                     const std::vector<ir::AstNode *> &variableDeclarations);
    std::vector<ir::AstNode *> CreateMethodDecorators(util::StringView className,
                                                      ir::MethodDefinition *node,
                                                      const std::vector<ir::AstNode *> &variableDeclarations,
                                                      bool isStatic);
    std::vector<ir::AstNode *> CreatePropertyDecorators(util::StringView className,
                                                        ir::ClassProperty *node,
                                                        const std::vector<ir::AstNode *> &variableDeclarations,
                                                        bool isStatic);
    ir::CallExpression *CreateGetOwnPropertyDescriptorCall(ir::Expression *target, ir::Expression *key,
                                                           const ir::AstNode *originalNode);
    ir::CallExpression *CreateDefinePropertyCall(ir::Expression *target, ir::Expression *key, ir::Expression *value,
                                                 const ir::AstNode *originalNode);
    ir::ClassStaticBlock *CreateClassStaticBlock(ir::ClassDeclaration *node, bool hasPrivateIdentifer);
    bool HasPrivateIdentifierInDecorators(const ir::ClassDefinition *classDefinition);
    void FindPrivateIdentifierInDecorator(const ir::AstNode *parent, bool *hasprivateIdentifier);
    void FindPrivateIdentifierInChildNode(const ir::AstNode *childNode, bool *hasprivateIdentifier);
    std::vector<ir::AstNode *> CreateVariableDeclarationForDecorators(ir::AstNode *node);
    std::vector<ir::AstNode *> CreateParamDecorators(util::StringView className,
                                                     ir::MethodDefinition *node,
                                                     const std::vector<ir::AstNode *> &variableDeclarations,
                                                     bool isConstructor,
                                                     bool isStatic);
    ir::MemberExpression *CreateClassPrototype(util::StringView className, const ir::AstNode *originalNode);
    ir::Expression *CreateDecoratorTarget(util::StringView className, bool isStatic,
                                          const ir::AstNode *originalNode = nullptr);
    ir::Identifier *CreateReferenceIdentifier(util::StringView name, ir::AstNode *originalNode = nullptr);
    util::StringView CreatePrivateElementBindName(util::StringView name);
    util::StringView CreateNewVariable(bool needAddToStatements = true);
    util::StringView CreateNewVariableName() const;
    util::StringView CreateUniqueName(const std::string &head, size_t *index = nullptr) const;

    util::StringView GetNameFromModuleDeclaration(ir::TSModuleDeclaration *node) const;
    util::StringView GetParamName(ir::AstNode *node, util::StringView name) const;
    ir::Expression *GetClassMemberName(ir::Expression *key, bool isComputed,
                                       ir::Statement *node, bool inDecorator = true);
    binder::Scope *FindExportVariableInTsModuleScope(util::StringView name) const;
    binder::Variable *FindTSModuleVariable(const ir::Expression *node, const binder::Scope *scope, bool *isType) const;
    util::StringView FindPrivateElementBindName(util::StringView name);
    void AddExportLocalEntryItem(util::StringView name, const ir::Identifier *identifier);
    void RemoveDefaultLocalExportEntry();
    bool IsInstantiatedImportEquals(const ir::TSImportEqualsDeclaration *node, binder::Scope *scope) const;
    void SetOriginalNode(ir::UpdateNodes res, ir::AstNode *originalNode) const;

    ir::UpdateNodes VisitTsEnumDeclaration(ir::TSEnumDeclaration *node, bool isExport = false);
    ir::AstNode *CreateVariableDeclarationForTSEnumOrTSModule(util::StringView name, ir::AstNode *node, bool isExport);
    util::StringView GetNameFromTsEnumDeclaration(const ir::TSEnumDeclaration *node) const;
    ir::CallExpression *CreateCallExpressionForTsEnum(ir::TSEnumDeclaration *node, util::StringView name,
                                                      bool isExport);
    void SetRangeRecursively(ir::AstNode *node, const ir::AstNode *originalNode, bool replaceAll = false);
    ir::ExpressionStatement *CreateTsEnumMember(ir::TSEnumMember *node, ir::TSEnumMember *preNode,
                                                util::StringView enumLiteralName);
    ir::ExpressionStatement *CreateTsEnumMemberWithStringInit(ir::TSEnumMember *node,
                                                              util::StringView enumLiteralName,
                                                              util::StringView enumMemberName);
    ir::ExpressionStatement *CreateTsEnumMemberWithNumberInit(ir::TSEnumMember *node,
                                                              util::StringView enumLiteralName,
                                                              util::StringView enumMemberName);
    ir::ExpressionStatement *CreateTsEnumMemberWithoutInit(ir::TSEnumMember *node,
                                                           ir::TSEnumMember *preNode,
                                                           util::StringView enumLiteralName,
                                                           util::StringView enumMemberName);
    ArenaVector<ir::Expression *> CreateCallExpressionArguments(util::StringView name, bool isExport);
    bool IsStringInitForEnumMember(const ir::Expression *expr, binder::Scope *scope) const;
    bool IsStringForMemberExpression(const ir::MemberExpression *memberExpr, binder::Scope *scope) const;
    bool IsInstantiatedNamespaceVariable(binder::Variable *var) const;
    ArenaVector<binder::Variable *> FindFrontIdentifierTSVariables(const ir::Identifier *ident,
                                                                   binder::Scope *scope) const;
    void FindLocalTSVariables(binder::Scope *scope, const util::StringView name,
                              const std::vector<binder::TSBindingType> &types,
                              ArenaVector<binder::Variable *> &findRes) const;
    void FindExportTSVariables(binder::Scope *scope, const util::StringView name,
                               const std::vector<binder::TSBindingType> &types,
                               ArenaVector<binder::Variable *> &findRes) const;
    bool VerifyMemberExpressionDeque(binder::Variable *currVar, ArenaDeque<const ir::Expression *> members) const;
    util::StringView GetNameForMemberExpressionItem(const ir::Expression *node) const;
    util::StringView GetNameFromEnumMember(const ir::TSEnumMember *node) const;
    binder::Scope *FindEnumMemberScope(const util::StringView name) const;
    ir::MemberExpression *CreateMemberExpressionFromIdentifier(binder::Scope *scope, ir::Identifier *node);

    void CheckTransformedAstNodes(const ir::AstNode *parent, bool *passed) const;
    void CheckTransformedAstNode(const ir::AstNode *parent, ir::AstNode *childNode, bool *passed) const;

    void ResetParentScope(ir::UpdateNodes res, binder::Scope *parentScope) const;
    void ResetParentScopeForAstNodes(const ir::AstNode *parent, binder::Scope *parentScope) const;
    void ResetParentScopeForAstNode(ir::AstNode *childNode, binder::Scope *parentScope) const;
    void ThrowStackOverflow(const lexer::SourcePosition &pos);

    template <typename T>
    ir::UpdateNodes VisitExportClassDeclaration(T *node);

    template <binder::TSBindingType type>
    binder::Variable *FindTSVariable(const binder::Scope *scope, const util::StringView &name) const;

    bool IsValueReference(ir::Identifier *node);

    bool IsTsModule() const
    {
        return (tsModuleList_.size() != 0);
    }

    bool IsTsEnum() const
    {
        return (tsEnumList_.size() != 0);
    }

    bool InClass() const
    {
        return (classList_.size() != 0);
    }

    template <typename T, typename... Args>
    T *AllocNode(Args &&... args)
    {
        auto ret = program_->Allocator()->New<T>(std::forward<Args>(args)...);
        if (ret == nullptr) {
            throw Error(ErrorType::GENERIC, "Unsuccessful allocation during parsing");
        }
        return ret;
    }

    ArenaAllocator *Allocator() const
    {
        return program_->Allocator();
    }

    binder::Binder *Binder() const
    {
        return program_->Binder();
    }

    binder::Scope *Scope() const
    {
        return Binder()->GetScope();
    }

    util::StringView GetCurrentTSModuleName() const
    {
        return tsModuleList_.back().name;
    }

    util::StringView FindTSModuleNameByScope(binder::Scope *scope) const
    {
        for (auto it : tsModuleList_) {
            if (it.scope == scope) {
                return it.name;
            }
        }
        UNREACHABLE();
    }

    util::StringView FindTSEnumNameByScope(binder::Scope *scope) const
    {
        for (auto it : tsEnumList_) {
            if (it.scope == scope) {
                return it.name;
            }
        }
        UNREACHABLE();
    }

    ScriptExtension Extension() const
    {
        return program_->Extension();
    }

    SourceTextModuleRecord *GetSourceTextModuleRecord()
    {
        return program_->ModuleRecord();
    }

    util::StringView RecordName() const
    {
        return program_->RecordName();
    }

    size_t GetCurrentClassInfoPropertyIndex() const
    {
        return classList_.back().propertyIndex;
    }

    void SetCurrentClassInfoPropertyIndex(size_t newIndex)
    {
        classList_.back().propertyIndex = newIndex;
    }

    void AddPrivateElementBinding(util::StringView name, util::StringView bindName)
    {
        classList_.back().bindNameMap->insert({name, bindName});
    }

    void AddComputedPropertyBinding(ir::Statement *property, util::StringView name)
    {
        classList_.back().computedPropertyMap->insert({property, name});
    }

    util::StringView GetComputedPropertyBinding(ir::Statement *property)
    {
        auto classInfo = classList_.back();
        auto res = classInfo.computedPropertyMap->find(property);
        ASSERT(res != classInfo.computedPropertyMap->end());
        return res->second;
    }

    util::StringView GetClassAliasName() const
    {
        return classList_.back().aliasName;
    }

    util::StringView GetClassAliasName(util::StringView originName, const ir::ClassDefinition *node) const
    {
        if (node == nullptr) {
            return originName;
        }

        for (int i = static_cast<int>(classList_.size() - 1); i >= 0; i--) {
            if (classList_[i].node == node) {
                return classList_[i].aliasName;
            }
        }
        return originName;
    }

    Program *program_;
    ArenaVector<TsModuleInfo> tsModuleList_;
    ArenaVector<TsEnumInfo> tsEnumList_;
    ArenaVector<ClassInfo> classList_;
    std::unordered_map<util::StringView, binder::Scope *> tempVarDeclStatements_ {};
};

}  // namespace panda::es2panda::parser

#endif