#ifndef RUBY_TYPER_NAMEREF_H
#define RUBY_TYPER_NAMEREF_H
#include "common/common.h"
#include "core/DebugOnlyCheck.h"
#include <compare>

namespace sorbet::core {
class GlobalState;
class NameSubstitution;
struct UniqueName;
struct ConstantName;
struct UTF8Name;

// If you add NameKinds, make sure NameRef::KIND_BITS is kept in sync!
enum class NameKind : uint8_t {
    UTF8 = 1,
    UNIQUE = 2,
    CONSTANT = 3,
};

CheckSize(NameKind, 1, 1);

enum class UniqueNameKind : uint8_t {
    Desugar,
    Namer,
    MangleRename,
    Singleton,
    Overload,
    TypeVarName,
    PositionalArg,        // num >=0 - normal args, -1 ==> restarg, -2 => kwrest arg
    MangledKeywordArg,    // only used when we have duplicated keyword arguments
    ResolverMissingClass, // used by resolver when we want to enter a stub class into a static field. see
                          // test/resolver/stub_missing_class_alias.rb
    TEnum,
    Struct,
    Packager,
    DesugarCsend, // Used for Extract to Variable; see the CSend case in desugar.cc for more details
};

struct NameDataDebugCheck {
    const GlobalState &gs;
    const unsigned int nameCountAtCreation;

    NameDataDebugCheck(const GlobalState &gs);
    void check() const;
};

/** This is to `NameRef &` what SymbolData is to `SymbolRef &`. Read docs on SymbolData */
class UniqueNameData : private DebugOnlyCheck<NameDataDebugCheck> {
    const UniqueName &name;

public:
    UniqueNameData(const UniqueName &ref, const GlobalState &gs);
    const UniqueName *operator->() const;
};

class ConstantNameData : private DebugOnlyCheck<NameDataDebugCheck> {
    const ConstantName &name;

public:
    ConstantNameData(const ConstantName &ref, const GlobalState &gs);
    const ConstantName *operator->() const;
};

class UTF8NameData : private DebugOnlyCheck<NameDataDebugCheck> {
    const UTF8Name &name;

public:
    UTF8NameData(const UTF8Name &ref, const GlobalState &gs);
    const UTF8Name *operator->() const;
};

constexpr size_t sizeof__UniqueName = sizeof(UniqueName *);
constexpr size_t alignof__UniqueName = alignof(UniqueName *);
CheckSize(UniqueNameData, sizeof__UniqueName, alignof__UniqueName);

struct NameRefDebugCheck {
    int globalStateId;

    constexpr NameRefDebugCheck() : globalStateId(-1) {}

    NameRefDebugCheck(const GlobalState &gs, NameKind kind, uint32_t id);

    void check(const GlobalState &gs, NameKind kind, uint32_t id) const;
    void check(const NameSubstitution &subst) const;
};

class NameRef final : private DebugOnlyCheck<NameRefDebugCheck> {
private:
    // NameKind takes up this many bits in _id.
    static constexpr uint32_t KIND_BITS = 2;
    static constexpr uint32_t ID_BITS = 32 - KIND_BITS;
    static constexpr uint32_t KIND_MASK = (1 << KIND_BITS) - 1;
    static constexpr uint32_t MAX_ID = (1 << ID_BITS) - 1;

    uint32_t _id;

    uint32_t unsafeTableIndex() const {
        return _id >> KIND_BITS;
    }

public:
    // The value `0` implies that there is no NameKind tag present (NameKind begin at 1) and is a special-value used to
    // indicate a non-existent NameRef.
    constexpr NameRef() noexcept : _id(0){};

    // WellKnown is a tag to statically indicate that the caller is deliberately
    // constructing a well-known name, whose ID is stable across all
    // GlobalStates. This should never be used outside of the name constructors
    // generated by tools/generate_names.cc
    struct WellKnown {};

    constexpr NameRef(WellKnown, NameKind kind, uint32_t id) : _id{(id << KIND_BITS) | static_cast<uint32_t>(kind)} {
        // If this fails, the symbol table is too big :(
        ENFORCE_NO_TIMER(id <= MAX_ID);
    }

    NameRef(const GlobalState &gs, NameKind kind, uint32_t id);

    NameRef(const GlobalState &gs, NameRef ref);

    NameRef(const NameRef &nm) = default;

    NameRef(NameRef &&nm) = default;

    static NameRef fromRaw(const GlobalState &gs, uint32_t raw) {
        return NameRef(gs, NameRef::fromRawUnchecked(raw));
    }

    static NameRef fromRawUnchecked(uint32_t raw) {
        auto ref = NameRef();
        ref._id = raw;
        return ref;
    }

    NameRef &operator=(const NameRef &rhs) = default;

    auto operator<=>(const NameRef &rhs) const noexcept {
        return this->_id <=> rhs._id;
    }

    inline bool operator==(const NameRef &rhs) const noexcept {
        return std::is_eq(*this <=> rhs);
    }
    inline bool operator!=(const NameRef &rhs) const noexcept {
        return std::is_neq(*this <=> rhs);
    }

    constexpr uint32_t rawId() const {
        return _id;
    }

    uint32_t utf8Index() const {
        ENFORCE_NO_TIMER(kind() == NameKind::UTF8);
        return unsafeTableIndex();
    }

    uint32_t uniqueIndex() const {
        ENFORCE_NO_TIMER(kind() == NameKind::UNIQUE);
        return unsafeTableIndex();
    }

    uint32_t constantIndex() const {
        ENFORCE_NO_TIMER(kind() == NameKind::CONSTANT);
        return unsafeTableIndex();
    }

    constexpr NameKind kind() const {
        auto nameKind = static_cast<NameKind>(_id & KIND_MASK);
        ENFORCE_NO_TIMER(static_cast<uint8_t>(nameKind) > 0);
        return nameKind;
    }

    /* Names are one of three different kinds: unique, utf8, or constant. */

    const UniqueNameData dataUnique(const GlobalState &gs) const;
    const UTF8NameData dataUtf8(const GlobalState &gs) const;
    const ConstantNameData dataCnst(const GlobalState &gs) const;

    // Returns the `0` NameRef, used to indicate non-existence of a name
    static constexpr NameRef noName() {
        return NameRef();
    }

    inline bool exists() const {
        return _id != 0;
    }

    NameRef addEq(GlobalState &gs) const;
    NameRef lookupWithEq(const GlobalState &gs) const;
    bool isSetter(const GlobalState &gs) const;

    NameRef addQuestion(GlobalState &gs) const;

    NameRef addAt(GlobalState &gs) const;
    NameRef lookupWithAt(const GlobalState &gs) const;

    NameRef prepend(GlobalState &gs, std::string_view s) const;

    bool isOverloadName(const GlobalState &gs) const;

    bool isClassName(const GlobalState &gs) const;

    // Convenience method, because enums need to be special cased in more places than other kinds of
    // unique names, and everyone always forget to unwrap the first layer (NameKind::CONSTANT)
    // before checking for UniqueNameKind::TEnum.
    bool isTEnumName(const GlobalState &gs) const;

    bool isValidConstantName(const GlobalState &gs) const;

    // Returns true if the name is `<static-init>` (class-level static init) or `<static-init>$...`
    // (file-level static init).
    bool isAnyStaticInitName(const GlobalState &gs) const;

    bool isUniqueNameOf(const GlobalState &gs, NameRef name) const;

    bool hasUniqueNameKind(const GlobalState &gs, UniqueNameKind uniqueNameKind) const;

    // All the names that Environment::updateKnowledge treats as special for the purposes of
    // updating control flow-sensitive type knowledge.
    //
    // (This is a small hack, because Sorbet should really use Symbols not names for that purpose:
    // https://github.com/sorbet/sorbet/issues/1747)
    bool isUpdateKnowledgeName() const;

    // Returns true if this name is one of the "keywords" that can modify a method `def`,
    // like private/protected/public, etc.
    bool isMethodDefModifierName() const;

    std::string_view shortName(const GlobalState &gs) const;
    std::string showRaw(const GlobalState &gs) const;
    std::string toString(const GlobalState &gs) const;
    std::string show(const GlobalState &gs) const;
    std::string showAsSymbolLiteral(const GlobalState &gs) const;

    void enforceCorrectGlobalState(const GlobalState &gs) const;
    void sanityCheckSubstitution(const NameSubstitution &subst) const;
    void sanityCheck(const GlobalState &gs) const;
};
CheckSize(NameRef, 4, 4);

template <typename H> H AbslHashValue(H h, const NameRef &m) {
    return H::combine(std::move(h), m.rawId());
}
} // namespace sorbet::core

#endif // RUBY_TYPER_NAMEREF_H
