/*
 * Copyright (C) 2005 Frerich Raabe <raabe@kde.org>
 * Copyright (C) 2006-2025 Apple Inc. All rights reserved.
 * Copyright (C) 2019 Google Inc. All rights reserved.
 * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "XPathFunctions.h"

#include "Attribute.h"
#include "ElementInlines.h"
#include "ProcessingInstruction.h"
#include "TreeScope.h"
#include "XMLNames.h"
#include "XPathUtil.h"
#include <wtf/MathExtras.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/RobinHoodHashMap.h>
#include <wtf/SetForScope.h>
#include <wtf/TZoneMallocInlines.h>
#include <wtf/text/MakeString.h>
#include <wtf/text/StringBuilder.h>

namespace WebCore::XPath {

WTF_MAKE_TZONE_ALLOCATED_IMPL(Function);

static inline bool isWhitespace(char16_t c)
{
    return c == ' ' || c == '\n' || c == '\r' || c == '\t';
}

#define DEFINE_FUNCTION_CREATOR(Suffix) static std::unique_ptr<Function> createFunction##Suffix() { return makeUnique<Fun##Suffix>(); }

class Interval {
public:
    static const int Inf = -1;

    Interval();
    Interval(int value);
    Interval(int min, int max);

    bool contains(int value) const;

private:
    int m_min;
    int m_max;
};

class FunLast final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunLast);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Number; }
public:
    FunLast() { setIsContextSizeSensitive(true); }
};

class FunPosition final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunPosition);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Number; }
public:
    FunPosition() { setIsContextPositionSensitive(true); }
};

class FunCount final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunCount);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Number; }
};

class FunId final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunId);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::NodeSet; }
};

class FunLocalName final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunLocalName);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::String; }
public:
    FunLocalName() { setIsContextNodeSensitive(true); } // local-name() with no arguments uses context node. 
};

class FunNamespaceURI final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunNamespaceURI);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::String; }
public:
    FunNamespaceURI() { setIsContextNodeSensitive(true); } // namespace-uri() with no arguments uses context node. 
};

class FunName final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunName);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::String; }
public:
    FunName() { setIsContextNodeSensitive(true); } // name() with no arguments uses context node. 
};

class FunString final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunString);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::String; }
public:
    FunString() { setIsContextNodeSensitive(true); } // string() with no arguments uses context node. 
};

class FunConcat final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunConcat);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::String; }
};

class FunStartsWith final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunStartsWith);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Boolean; }
};

class FunContains final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunContains);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Boolean; }
};

class FunSubstringBefore final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunSubstringBefore);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::String; }
};

class FunSubstringAfter final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunSubstringAfter);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::String; }
};

class FunSubstring final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunSubstring);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::String; }
};

class FunStringLength final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunStringLength);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Number; }
public:
    FunStringLength() { setIsContextNodeSensitive(true); } // string-length() with no arguments uses context node. 
};

class FunNormalizeSpace final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunNormalizeSpace);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::String; }
public:
    FunNormalizeSpace() { setIsContextNodeSensitive(true); } // normalize-space() with no arguments uses context node. 
};

class FunTranslate final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunTranslate);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::String; }
};

class FunBoolean final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunBoolean);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Boolean; }
};

class FunNot : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunNot);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Boolean; }
};

class FunTrue final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunTrue);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Boolean; }
};

class FunFalse final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunFalse);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Boolean; }
};

class FunLang final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunLang);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Boolean; }
public:
    FunLang() { setIsContextNodeSensitive(true); } // lang() always works on context node. 
};

class FunNumber final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunNumber);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Number; }
public:
    FunNumber() { setIsContextNodeSensitive(true); } // number() with no arguments uses context node. 
};

class FunSum final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunSum);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Number; }
};

class FunFloor final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunFloor);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Number; }
};

class FunCeiling final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunCeiling);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Number; }
};

class FunRound final : public Function {
    WTF_MAKE_TZONE_ALLOCATED_INLINE(FunRound);
    Value evaluate() const override;
    Value::Type resultType() const override { return Value::Type::Number; }
public:
    static double round(double);
};

DEFINE_FUNCTION_CREATOR(Last)
DEFINE_FUNCTION_CREATOR(Position)
DEFINE_FUNCTION_CREATOR(Count)
DEFINE_FUNCTION_CREATOR(Id)
DEFINE_FUNCTION_CREATOR(LocalName)
DEFINE_FUNCTION_CREATOR(NamespaceURI)
DEFINE_FUNCTION_CREATOR(Name)

DEFINE_FUNCTION_CREATOR(String)
DEFINE_FUNCTION_CREATOR(Concat)
DEFINE_FUNCTION_CREATOR(StartsWith)
DEFINE_FUNCTION_CREATOR(Contains)
DEFINE_FUNCTION_CREATOR(SubstringBefore)
DEFINE_FUNCTION_CREATOR(SubstringAfter)
DEFINE_FUNCTION_CREATOR(Substring)
DEFINE_FUNCTION_CREATOR(StringLength)
DEFINE_FUNCTION_CREATOR(NormalizeSpace)
DEFINE_FUNCTION_CREATOR(Translate)

DEFINE_FUNCTION_CREATOR(Boolean)
DEFINE_FUNCTION_CREATOR(Not)
DEFINE_FUNCTION_CREATOR(True)
DEFINE_FUNCTION_CREATOR(False)
DEFINE_FUNCTION_CREATOR(Lang)

DEFINE_FUNCTION_CREATOR(Number)
DEFINE_FUNCTION_CREATOR(Sum)
DEFINE_FUNCTION_CREATOR(Floor)
DEFINE_FUNCTION_CREATOR(Ceiling)
DEFINE_FUNCTION_CREATOR(Round)

#undef DEFINE_FUNCTION_CREATOR

inline Interval::Interval()
    : m_min(Inf), m_max(Inf)
{
}

inline Interval::Interval(int value)
    : m_min(value), m_max(value)
{
}

inline Interval::Interval(int min, int max)
    : m_min(min), m_max(max)
{
}

inline bool Interval::contains(int value) const
{
    if (m_min == Inf && m_max == Inf)
        return true;

    if (m_min == Inf)
        return value <= m_max;

    if (m_max == Inf)
        return value >= m_min;

    return value >= m_min && value <= m_max;
}

void Function::setArguments(const String& name, Vector<std::unique_ptr<Expression>> arguments)
{
    ASSERT(!subexpressionCount());

    // Functions that use the context node as an implicit argument are context node sensitive when they
    // have no arguments, but when explicit arguments are added, they are no longer context node sensitive.
    // As of this writing, the only exception to this is the "lang" function.
    if (name != "lang"_s && !arguments.isEmpty())
        setIsContextNodeSensitive(false);

    setSubexpressions(WTF::move(arguments));
}

Value FunLast::evaluate() const
{
    return Expression::evaluationContext().size;
}

Value FunPosition::evaluate() const
{
    return Expression::evaluationContext().position;
}

Value FunId::evaluate() const
{
    Value a = argument(0).evaluate();
    StringBuilder idList; // A whitespace-separated list of IDs

    if (!a.isNodeSet())
        idList.append(a.toString());
    else {
        for (auto& node : a.toNodeSet()) {
            idList.append(stringValue(node.get()));
            idList.append(' ');
        }
    }
    
    Ref contextScope = evaluationContext().node->treeScope();
    NodeSet result;
    HashSet<Ref<Node>> resultSet;

    unsigned startPos = 0;
    unsigned length = idList.length();
    while (true) {
        while (startPos < length && isWhitespace(idList[startPos]))
            ++startPos;
        
        if (startPos == length)
            break;

        size_t endPos = startPos;
        while (endPos < length && !isWhitespace(idList[endPos]))
            ++endPos;

        // If there are several nodes with the same id, id() should return the first one.
        // In WebKit, getElementById behaves so, too, although its behavior in this case is formally undefined.
        RefPtr node = contextScope->getElementById(StringView(idList).substring(startPos, endPos - startPos));
        if (node && resultSet.add(*node).isNewEntry)
            result.append(node.releaseNonNull());
        
        startPos = endPos;
    }
    
    result.markSorted(false);
    
    return Value(WTF::move(result));
}

static inline String expandedNameLocalPart(Node& node)
{
    if (auto* pi = dynamicDowncast<ProcessingInstruction>(node))
        return pi->target();
    return node.localName().string();
}

static inline String expandedName(Node& node)
{
    auto& prefix = node.prefix();
    return prefix.isEmpty() ? expandedNameLocalPart(node) : makeString(prefix, ':', expandedNameLocalPart(node));
}

Value FunLocalName::evaluate() const
{
    if (argumentCount() > 0) {
        Value a = argument(0).evaluate();
        if (!a.isNodeSet())
            return emptyString();

        RefPtr node = a.toNodeSet().firstNode();
        return node ? expandedNameLocalPart(*node) : emptyString();
    }

    return expandedNameLocalPart(*evaluationContext().protectedNode());
}

Value FunNamespaceURI::evaluate() const
{
    if (argumentCount() > 0) {
        Value a = argument(0).evaluate();
        if (!a.isNodeSet())
            return emptyString();

        RefPtr node = a.toNodeSet().firstNode();
        return node ? node->namespaceURI().string() : emptyString();
    }

    return evaluationContext().protectedNode()->namespaceURI().string();
}

Value FunName::evaluate() const
{
    if (argumentCount() > 0) {
        Value a = argument(0).evaluate();
        if (!a.isNodeSet())
            return emptyString();

        RefPtr node = a.toNodeSet().firstNode();
        return node ? expandedName(*node) : emptyString();
    }

    return expandedName(*evaluationContext().protectedNode());
}

Value FunCount::evaluate() const
{
    Value a = argument(0).evaluate();
    
    return double(a.toNodeSet().size());
}

Value FunString::evaluate() const
{
    if (!argumentCount())
        return Value(NodeSet(*Expression::evaluationContext().node)).toString();
    return argument(0).evaluate().toString();
}

Value FunConcat::evaluate() const
{
    StringBuilder result;
    result.reserveCapacity(1024);

    for (unsigned i = 0, count = argumentCount(); i < count; ++i) {
        EvaluationContext clonedContext(Expression::evaluationContext());
        SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext);
        result.append(argument(i).evaluate().toString());
    }

    return result.toString();
}

Value FunStartsWith::evaluate() const
{
    auto clonedContext = Expression::evaluationContext();

    String s1 = argument(0).evaluate().toString();
    String s2;
    {
        SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext);
        s2 = argument(1).evaluate().toString();
    }

    if (s2.isEmpty())
        return true;

    return s1.startsWith(s2);
}

Value FunContains::evaluate() const
{
    auto clonedContext = Expression::evaluationContext();

    String s1 = argument(0).evaluate().toString();
    String s2;
    {
        SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext);
        s2 = argument(1).evaluate().toString();
    }

    if (s2.isEmpty()) 
        return true;

    return s1.contains(s2) != 0;
}

Value FunSubstringBefore::evaluate() const
{
    auto clonedContext = Expression::evaluationContext();

    String s1 = argument(0).evaluate().toString();
    String s2;

    {
        SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext);
        s2 = argument(1).evaluate().toString();
    }

    if (s2.isEmpty())
        return emptyString();

    size_t i = s1.find(s2);

    if (i == notFound)
        return emptyString();

    return s1.left(i);
}

Value FunSubstringAfter::evaluate() const
{
    auto clonedContext = Expression::evaluationContext();

    String s1 = argument(0).evaluate().toString();
    String s2;

    {
        SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext);
        s2 = argument(1).evaluate().toString();
    }

    size_t i = s1.find(s2);
    if (i == notFound)
        return emptyString();

    return s1.substring(i + s2.length());
}

// Computes the 1-based start and end (exclusive) string indices for
// substring. This is all the positions [1, maxLen (inclusive)] where
// start <= position < start + len
static std::pair<unsigned, unsigned> computeSubstringStartEnd(double start, double len, double maxLen)
{
    ASSERT(std::isfinite(maxLen));
    const double end = start + len;
    if (std::isnan(start) || std::isnan(end))
        return std::make_pair(1, 1);

    // Neither start nor end are NaN, but may still be +/- Inf
    const double clampedStart = std::clamp<double>(start, 1, maxLen + 1);
    const double clampedEnd = std::clamp<double>(end, clampedStart, maxLen + 1);
    return std::make_pair(static_cast<unsigned>(clampedStart), static_cast<unsigned>(clampedEnd));
}

// substring(string, number pos, number? len)
//
// Characters in string are indexed from 1. Numbers are doubles and
// substring is specified to work with IEEE-754 infinity, NaN, and
// XPath's bespoke rounding function, round.
//
// <https://www.w3.org/TR/xpath/#function-substring>
Value FunSubstring::evaluate() const
{
    EvaluationContext clonedContext1(Expression::evaluationContext());
    EvaluationContext clonedContext2(Expression::evaluationContext());
    EvaluationContext clonedContext3(Expression::evaluationContext());

    String sourceString;
    double pos;
    double len;

    {
        SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext1);
        sourceString = argument(0).evaluate().toString();
    }
    {
        SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext2);
        pos = FunRound::round(argument(1).evaluate().toNumber());
    }

    if (argumentCount() == 3) {
        SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext3);
        len = FunRound::round(argument(2).evaluate().toNumber());
    } else {
        len = std::numeric_limits<double>::infinity();
    }

    const auto bounds = computeSubstringStartEnd(pos, len, sourceString.length());
    if (bounds.second <= bounds.first)
        return emptyString();

    return sourceString.substring(bounds.first - 1, bounds.second - bounds.first);
}

Value FunStringLength::evaluate() const
{
    if (!argumentCount())
        return Value(NodeSet(*Expression::evaluationContext().node)).toString().length();
    return argument(0).evaluate().toString().length();
}

Value FunNormalizeSpace::evaluate() const
{
    // https://www.w3.org/TR/1999/REC-xpath-19991116/#function-normalize-space
    if (!argumentCount()) {
        String s = Value(NodeSet(*Expression::evaluationContext().node)).toString();
        return s.simplifyWhiteSpace(isASCIIWhitespaceWithoutFF<char16_t>);
    }
    String s = argument(0).evaluate().toString();
    return s.simplifyWhiteSpace(isASCIIWhitespaceWithoutFF<char16_t>);
}

Value FunTranslate::evaluate() const
{
    EvaluationContext clonedContext1(Expression::evaluationContext());
    EvaluationContext clonedContext2(Expression::evaluationContext());

    String s1 = argument(0).evaluate().toString();
    String s2, s3;
    {
        SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext1);
        s2 = argument(1).evaluate().toString();
    }
    {
        SetForScope<EvaluationContext> contextForScope(Expression::evaluationContext(), clonedContext2);
        s3 = argument(2).evaluate().toString();
    }

    StringBuilder result;

    for (unsigned i1 = 0; i1 < s1.length(); ++i1) {
        char16_t ch = s1[i1];
        size_t i2 = s2.find(ch);
        
        if (i2 == notFound)
            result.append(ch);
        else if (i2 < s3.length())
            result.append(s3[i2]);
    }

    return result.toString();
}

Value FunBoolean::evaluate() const
{
    return argument(0).evaluate().toBoolean();
}

Value FunNot::evaluate() const
{
    return !argument(0).evaluate().toBoolean();
}

Value FunTrue::evaluate() const
{
    return true;
}

Value FunLang::evaluate() const
{
    String lang = argument(0).evaluate().toString();

    const Attribute* languageAttribute = nullptr;
    RefPtr node = evaluationContext().node.get();
    while (node) {
        if (RefPtr element = dynamicDowncast<Element>(*node)) {
            if (element->hasAttributes())
                languageAttribute = element->findAttributeByName(XMLNames::langAttr);
        }
        if (languageAttribute)
            break;
        node = node->parentNode();
    }

    if (!languageAttribute)
        return false;

    String langValue = languageAttribute->value();
    while (true) {
        if (equalIgnoringASCIICase(langValue, lang))
            return true;

        // Remove suffixes one by one.
        size_t index = langValue.reverseFind('-');
        if (index == notFound)
            break;
        langValue = langValue.left(index);
    }

    return false;
}

Value FunFalse::evaluate() const
{
    return false;
}

Value FunNumber::evaluate() const
{
    if (!argumentCount())
        return Value(NodeSet(*Expression::evaluationContext().node)).toNumber();
    return argument(0).evaluate().toNumber();
}

Value FunSum::evaluate() const
{
    Value a = argument(0).evaluate();
    if (!a.isNodeSet())
        return 0.0;

    double sum = 0.0;
    const NodeSet& nodes = a.toNodeSet();
    // To be really compliant, we should sort the node-set, as floating point addition is not associative.
    // However, this is unlikely to ever become a practical issue, and sorting is slow.

    for (auto& node : nodes)
        sum += Value(stringValue(node.get())).toNumber();
    
    return sum;
}

Value FunFloor::evaluate() const
{
    return floor(argument(0).evaluate().toNumber());
}

Value FunCeiling::evaluate() const
{
    return ceil(argument(0).evaluate().toNumber());
}

double FunRound::round(double val)
{
    if (std::isfinite(val)) {
        if (std::signbit(val) && val >= -0.5)
            val *= 0; // negative zero
        else
            val = floor(val + 0.5);
    }
    return val;
}

Value FunRound::evaluate() const
{
    return round(argument(0).evaluate().toNumber());
}

struct FunctionMapValue {
    std::unique_ptr<Function> (*creationFunction)();
    Interval argumentCountInterval;
};

static MemoryCompactLookupOnlyRobinHoodHashMap<String, FunctionMapValue> createFunctionMap()
{
    struct FunctionMapping {
        ASCIILiteral name;
        FunctionMapValue function;
    };

    static const auto functions = std::to_array<FunctionMapping>({
        { "boolean"_s, { createFunctionBoolean, 1 } },
        { "ceiling"_s, { createFunctionCeiling, 1 } },
        { "concat"_s, { createFunctionConcat, Interval(2, Interval::Inf) } },
        { "contains"_s, { createFunctionContains, 2 } },
        { "count"_s, { createFunctionCount, 1 } },
        { "false"_s, { createFunctionFalse, 0 } },
        { "floor"_s, { createFunctionFloor, 1 } },
        { "id"_s, { createFunctionId, 1 } },
        { "lang"_s, { createFunctionLang, 1 } },
        { "last"_s, { createFunctionLast, 0 } },
        { "local-name"_s, { createFunctionLocalName, Interval(0, 1) } },
        { "name"_s, { createFunctionName, Interval(0, 1) } },
        { "namespace-uri"_s, { createFunctionNamespaceURI, Interval(0, 1) } },
        { "normalize-space"_s, { createFunctionNormalizeSpace, Interval(0, 1) } },
        { "not"_s, { createFunctionNot, 1 } },
        { "number"_s, { createFunctionNumber, Interval(0, 1) } },
        { "position"_s, { createFunctionPosition, 0 } },
        { "round"_s, { createFunctionRound, 1 } },
        { "starts-with"_s, { createFunctionStartsWith, 2 } },
        { "string"_s, { createFunctionString, Interval(0, 1) } },
        { "string-length"_s, { createFunctionStringLength, Interval(0, 1) } },
        { "substring"_s, { createFunctionSubstring, Interval(2, 3) } },
        { "substring-after"_s, { createFunctionSubstringAfter, 2 } },
        { "substring-before"_s, { createFunctionSubstringBefore, 2 } },
        { "sum"_s, { createFunctionSum, 1 } },
        { "translate"_s, { createFunctionTranslate, 3 } },
        { "true"_s, { createFunctionTrue, 0 } },
    });

    MemoryCompactLookupOnlyRobinHoodHashMap<String, FunctionMapValue> map;
    for (auto& function : functions)
        map.add(function.name, function.function);
    return map;
}

std::unique_ptr<Function> Function::create(const String& name, unsigned numArguments)
{
    static NeverDestroyed functionMap = createFunctionMap();

    auto it = functionMap.get().find(name);
    if (it == functionMap.get().end())
        return nullptr;

    if (!it->value.argumentCountInterval.contains(numArguments))
        return nullptr;

    return it->value.creationFunction();
}

std::unique_ptr<Function> Function::create(const String& name)
{
    return create(name, 0);
}

std::unique_ptr<Function> Function::create(const String& name, Vector<std::unique_ptr<Expression>> arguments)
{
    auto function = create(name, arguments.size());
    if (function)
        function->setArguments(name, WTF::move(arguments));
    return function;
}

} // namespace WebCore::XPath
