/**
 * 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_COMPILER_SCOPES_VARIABLE_H
#define ES2PANDA_COMPILER_SCOPES_VARIABLE_H

#include <binder/enumMemberResult.h>
#include <binder/variableFlags.h>
#include <ir/irnode.h>
#include <macros.h>
#include <util/patchFix.h>
#include <util/ustring.h>

#include <limits>

namespace panda::es2panda::binder {

class Decl;
class Scope;
class VariableScope;
class ExportBindings;
class Variable;

using VariableMap = ArenaMap<util::StringView, Variable *>;

#define DECLARE_CLASSES(type, className) class className;
VARIABLE_TYPES(DECLARE_CLASSES)
#undef DECLARE_CLASSES

class Variable {
public:
    virtual ~Variable() = default;
    NO_COPY_SEMANTIC(Variable);
    NO_MOVE_SEMANTIC(Variable);

    virtual VariableType Type() const = 0;

#define DECLARE_CHECKS_CASTS(variableType, className)     \
    bool Is##className() const                            \
    {                                                     \
        return Type() == VariableType::variableType;      \
    }                                                     \
    className *As##className()                            \
    {                                                     \
        ASSERT(Is##className());                          \
        return reinterpret_cast<className *>(this);       \
    }                                                     \
    const className *As##className() const                \
    {                                                     \
        ASSERT(Is##className());                          \
        return reinterpret_cast<const className *>(this); \
    }
    VARIABLE_TYPES(DECLARE_CHECKS_CASTS)
#undef DECLARE_CHECKS_CASTS

    Decl *Declaration() const
    {
        return decl_;
    }

    VariableFlags Flags() const
    {
        return flags_;
    }

    void AddFlag(VariableFlags flag)
    {
        flags_ |= flag;
    }

    bool HasFlag(VariableFlags flag) const
    {
        return (flags_ & flag) != 0;
    }

    void RemoveFlag(VariableFlags flag)
    {
        flags_ &= ~flag;
    }

    void Reset(Decl *decl, VariableFlags flags)
    {
        decl_ = decl;
        flags_ = flags;
    }

    bool LexicalBound() const
    {
        return HasFlag(VariableFlags::LEXICAL_BOUND);
    }

    bool InSendableEnv() const
    {
        return HasFlag(VariableFlags::IN_SENDABLE_ENV);
    }

    const util::StringView &Name() const;
    virtual void SetLexical(Scope *scope, util::PatchFix *patchFixHelper = nullptr) = 0;

protected:
    explicit Variable(Decl *decl, VariableFlags flags) : decl_(decl), flags_(flags) {}

    Decl *decl_;
    VariableFlags flags_ {};
};

class LocalVariable : public Variable {
public:
    explicit LocalVariable(Decl *decl, VariableFlags flags);

    VariableType Type() const override
    {
        return VariableType::LOCAL;
    }

    void BindVReg(compiler::VReg vreg)
    {
        ASSERT(!LexicalBound());
        vreg_ = vreg;
    }

    void BindLexEnvSlot(uint32_t slot)
    {
        ASSERT(!LexicalBound());
        AddFlag(VariableFlags::LEXICAL_BOUND);
        vreg_ = slot;
    }

    compiler::VReg Vreg() const
    {
        return vreg_;
    }

    uint32_t LexIdx() const
    {
        ASSERT(LexicalBound());
        return vreg_;
    }

    void SetLexical([[maybe_unused]] Scope *scope, [[maybe_unused]] util::PatchFix *patchFixHelper = nullptr) override;
    LocalVariable *Copy(ArenaAllocator *allocator, Decl *decl) const;

private:
    uint32_t vreg_ {};
};

class GlobalVariable : public Variable {
public:
    explicit GlobalVariable(Decl *decl, VariableFlags flags) : Variable(decl, flags) {}

    VariableType Type() const override
    {
        return VariableType::GLOBAL;
    }

    void SetLexical([[maybe_unused]] Scope *scope, [[maybe_unused]] util::PatchFix *patchFixHelper = nullptr) override;
};

class ModuleVariable : public Variable {
public:
    explicit ModuleVariable(Decl *decl, VariableFlags flags) : Variable(decl, flags) {}

    VariableType Type() const override
    {
        return VariableType::MODULE;
    }

    void SetLexical([[maybe_unused]] Scope *scope, [[maybe_unused]] util::PatchFix *patchFixHelper = nullptr) override;

    void AssignIndex(uint32_t index)
    {
        index_ = index;
    }

    uint32_t Index() const
    {
        return index_;
    }

private:
    uint32_t index_ {0};
};

class EnumVariable : public Variable {
public:
    explicit EnumVariable(Decl *decl, bool backReference = false)
        : Variable(decl, VariableFlags::NONE), backReference_(backReference)
    {
    }

    VariableType Type() const override
    {
        return VariableType::ENUM;
    }

    void SetValue(EnumMemberResult value)
    {
        value_ = value;
    }

    const EnumMemberResult &Value() const
    {
        return value_;
    }

    bool BackReference() const
    {
        return backReference_;
    }

    void SetBackReference()
    {
        backReference_ = true;
    }

    bool IsVisited() const
    {
        return isVisited_;
    }

    void SetVisited()
    {
        isVisited_ = true;
    }

    bool StringInit() const
    {
        return isStringInit_;
    }

    void SetStringInit()
    {
        isStringInit_ = true;
    }

    void ResetDecl(Decl *decl);

    void SetLexical([[maybe_unused]] Scope *scope, [[maybe_unused]] util::PatchFix *patchFixHelper = nullptr) override;

private:
    EnumMemberResult value_ {false};
    bool backReference_ {};
    bool isVisited_ {false};
    bool isStringInit_ {false};
};

class NamespaceVariable : public Variable {
public:
    explicit NamespaceVariable(Decl *decl, VariableFlags flags) : Variable(decl, flags) {}

    VariableType Type() const override
    {
        return VariableType::NAMESPACE;
    }

    void SetLexical([[maybe_unused]] Scope *scope, [[maybe_unused]] util::PatchFix *patchFixHelper = nullptr) override;

    ExportBindings *GetExportBindings()
    {
        return exportBindings_;
    }

    const ExportBindings *GetExportBindings() const
    {
        return exportBindings_;
    }

    void SetExportBindings(ExportBindings *exportBindings)
    {
        exportBindings_ = exportBindings;
    }

private:
    ExportBindings *exportBindings_ {nullptr};
};

class EnumLiteralVariable : public Variable {
public:
    explicit EnumLiteralVariable(Decl *decl, VariableFlags flags) : Variable(decl, flags) {}

    VariableType Type() const override
    {
        return VariableType::ENUMLITERAL;
    }

    void SetLexical([[maybe_unused]] Scope *scope, [[maybe_unused]] util::PatchFix *patchFixHelper = nullptr) override;

    VariableMap *GetEnumMembers() const
    {
        return enumMemberBindings_;
    }

    Variable *FindEnumMemberVariable(const util::StringView &name) const
    {
        auto res = enumMemberBindings_->find(name);
        if (res == enumMemberBindings_->end()) {
            return nullptr;
        }
        return res->second;
    }

    void SetEnumMembers(VariableMap *enumMemberBindings)
    {
        enumMemberBindings_ = enumMemberBindings;
    }

private:
    VariableMap *enumMemberBindings_ {nullptr};
};

class ImportEqualsVariable : public Variable {
public:
    explicit ImportEqualsVariable(Decl *decl, VariableFlags flags) : Variable(decl, flags) {}

    VariableType Type() const override
    {
        return VariableType::IMPORT_EQUALS;
    }

    void SetLexical([[maybe_unused]] Scope *scope, [[maybe_unused]] util::PatchFix *patchFixHelper = nullptr) override;

    Scope *GetScope()
    {
        return scope_;
    }

    void SetScope(Scope *scope)
    {
        scope_ = scope;
    }

private:
    Scope *scope_ {nullptr};
};

}  // namespace panda::es2panda::binder
#endif