#include "slang-mangle.h"

#include "compiler-core/slang-name.h"
#include "slang-check.h"
#include "slang-syntax.h"

namespace Slang
{
struct ManglingContext
{
    ManglingContext(ASTBuilder* inAstBuilder)
        : astBuilder(inAstBuilder)
    {
    }
    ASTBuilder* astBuilder;
    StringBuilder sb;
    Decl* targetDecl = nullptr; // The decl we are mangling name for.
    bool isEmittingTarget = false;
};

void emitRaw(ManglingContext* context, char const* text)
{
    context->sb.append(text);
}

void emit(ManglingContext* context, UInt value)
{
    context->sb.append(value);
}

void emit(ManglingContext* context, String const& value)
{
    context->sb.append(value);
}

void emitNameForLinkage(StringBuilder& sb, UnownedStringSlice str)
{
    Index length = str.getLength();
    // If the name consists of only traditional "identifer characters"
    // (`[a-zA-Z_]`), then we want to emit it more or less directly.
    //
    bool allAllowed = true;
    for (auto c : str)
    {
        if (('a' <= c) && (c <= 'z'))
            continue;
        if (('A' <= c) && (c <= 'Z'))
            continue;
        if (('0' <= c) && (c <= '9'))
            continue;
        if (c == '_')
            continue;

        allAllowed = false;
        break;
    }
    if (allAllowed)
    {
        // We prefix the string with its byte length, so that
        // decoding doesn't have to worry about finding a terminator.
        //
        // Note: in this case `length` is the same as the number of
        // code points and the number of extended grapheme clusters,
        // since the entire name is within the ASCII subset.
        //
        sb.append(length);
        sb.append(str);
    }
    else
    {
        // Other names that aren't pure ASCII require escaping. We
        // will use a rather simple escaping scheme where the basic
        // ASCII alphanumeric code points go through unmodified,
        // and we use `_` as a kind of escape character.
        //
        StringBuilder encoded;

        // TODO: This loop probalby ought to be over code points
        // rather than bytes.
        //
        for (auto c : str)
        {
            if (('a' <= c) && (c <= 'z') || ('A' <= c) && (c <= 'Z') || ('0' <= c) && (c <= '9'))
            {
                encoded.append(c);
            }
            else if (c == '_')
            {
                encoded.append("_u");
            }
            else
            {
                // Any byte that isn't within the allowed ranges
                // we be turned into hex, prefixed with `_` and
                // suffixed with `x`.
                //
                encoded.append("_");
                encoded.append(uint32_t((unsigned char)c), 16);
                encoded.appendChar('x');
            }
        }

        sb.append("R");
        sb.append(encoded.getLength());
        sb.append(encoded);
    }

    // TODO: This logic does not rule out consecutive underscores,
    // even though the GLSL target does not support consecutive underscores
    // (or leading underscores, IIRC) in user identifiers.
    //
    // Realistically, that is best dealt with as a quirk of tha particular
    // target, rather than adding complexity here.
}

void emitNameImpl(ManglingContext* context, UnownedStringSlice str)
{
    emitNameForLinkage(context->sb, str);
}

void emitName(ManglingContext* context, Name* name)
{
    String str = getText(name);
    emitNameImpl(context, str.getUnownedSlice());
}

void emitVal(ManglingContext* context, Val* val);

void emitQualifiedName(ManglingContext* context, DeclRef<Decl> declRef, bool includeModuleName);

void emitSimpleIntVal(ManglingContext* context, Val* val)
{
    if (auto constVal = as<ConstantIntVal>(val))
    {
        auto cVal = constVal->getValue();
        if (cVal >= 0 && cVal <= 9)
        {
            emit(context, (UInt)cVal);
            return;
        }
    }

    // Fallback:
    emitVal(context, val);
}

void emitBaseType(ManglingContext* context, BaseType baseType)
{
    switch (baseType)
    {
    case BaseType::Void:
        emitRaw(context, "V");
        break;
    case BaseType::Bool:
        emitRaw(context, "b");
        break;
    case BaseType::Int8:
        emitRaw(context, "c");
        break;
    case BaseType::Int16:
        emitRaw(context, "s");
        break;
    case BaseType::Int:
        emitRaw(context, "i");
        break;
    case BaseType::Int64:
        emitRaw(context, "I");
        break;
    case BaseType::UInt8:
        emitRaw(context, "C");
        break;
    case BaseType::UInt16:
        emitRaw(context, "S");
        break;
    case BaseType::UInt:
        emitRaw(context, "u");
        break;
    case BaseType::UInt64:
        emitRaw(context, "U");
        break;
    case BaseType::Half:
        emitRaw(context, "h");
        break;
    case BaseType::Float:
        emitRaw(context, "f");
        break;
    case BaseType::Double:
        emitRaw(context, "d");
        break;
    case BaseType::UIntPtr:
        emitRaw(context, "up");
        break;
    case BaseType::IntPtr:
        emitRaw(context, "ip");
        break;

    default:
        SLANG_UNEXPECTED("unimplemented case in base type mangling");
        break;
    }
}

void emitType(ManglingContext* context, Type* type)
{
    // TODO: actually implement this bit...

    if (auto basicType = dynamicCast<BasicExpressionType>(type))
    {
        emitBaseType(context, basicType->getBaseType());
    }
    else if (auto vecType = dynamicCast<VectorExpressionType>(type))
    {
        emitRaw(context, "v");
        emitSimpleIntVal(context, vecType->getElementCount());
        emitType(context, vecType->getElementType());
    }
    else if (auto matType = dynamicCast<MatrixExpressionType>(type))
    {
        emitRaw(context, "m");
        emitSimpleIntVal(context, matType->getRowCount());
        emitRaw(context, "x");
        emitSimpleIntVal(context, matType->getColumnCount());
        emitType(context, matType->getElementType());
    }
    else if (auto namedType = dynamicCast<NamedExpressionType>(type))
    {
        emitType(context, getType(context->astBuilder, namedType->getDeclRef()));
    }
    else if (auto declRefType = dynamicCast<DeclRefType>(type))
    {
        emitQualifiedName(context, declRefType->getDeclRef(), true);
    }
    else if (auto arrType = dynamicCast<ArrayExpressionType>(type))
    {
        emitRaw(context, "a");
        emitSimpleIntVal(context, arrType->getElementCount());
        emitType(context, arrType->getElementType());
    }
    else if (auto thisType = dynamicCast<ThisType>(type))
    {
        emitRaw(context, "t");
        emitQualifiedName(context, thisType->getInterfaceDeclRef(), true);
    }
    else if (const auto errorType = dynamicCast<ErrorType>(type); errorType)
    {
        emitRaw(context, "E");
    }
    else if (const auto bottomType = dynamicCast<BottomType>(type); bottomType)
    {
        emitRaw(context, "B");
    }
    else if (auto funcType = dynamicCast<FuncType>(type))
    {
        emitRaw(context, "F");
        auto n = funcType->getParamCount();
        emit(context, n);
        for (Index i = 0; i < n; ++i)
            emitType(context, funcType->getParamTypeWithModeWrapper(i));
        emitType(context, funcType->getResultType());
        emitType(context, funcType->getErrorType());
    }
    else if (auto tupleType = dynamicCast<TupleType>(type))
    {
        emitRaw(context, "Tu");
        auto n = tupleType->getMemberCount();
        emit(context, n);
        for (Index i = 0; i < n; ++i)
            emitType(context, tupleType->getMember(i));
    }
    else if (auto modifiedType = dynamicCast<ModifiedType>(type))
    {
        emitRaw(context, "Tm");
        emitType(context, modifiedType->getBase());
        auto n = modifiedType->getModifierCount();
        emit(context, n);
        for (Index i = 0; i < n; ++i)
            emitVal(context, modifiedType->getModifier(i));
    }
    else if (auto andType = as<AndType>(type))
    {
        emitRaw(context, "Ta");
        emitType(context, andType->getLeft());
        emitType(context, andType->getRight());
    }
    else if (auto expandType = as<ExpandType>(type))
    {
        emitRaw(context, "Tx");
        emitType(context, expandType->getPatternType());
    }
    else if (auto packBranchType = as<PackBranchType>(type))
    {
        emitRaw(context, "Tb");
        emitVal(context, packBranchType->getPackOperand());
        emitType(context, packBranchType->getEmptyType());
        emitType(context, packBranchType->getNonEmptyType());
    }
    else if (auto firstType = as<FirstPackElementType>(type))
    {
        emitRaw(context, "Tf");
        emitType(context, firstType->getBasePack());
    }
    else if (auto lastType = as<LastPackElementType>(type))
    {
        emitRaw(context, "Tl");
        emitType(context, lastType->getBasePack());
    }
    else if (auto trimFirstType = as<TrimFirstTypePack>(type))
    {
        emitRaw(context, "Th");
        emitType(context, trimFirstType->getBasePack());
    }
    else if (auto trimLastType = as<TrimLastTypePack>(type))
    {
        emitRaw(context, "Tt");
        emitType(context, trimLastType->getBasePack());
    }
    else if (auto eachType = as<EachType>(type))
    {
        emitRaw(context, "Te");
        emitType(context, eachType->getElementType());
    }
    else if (auto typePack = as<ConcreteTypePack>(type))
    {
        emitRaw(context, "Tp");
        emit(context, typePack->getTypeCount());
        for (Index i = 0; i < typePack->getTypeCount(); i++)
            emitType(context, typePack->getElementType(i));
    }
    else if (auto valuePackType = as<ValuePackType>(type))
    {
        emitRaw(context, "TVp");
        emitType(context, valuePackType->getElementType());
    }
    else
    {
        SLANG_UNEXPECTED("unimplemented case in type mangling");
    }
}

void emitVal(ManglingContext* context, Val* val)
{
    if (auto type = dynamicCast<Type>(val))
    {
        emitType(context, type);
    }
    else if (const auto witness = dynamicCast<Witness>(val); witness)
    {
        // We don't emit witnesses as part of a mangled
        // name, because the way that the front-end
        // arrived at the witness is not important;
        // what matters is that the type constraint
        // was satisfied.
        //
        // TODO: make sure we can't get name collisions
        // between specializations of declarations
        // with the same numbers of generic parameters,
        // but different constraints. We might have
        // to mangle in the constraints even when
        // the whole thing is specialized...
    }
    else if (auto genericParamIntVal = dynamicCast<DeclRefIntVal>(val))
    {
        // TODO: we shouldn't be including the names of generic parameters
        // anywhere in mangled names, since changing parameter names
        // shouldn't break binary compatibility.
        //
        // The right solution in the long term is for generic parameters
        // (both types and values) to be mangled in terms of their
        // "depth" (how many outer generics) and "index" (which
        // parameter are they at the specified depth).
        emitRaw(context, "K");
        emitName(context, genericParamIntVal->getDeclRef().getName());
    }
    else if (auto constantIntVal = dynamicCast<ConstantIntVal>(val))
    {
        // TODO: need to figure out what prefix/suffix is needed
        // to allow demangling later.
        emitRaw(context, "k");
        emit(context, (UInt)constantIntVal->getValue());
    }
    else if (auto builtinOpIntVal = dynamicCast<BuiltinOperationIntVal>(val))
    {
        emitRaw(context, "KB");
        emit(context, builtinOpIntVal->getArgs().getCount());
        emitNameImpl(context, getBuiltinOperationOpText(builtinOpIntVal->getOp()));
        for (Index i = 0; i < builtinOpIntVal->getArgs().getCount(); i++)
            emitVal(context, builtinOpIntVal->getArgs()[i]);
    }
    else if (auto lookupIntVal = dynamicCast<WitnessLookupIntVal>(val))
    {
        emitRaw(context, "KL");
        emitVal(context, lookupIntVal->getWitness());
        emitName(context, lookupIntVal->getKey()->getName());
    }
    else if (auto sizeOfIntVal = dynamicCast<SizeOfIntVal>(val))
    {
        emitRaw(context, "KSO");
        emitVal(context, sizeOfIntVal->getValArg());
    }
    else if (auto alignOfIntVal = dynamicCast<AlignOfIntVal>(val))
    {
        emitRaw(context, "KAO");
        emitVal(context, alignOfIntVal->getValArg());
    }
    else if (auto countOfIntVal = dynamicCast<CountOfIntVal>(val))
    {
        emitRaw(context, "KCO");
        emitVal(context, countOfIntVal->getValArg());
    }
    else if (auto firstIntVal = as<FirstIntVal>(val))
    {
        emitRaw(context, "KPF");
        emitVal(context, firstIntVal->getBasePack());
    }
    else if (auto lastIntVal = as<LastIntVal>(val))
    {
        emitRaw(context, "KPL");
        emitVal(context, lastIntVal->getBasePack());
    }
    else if (const auto polynomialIntVal = dynamicCast<PolynomialIntVal>(val))
    {
        emitRaw(context, "KX");
        emit(context, (UInt)polynomialIntVal->getConstantTerm());
        emit(context, (UInt)polynomialIntVal->getTerms().getCount());
        for (auto term : polynomialIntVal->getTerms())
        {
            emit(context, (UInt)term->getConstFactor());
            emit(context, (UInt)term->getParamFactors().getCount());
            for (auto factor : term->getParamFactors())
            {
                emitVal(context, factor->getParam());
                emit(context, (UInt)factor->getPower());
            }
        }
    }
    else if (const auto typecastIntVal = dynamicCast<TypeCastIntVal>(val))
    {
        emitRaw(context, "KK");
        emitVal(context, typecastIntVal->getType());
        emitVal(context, typecastIntVal->getBase());
    }
    else if (auto modifier = as<ModifierVal>(val))
    {
        emitNameImpl(context, UnownedStringSlice(modifier->getClass().getName()));
    }
    else if (auto concreteValPack = as<ConcreteIntValPack>(val))
    {
        emitRaw(context, "Vp");
        emit(context, concreteValPack->getCount());
        for (Index i = 0; i < concreteValPack->getCount(); i++)
            emitVal(context, concreteValPack->getElement(i));
    }
    else if (auto eachIntVal = as<EachIntVal>(val))
    {
        emitRaw(context, "Ve");
        emitVal(context, eachIntVal->getBasePack());
    }
    else if (auto expandIntValPack = as<ExpandIntValPack>(val))
    {
        emitRaw(context, "Vx");
        emitVal(context, expandIntValPack->getPatternVal());
        emit(context, expandIntValPack->getCapturedPackCount());
        for (Index i = 0; i < expandIntValPack->getCapturedPackCount(); i++)
            emitVal(context, expandIntValPack->getCapturedPack(i));
    }
    else if (auto trimFirstIntValPack = as<TrimFirstIntValPack>(val))
    {
        emitRaw(context, "Vh");
        emitVal(context, trimFirstIntValPack->getBasePack());
    }
    else if (auto trimLastIntValPack = as<TrimLastIntValPack>(val))
    {
        emitRaw(context, "Vt");
        emitVal(context, trimLastIntValPack->getBasePack());
    }
    else if (auto shapeConcatIntValPack = as<ShapeConcatIntValPack>(val))
    {
        emitRaw(context, "Vc");
        emitVal(context, shapeConcatIntValPack->getLeftPack());
        emitVal(context, shapeConcatIntValPack->getRightPack());
        emitVal(context, shapeConcatIntValPack->getAxis());
    }
    else if (auto shapePermuteIntValPack = as<ShapePermuteIntValPack>(val))
    {
        emitRaw(context, "Vr");
        emitVal(context, shapePermuteIntValPack->getValuePack());
        emitVal(context, shapePermuteIntValPack->getOrderPack());
    }
    else if (auto shapeSwapIntValPack = as<ShapeSwapIntValPack>(val))
    {
        emitRaw(context, "Vs");
        emitVal(context, shapeSwapIntValPack->getValuePack());
        emitVal(context, shapeSwapIntValPack->getDim0());
        emitVal(context, shapeSwapIntValPack->getDim1());
    }
    else if (auto shapeReduceIntValPack = as<ShapeReduceIntValPack>(val))
    {
        emitRaw(context, "Vd");
        emitVal(context, shapeReduceIntValPack->getValuePack());
        emitVal(context, shapeReduceIntValPack->getAxis());
    }
    else
    {
        SLANG_UNEXPECTED("unimplemented case in val mangling");
    }
}

void emitQualifiedName(ManglingContext* context, DeclRef<Decl> declRef, bool includeModuleName)
{
    if (declRef.getDecl() == context->targetDecl)
    {
        if (!context->isEmittingTarget)
        {
            context->isEmittingTarget = true;
        }
        else
        {
            // We are already in the middle of mangling the target decl,
            // so we must have run into a cycle.
            //
            // For example, this can happen when mangling a struct defined
            // inline at a parameter-type locataion inside a function.
            // During mangling that struct, we will mangle the parent function,
            // but the mangled name of that parent function will contain
            // parameter types, which means we will attempt to mangle the struct
            // again.
            //
            // In this case we will just skip emitting for the current declRef
            // to break the cycle.
            return;
        }
    }

    bool ignoreName = false;
    if (!includeModuleName)
    {
        if (as<ModuleDecl>(declRef))
            return;
    }
    else
    {
        for (auto modifier : declRef.getDecl()->modifiers)
        {
            if (as<ExternModifier>(modifier) || as<HLSLExportModifier>(modifier))
            {
                includeModuleName = false;
                break;
            }
        }
    }

    if (declRef.getDecl()->hasModifier<ExternCppModifier>())
    {
        emit(context, declRef.getDecl()->getName()->text);
        return;
    }

    if (auto genTypeParamDecl = as<GenericTypeParamDeclBase>(declRef.getDecl()))
    {
        emit(context, "GP");
        emit(context, genTypeParamDecl->parameterIndex);
        return;
    }
    if (auto genValParamDecl = as<GenericValueParamDecl>(declRef.getDecl()))
    {
        emit(context, "GP");
        emit(context, genValParamDecl->parameterIndex);
        return;
    }
    if (auto genValPackParamDecl = as<GenericValuePackParamDecl>(declRef.getDecl()))
    {
        emit(context, "GP");
        emit(context, genValPackParamDecl->parameterIndex);
        return;
    }

    auto parentDeclRef = declRef.getParent();
    if (as<FileDecl>(parentDeclRef))
        parentDeclRef = parentDeclRef.getParent();

    auto parentGenericDeclRef = parentDeclRef.as<GenericDecl>();
    if (parentDeclRef)
    {
        emitQualifiedName(context, parentDeclRef, includeModuleName);
    }

    // A generic declaration is kind of a pseudo-declaration
    // as far as the user is concerned; so we don't want
    // to emit its name.
    if (auto genericDeclRef = declRef.as<GenericDecl>())
    {
        return;
    }

    if (auto extensionDeclRef = declRef.as<ExtensionDecl>())
    {
        // TODO: as a special case, an "unconditional" extension
        // that is in the same module as the type it extends should
        // be treated as equivalent to the type itself.
        emit(context, "X");
        emitType(context, getTargetType(context->astBuilder, extensionDeclRef));
        for (auto inheritanceDecl :
             getMembersOfType<InheritanceDecl>(context->astBuilder, extensionDeclRef))
        {
            emit(context, "I");
            emitType(context, getSup(context->astBuilder, inheritanceDecl));
        }
        // A non generic extension doesn't have a name worth
        // emitting, and we should base things on its target
        // type instead.
        if (parentGenericDeclRef)
        {
            ignoreName = true;
        }
    }
    // Inheritance declarations don't have meaningful names,
    // and so we should emit them based on the type
    // that is doing the inheriting.
    else if (auto inheritanceDeclRef = declRef.as<TypeConstraintDecl>())
    {
        emit(context, "I");
        emitType(context, getSup(context->astBuilder, inheritanceDeclRef));

        // An interface-level constraint that is a direct member of an interface
        // (a `GenericTypeConstraintDecl` produced by relocating an associated
        // type's bound -- `associatedtype A : IFoo`, an `associatedtype ...
        // where` clause, or an explicit `__constraint`) is not uniquely
        // identified by the parent interface and the bound `sup` alone: a single
        // interface may constrain several associated types to the *same* bound,
        // e.g. `associatedtype A : IFoo; associatedtype B : IFoo;`. Without
        // encoding the constrained type these would mangle to the same
        // requirement-key name and collide (e.g. in the witness-table cloning
        // dictionary during linking). Emit the constrained `sub` type so each
        // such constraint gets a distinct name. Ordinary inheritance
        // (`InheritanceDecl`) and generic `where` constraints (whose parent is a
        // generic, not an interface) are unaffected.
        if (auto genConstraintDeclRef = declRef.as<GenericTypeConstraintDecl>())
        {
            if (isInterfaceRequirement(genConstraintDeclRef.getDecl()))
            {
                emitType(context, getSub(context->astBuilder, genConstraintDeclRef));
            }
        }

        if (parentGenericDeclRef)
        {
            ignoreName = true;
        }
    }

    // TODO: we should special case GenericTypeParamDecl and GenericValueParamDecl nodes
    // instead of just dumping their names here to avoid the name of a generic parameter
    // to have affect the binary signature.
    // For each generic parameter, we should assign it a unique ID (i, j), where i is the
    // nesting level of the generic, and j is the sequential order of the parameter within
    // its generic parent, and use this 2D ID to refer to such a parameter.
    if (!ignoreName)
    {
        emitName(context, declRef.getName());
    }

    // Special case: accessors need some way to distinguish themselves
    // so that a getter/setter/ref-er don't all compile to the same name.
    {
        if (declRef.is<GetterDecl>())
            emitRaw(context, "Ag");
        if (declRef.is<SetterDecl>())
            emitRaw(context, "As");
        if (declRef.is<RefAccessorDecl>())
            emitRaw(context, "Ar");
    }

    // Special case: need a way to tell prefix and postfix unary
    // operators apart.
    {
        if (declRef.getDecl()->hasModifier<PostfixModifier>())
            emitRaw(context, "P");
        if (declRef.getDecl()->hasModifier<PrefixModifier>())
            emitRaw(context, "p");
    }


    // Are we the "inner" declaration beneath a generic decl?
    if (parentGenericDeclRef && (parentGenericDeclRef.getDecl()->inner == declRef.getDecl()))
    {
        // There are two cases here: either we have specializations
        // in place for the parent generic declaration, or we don't.

        auto substArgs =
            tryGetGenericArguments(SubstitutionSet(declRef), parentGenericDeclRef.getDecl());
        if (substArgs.getCount())
        {
            // This is the case where we *do* have substitutions.
            emitRaw(context, "G");
            UInt genericArgCount = substArgs.getCount();
            emit(context, genericArgCount);
            for (auto aa : substArgs)
            {
                emitVal(context, aa);
            }
        }
        else
        {
            // We don't have substitutions, so we will emit
            // information about the parameters of the generic here.
            emitRaw(context, "g");
            UInt genericParameterCount = 0;
            for (auto mm :
                 getMembers(context->astBuilder, parentGenericDeclRef.as<ContainerDecl>()))
            {
                if (mm.is<GenericTypeParamDecl>())
                {
                    genericParameterCount++;
                }
                else if (mm.is<GenericValueParamDecl>())
                {
                    genericParameterCount++;
                }
                else if (mm.is<GenericTypeConstraintDecl>())
                {
                    genericParameterCount++;
                }
                else if (mm.is<GenericTypePackParamDecl>())
                {
                    genericParameterCount++;
                }
                else if (mm.is<GenericValuePackParamDecl>())
                {
                    genericParameterCount++;
                }
                else
                {
                }
            }

            emit(context, genericParameterCount);

            OrderedDictionary<GenericTypeParamDeclBase*, List<Type*>> genericConstraints;
            for (auto mm : getMembers(context->astBuilder, parentGenericDeclRef))
            {
                if (auto genericTypeParamDecl = mm.as<GenericTypeParamDecl>())
                {
                    emitRaw(context, "T");
                }
                if (auto genericTypePackParamDecl = mm.as<GenericTypePackParamDecl>())
                {
                    emitRaw(context, "TP");
                }
                else if (auto genericValuePackParamDecl = mm.as<GenericValuePackParamDecl>())
                {
                    emitRaw(context, "VP");
                    emitType(context, getType(context->astBuilder, genericValuePackParamDecl));
                }
                else if (auto genericValueParamDecl = mm.as<GenericValueParamDecl>())
                {
                    emitRaw(context, "v");
                    emitType(context, getType(context->astBuilder, genericValueParamDecl));
                }
                else
                {
                }
            }

            auto canonicalizedConstraints =
                getCanonicalGenericConstraints2(context->astBuilder, parentGenericDeclRef);
            for (auto& constraint : canonicalizedConstraints)
            {
                if (constraint.value.getCount() > 0)
                {
                    emitRaw(context, "C");
                    emitType(context, constraint.key);
                    int counter = 0;
                    for (auto type : constraint.value)
                    {
                        if (counter > 0)
                        {
                            emitRaw(context, "_");
                        }
                        ++counter;
                        emitType(context, type);
                    }
                }
            }
        }
    }

    // If the declaration has parameters, then we need to emit
    // those parameters to distinguish it from other declarations
    // of the same name that might have different parameters.
    //
    // We'll also go ahead and emit the result type as well,
    // just for completeness.
    //
    if (auto callableDeclRef = declRef.as<CallableDecl>())
    {
        // If we're dealing with an alias, then pull the parameters from
        // the target decl-ref. We're careful to only do this when emitting
        // the parameter and result names, since we don't want to accidentally
        // overwrite the function name (which should be the name of the alias).
        //
        if (auto funcAliasDeclRef = callableDeclRef.as<FuncAliasDecl>())
        {
            callableDeclRef = substituteDeclRef(
                                  SubstitutionSet(funcAliasDeclRef),
                                  context->astBuilder,
                                  funcAliasDeclRef.getDecl()->targetDeclRef)
                                  .as<CallableDecl>();
        }

        // Get parameter type as a list.
        List<Type*> parameterTypes;
        List<ParamPassingMode> paramPassingModes;
        Type* resultType = nullptr;

        if (!callableDeclRef.getDecl()->funcType.type)
        {
            auto parameters = getParameters(context->astBuilder, callableDeclRef);
            for (auto paramDeclRef : parameters)
            {
                auto paramType = getType(context->astBuilder, paramDeclRef);
                parameterTypes.add(paramType);
                paramPassingModes.add(getParamPassingMode(paramDeclRef.getDecl()));
            }
            resultType = getResultType(context->astBuilder, callableDeclRef);

            emitRaw(context, "p");
            emit(context, parameterTypes.getCount());
            emitRaw(context, "p");

            for (Index i = 0; i < parameterTypes.getCount(); i++)
            {
                // parameter modifier makes big difference in the spirv code generation, for example
                // "out"/"inout" parameter will be passed by pointer. Therefore, we need to
                // distinguish them in the mangled name to avoid name collision.
                ParamPassingMode paramDirection = paramPassingModes[i];
                switch (paramDirection)
                {
                case ParamPassingMode::Ref:
                    emitRaw(context, "r_");
                    break;
                case ParamPassingMode::BorrowIn:
                    emitRaw(context, "c_");
                    break;
                case ParamPassingMode::Out:
                    emitRaw(context, "o_");
                    break;
                case ParamPassingMode::BorrowInOut:
                    emitRaw(context, "io_");
                    break;
                case ParamPassingMode::In:
                    emitRaw(context, "i_");
                    break;
                default:
                    StringBuilder errMsg;
                    errMsg << "Unknown parameter direction";
                    SLANG_ABORT_COMPILATION(errMsg.toString().begin());
                    break;
                }
                emitType(context, parameterTypes[i]);
            }

            // Don't print result type for an initializer/constructor,
            // since it is implicit in the qualified name.
            if (!callableDeclRef.is<ConstructorDecl>())
            {
                emitType(context, resultType);
            }

            // Include key modifiers in the mangled name so we never deduplicate
            // things like a nonmutating method and a mutating method.
            bool isMutating = false;
            bool isRefThis = false;
            bool isFwdDiff = false;
            bool isBwdDiff = false;
            bool isNoDiffThis = false;
            for (auto modifier : callableDeclRef.getDecl()->modifiers)
            {
                if (as<MutatingAttribute>(modifier))
                {
                    isMutating = true;
                }
                else if (as<RefAttribute>(modifier))
                {
                    isRefThis = true;
                }
                else if (as<ForwardDifferentiableAttribute>(modifier))
                {
                    isFwdDiff = true;
                }
                else if (as<BackwardDifferentiableAttribute>(modifier))
                {
                    isBwdDiff = true;
                }
                else if (as<NoDiffThisAttribute>(modifier))
                {
                    isNoDiffThis = true;
                }
            }

            if (isMutating)
                emitRaw(context, "m");
            if (isRefThis)
                emitRaw(context, "r");
            if (isFwdDiff)
                emitRaw(context, "f");
            if (isBwdDiff)
                emitRaw(context, "b");
            if (isNoDiffThis)
                emitRaw(context, "n");
        }
        else
        {
            auto funcType = callableDeclRef.getDecl()->funcType.type;
            auto resolvedFuncType =
                as<Type>(funcType->substitute(context->astBuilder, SubstitutionSet(callableDeclRef))
                             ->resolve());
            emitType(context, resolvedFuncType);
        }
    }
}

void mangleName(ManglingContext* context, DeclRef<Decl> declRef)
{
    // TODO: catch cases where the declaration should
    // forward to something else? E.g., what if we
    // are asked to mangle the name of a `typedef`?
    context->targetDecl = declRef.getDecl();

    auto decl = declRef.getDecl();
    if (!decl)
        return;

    // Handle `__extern_cpp` modifier by simply emitting
    // the given name.
    if (decl->hasModifier<ExternCppModifier>())
    {
        emit(context, decl->getName()->text);
        return;
    }

    // We will start with a unique prefix to avoid
    // clashes with user-defined symbols:
    emitRaw(context, "_S");

    // Next we will add a bit of info to register
    // the *kind* of declaration we are dealing with.
    //
    // Functions will get no prefix, since we assume
    // they are a common case:
    if (as<FuncDecl>(decl))
    {
    }
    // Types will get a `T` prefix:
    else if (as<AggTypeDecl>(decl))
        emitRaw(context, "T");
    else if (as<TypeDefDecl>(decl))
        emitRaw(context, "T");
    // Variables will get a `V` prefix:
    //
    // TODO: probably need to pull constant-buffer
    // declarations out of this...
    else if (as<VarDeclBase>(decl))
        emitRaw(context, "V");
    else if (DeclRef<GenericDecl> genericDecl = declRef.as<GenericDecl>())
    {
        // Mark that this is a generic, so we can differentiate bewteen when
        // mangling the generic and the inner entity
        emitRaw(context, "G");

        SLANG_ASSERT(SubstitutionSet(genericDecl).findGenericAppDeclRef() == nullptr);

        auto innerDecl = getInner(genericDecl);

        emitQualifiedName(context, makeDeclRef(innerDecl), true);
        return;
    }
    else
    {
        // TODO: handle other cases
    }

    // Now we encode the qualified name of the decl.

    emitQualifiedName(context, declRef, true);
}

static String getMangledName(ASTBuilder* astBuilder, DeclRef<Decl> const& declRef)
{
    SLANG_AST_BUILDER_RAII(astBuilder);
    ManglingContext context(astBuilder);
    mangleName(&context, declRef);
    return context.sb.produceString();
}

String getMangledName(ASTBuilder* astBuilder, DeclRefBase* declRef)
{
    SLANG_AST_BUILDER_RAII(astBuilder);

    return getMangledName(astBuilder, DeclRef<Decl>(declRef));
}

String getMangledName(ASTBuilder* astBuilder, Decl* decl)
{
    SLANG_AST_BUILDER_RAII(astBuilder);

    return getMangledName(astBuilder, makeDeclRef(decl));
}

String getMangledNameForConformanceWitness(
    ASTBuilder* astBuilder,
    DeclRef<Decl> sub,
    DeclRef<Decl> sup)
{
    SLANG_AST_BUILDER_RAII(astBuilder);
    ManglingContext context(astBuilder);
    emitRaw(&context, "_SW");
    emitQualifiedName(&context, sub, true);
    emitQualifiedName(&context, sup, true);
    return context.sb.produceString();
}

String getMangledNameForConformanceWitness(ASTBuilder* astBuilder, DeclRef<Decl> sub, Type* sup)
{
    SLANG_AST_BUILDER_RAII(astBuilder);
    // The mangled form for a witness that `sub`
    // conforms to `sup` will be named:
    //
    //     {Conforms(sub,sup)} => _SW{sub}{sup}
    //
    ManglingContext context(astBuilder);
    emitRaw(&context, "_SW");
    emitQualifiedName(&context, sub, true);
    emitType(&context, sup);
    return context.sb.produceString();
}

String getMangledNameForConformanceWitness(ASTBuilder* astBuilder, Type* sub, Type* sup)
{
    SLANG_AST_BUILDER_RAII(astBuilder);
    // The mangled form for a witness that `sub`
    // conforms to `sup` will be named:
    //
    //     {Conforms(sub,sup)} => _SW{sub}{sup}
    //
    ManglingContext context(astBuilder);
    emitRaw(&context, "_SW");
    emitType(&context, sub);
    emitType(&context, sup);
    return context.sb.produceString();
}

// This function takes an additional parameter to get a simplified
// mangled name when the witness-table is for enum-type.
//
// In order to deduplicate the witness-tables, we need to apply a little different
// rule for the mangled name when the `superType` is `enum` type.
// All witness-table for enum types whose underlying type is same should get the same
// manged name.
//
// TODO: We should remove this function and have a new IR for enum-type. The "option 2"
// described on the issue 6364 is more proper and ideal solution for the issue.
//
String getMangledNameForConformanceWitness(ASTBuilder* astBuilder, Type* sub, Type* sup, IROp subOp)
{
    SLANG_AST_BUILDER_RAII(astBuilder);

    ManglingContext context(astBuilder);
    emitRaw(&context, "_SW");

    if (as<EnumTypeType>(sup))
    {
        emitRaw(&context, getIROpInfo(subOp).name);
    }
    else
    {
        emitType(&context, sub);
    }

    emitType(&context, sup);
    return context.sb.produceString();
}

String getMangledTypeName(ASTBuilder* astBuilder, Type* type)
{
    SLANG_AST_BUILDER_RAII(astBuilder);
    ManglingContext context(astBuilder);
    emitRaw(&context, "_ST");
    emitType(&context, type);
    return context.sb.produceString();
}

String getMangledNameFromNameString(const UnownedStringSlice& name)
{
    ManglingContext context(nullptr);
    emitNameImpl(&context, name);
    return context.sb.produceString();
}

String getHashedName(const UnownedStringSlice& mangledName)
{
    StableHashCode64 hash = getStableHashCode64(mangledName.begin(), mangledName.getLength());

    StringBuilder builder;
    builder << "_Sh";
    builder.append(uint64_t(hash), 16);

    return builder;
}

} // namespace Slang
