/*
 * Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
 * Copyright (c) 2020-2021, the SerenityOS developers.
 * Copyright (c) 2021-2026, Sam Atkins <sam@ladybird.org>
 * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
 * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
 * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
 * Copyright (c) 2024, Tommy van der Vorst <tommy@pixelspark.nl>
 * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
 * Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/Debug.h>
#include <LibGfx/DecodedImageFrame.h>
#include <LibURL/Parser.h>
#include <LibWeb/CSS/CSSFontFeatureValuesRule.h>
#include <LibWeb/CSS/CSSFunctionDeclarations.h>
#include <LibWeb/CSS/CSSMarginRule.h>
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/CSS/ContainerQuery.h>
#include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/MediaList.h>
#include <LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h>
#include <LibWeb/CSS/Parser/ErrorReporter.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/PropertyName.h>
#include <LibWeb/CSS/PropertyNameAndID.h>
#include <LibWeb/CSS/Sizing.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Dump.h>
#include <LibWeb/HTML/HTMLImageElement.h>

static void log_parse_error(SourceLocation const& location = SourceLocation::current())
{
    dbgln_if(CSS_PARSER_DEBUG, "Parse error (CSS) {}", location);
}

namespace Web::CSS::Parser {

ParsingParams::ParsingParams(ParsingMode mode)
    : mode(mode)
{
}

ParsingParams::ParsingParams(ValueParsingContext value_context)
    : value_context(Vector { move(value_context) })
{
}

ParsingParams::ParsingParams(JS::Realm& realm, ParsingMode mode)
    : realm(realm)
    , mode(mode)
{
}

ParsingParams::ParsingParams(JS::Realm& realm, IsUAStyleSheet is_ua_style_sheet)
    : realm(realm)
    , is_ua_style_sheet(is_ua_style_sheet)
{
}

ParsingParams::ParsingParams(DOM::Document const& document, ParsingMode mode)
    : realm(const_cast<JS::Realm&>(document.realm()))
    , document(&document)
    , mode(mode)
{
}

Parser Parser::create(ParsingParams const& context, StringView input, StringView encoding)
{
    auto tokens = Tokenizer::tokenize(input, encoding);
    return Parser { context, move(tokens) };
}

Parser::Parser(ParsingParams const& context, Vector<Token> tokens)
    : m_document(context.document)
    , m_realm(context.realm)
    , m_parsing_mode(context.mode)
    , m_is_ua_style_sheet(context.is_ua_style_sheet)
    , m_tokens(move(tokens))
    , m_token_stream(m_tokens)
    , m_value_context(move(context.value_context))
    , m_rule_context(move(context.rule_context))
    , m_declared_namespaces(move(context.declared_namespaces))
{
}

// https://drafts.csswg.org/css-syntax/#parse-stylesheet
template<typename T>
Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<T>& input, Optional<::URL::URL> location)
{
    // To parse a stylesheet from an input given an optional url location:

    // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
    // 2. Normalize input, and set input to the result.
    // NOTE: These are done automatically when creating the Parser.

    // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
    ParsedStyleSheet style_sheet;
    style_sheet.location = move(location);

    // 4. Consume a stylesheet’s contents from input, and set the stylesheet’s rules to the result.
    style_sheet.rules = consume_a_stylesheets_contents(input);

    // 5. Return the stylesheet.
    return style_sheet;
}

// https://drafts.csswg.org/css-syntax/#parse-a-stylesheets-contents
template<typename T>
Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<T>& input)
{
    // To parse a stylesheet’s contents from input:

    // 1. Normalize input, and set input to the result.
    // NOTE: This is done automatically when creating the Parser.

    // 2. Consume a stylesheet’s contents from input, and return the result.
    return consume_a_stylesheets_contents(input);
}

GC::RootVector<GC::Ref<CSSRule>> Parser::convert_rules(Vector<Rule> const& raw_rules)
{
    bool import_rules_valid = true;
    bool namespace_rules_valid = true;

    // Interpret all of the resulting top-level qualified rules as style rules, defined below.
    GC::RootVector<GC::Ref<CSSRule>> rules(realm().heap());
    for (auto const& raw_rule : raw_rules) {
        auto rule = convert_to_rule<CSSNestedDeclarations>(raw_rule, Nested::No);
        // If any style rule is invalid, or any at-rule is not recognized or is invalid according to its grammar or context, it’s a parse error.
        // Discard that rule.
        if (!rule) {
            log_parse_error();
            continue;
        }

        // "Any @import rules must precede all other valid at-rules and style rules in a style sheet
        // (ignoring @charset and @layer statement rules) and must not have any other valid at-rules
        // or style rules between it and previous @import rules, or else the @import rule is invalid."
        // https://drafts.csswg.org/css-cascade-5/#at-import
        //
        // "Any @namespace rules must follow all @charset and @import rules and precede all other
        // non-ignored at-rules and style rules in a style sheet.
        // ...
        // A syntactically invalid @namespace rule (whether malformed or misplaced) must be ignored."
        // https://drafts.csswg.org/css-namespaces/#syntax
        switch (rule->type()) {
        case CSSRule::Type::LayerStatement:
            break;
        case CSSRule::Type::Import:
            if (!import_rules_valid)
                continue;
            break;
        case CSSRule::Type::Namespace:
            import_rules_valid = false;

            if (!namespace_rules_valid)
                continue;

            m_declared_namespaces.set(as<CSSNamespaceRule>(*rule).prefix());
            break;
        default:
            import_rules_valid = false;
            namespace_rules_valid = false;
            break;
        }

        rules.append(*rule);
    }

    return rules;
}

GC::RootVector<GC::Ref<CSSRule>> Parser::parse_as_stylesheet_contents()
{
    return convert_rules(parse_a_stylesheets_contents(m_token_stream));
}

// https://drafts.csswg.org/css-syntax/#parse-a-css-stylesheet
GC::Ref<CSS::CSSStyleSheet> Parser::parse_as_css_stylesheet(Optional<::URL::URL> location, GC::Ptr<MediaList> media_list)
{
    // To parse a CSS stylesheet, first parse a stylesheet.
    auto const& style_sheet = parse_a_stylesheet(m_token_stream, location);

    auto rule_list = CSSRuleList::create(realm(), convert_rules(style_sheet.rules));
    if (!media_list)
        media_list = MediaList::create(realm(), {});
    return CSSStyleSheet::create(realm(), rule_list, *media_list, move(location));
}

RefPtr<Supports> Parser::parse_as_supports()
{
    return parse_a_supports(m_token_stream);
}

template<typename T>
RefPtr<Supports> Parser::parse_a_supports(TokenStream<T>& tokens)
{
    auto transaction = tokens.begin_transaction();
    auto component_values = parse_a_list_of_component_values(tokens);
    TokenStream<ComponentValue> token_stream { component_values };
    auto maybe_condition = parse_supports_condition(token_stream);
    token_stream.discard_whitespace();
    if (maybe_condition && !token_stream.has_next_token()) {
        transaction.commit();
        return Supports::create(maybe_condition.release_nonnull());
    }

    return {};
}

// https://drafts.csswg.org/css-values-5/#typedef-boolean-expr
OwnPtr<BooleanExpression> Parser::parse_boolean_expression(TokenStream<ComponentValue>& tokens, MatchResult result_for_general_enclosed, ParseTest parse_test)
{
    // <boolean-expr[ <test> ]> = not <boolean-expr-group> | <boolean-expr-group>
    //                            [ [ and <boolean-expr-group> ]*
    //                            | [ or <boolean-expr-group> ]* ]

    auto transaction = tokens.begin_transaction();
    tokens.discard_whitespace();

    auto const& peeked_token = tokens.next_token();
    // `not <boolean-expr-group>`
    if (peeked_token.is_ident("not"sv)) {
        tokens.discard_a_token();
        tokens.discard_whitespace();

        if (auto child = parse_boolean_expression_group(tokens, result_for_general_enclosed, parse_test)) {
            tokens.discard_whitespace();
            transaction.commit();
            return BooleanNotExpression::create(child.release_nonnull());
        }
        return {};
    }

    // `<boolean-expr-group>
    //   [ [ and <boolean-expr-group> ]*
    //   | [ or <boolean-expr-group> ]* ]`
    Vector<NonnullOwnPtr<BooleanExpression>> children;
    enum class Combinator : u8 {
        And,
        Or,
    };
    Optional<Combinator> combinator;
    auto as_combinator = [](auto& token) -> Optional<Combinator> {
        if (!token.is(Token::Type::Ident))
            return {};
        auto ident = token.token().ident();
        if (ident.equals_ignoring_ascii_case("and"sv))
            return Combinator::And;
        if (ident.equals_ignoring_ascii_case("or"sv))
            return Combinator::Or;
        return {};
    };

    while (tokens.has_next_token()) {
        if (!children.is_empty()) {
            // Expect `and` or `or` here
            auto maybe_combinator = as_combinator(tokens.consume_a_token());
            if (!maybe_combinator.has_value())
                return {};
            if (!combinator.has_value()) {
                combinator = maybe_combinator.value();
            } else if (maybe_combinator != combinator) {
                return {};
            }
        }

        tokens.discard_whitespace();

        if (auto child = parse_boolean_expression_group(tokens, result_for_general_enclosed, parse_test)) {
            children.append(child.release_nonnull());
        } else {
            return {};
        }

        tokens.discard_whitespace();
    }

    if (children.is_empty())
        return {};

    transaction.commit();
    if (children.size() == 1)
        return children.take_first();

    VERIFY(combinator.has_value());
    switch (*combinator) {
    case Combinator::And:
        return BooleanAndExpression::create(move(children));
    case Combinator::Or:
        return BooleanOrExpression::create(move(children));
    }
    VERIFY_NOT_REACHED();
}

OwnPtr<BooleanExpression> Parser::parse_boolean_expression_group(TokenStream<ComponentValue>& tokens, MatchResult result_for_general_enclosed, ParseTest parse_test)
{
    // <boolean-expr-group> = <test> | ( <boolean-expr[ <test> ]> ) | <general-enclosed>

    // `( <boolean-expr[ <test> ]> )`
    auto const& first_token = tokens.next_token();
    if (first_token.is_block() && first_token.block().is_paren()) {
        auto transaction = tokens.begin_transaction();
        tokens.discard_a_token();
        tokens.discard_whitespace();

        TokenStream child_tokens { first_token.block().value };
        if (auto expression = parse_boolean_expression(child_tokens, result_for_general_enclosed, parse_test)) {
            if (child_tokens.has_next_token())
                return {};
            transaction.commit();
            return BooleanExpressionInParens::create(expression.release_nonnull());
        }
    }

    // `<test>`
    if (auto test = parse_test(tokens))
        return test.release_nonnull();

    // `<general-enclosed>`
    if (auto general_enclosed = parse_general_enclosed(tokens, result_for_general_enclosed))
        return general_enclosed.release_nonnull();

    return {};
}

// https://drafts.csswg.org/css-conditional-3/#typedef-supports-condition
OwnPtr<BooleanExpression> Parser::parse_supports_condition(TokenStream<ComponentValue>& tokens)
{
    m_rule_context.append(RuleContext::SupportsCondition);
    auto maybe_condition = parse_boolean_expression(tokens, MatchResult::False, [this](auto& tokens) { return parse_supports_feature(tokens); });
    m_rule_context.take_last();

    return maybe_condition;
}

// https://drafts.csswg.org/css-conditional-5/#typedef-supports-feature
OwnPtr<BooleanExpression> Parser::parse_supports_feature(TokenStream<ComponentValue>& tokens)
{
    // <supports-feature> = <supports-selector-fn> | <supports-font-tech-fn>
    //                    | <supports-font-format-fn> | <supports-env-fn>
    //                    | <supports-decl>
    auto transaction = tokens.begin_transaction();
    tokens.discard_whitespace();
    auto const& first_token = tokens.consume_a_token();

    // `<supports-decl> = ( <declaration> )`
    if (first_token.is_block() && first_token.block().is_paren()) {
        TokenStream block_tokens { first_token.block().value };
        if (auto declaration = parse_supports_declaration(block_tokens)) {
            transaction.commit();
            return BooleanExpressionInParens::create(declaration.release_nonnull<BooleanExpression>());
        }
    }

    // `<supports-selector-fn> = selector( <complex-selector> )`
    if (first_token.is_function("selector"sv)) {
        // FIXME: Parsing and then converting back to a string is weird.
        StringBuilder builder;
        for (auto const& item : first_token.function().value)
            builder.append(item.to_string());
        transaction.commit();
        TokenStream selector_tokens { first_token.function().value };
        auto maybe_selector = parse_complex_selector(selector_tokens, SelectorType::Standalone);
        // A CSS processor is considered to support a CSS selector if it accepts that all aspects of that selector,
        // recursively, (rather than considering any of its syntax to be unknown or invalid) and that selector doesn’t
        // contain unknown -webkit- pseudo-elements.
        // https://drafts.csswg.org/css-conditional-4/#dfn-support-selector
        bool matches = !maybe_selector.is_error() && !maybe_selector.value()->contains_unknown_webkit_pseudo_element();
        return Supports::Selector::create(builder.to_string_without_validation(), matches);
    }

    // `<supports-font-tech-fn> = font-tech( <font-tech> )`
    if (first_token.is_function("font-tech"sv)) {
        TokenStream tech_tokens { first_token.function().value };
        tech_tokens.discard_whitespace();
        auto tech_token = tech_tokens.consume_a_token();
        tech_tokens.discard_whitespace();
        if (tech_tokens.has_next_token() || !tech_token.is(Token::Type::Ident))
            return {};

        transaction.commit();
        auto tech_name = tech_token.token().ident();
        bool matches = font_tech_is_supported(tech_name);
        return Supports::FontTech::create(move(tech_name), matches);
    }

    // `<supports-font-format-fn> = font-format( <font-format> )`
    if (first_token.is_function("font-format"sv)) {
        TokenStream format_tokens { first_token.function().value };
        format_tokens.discard_whitespace();
        auto format_token = format_tokens.consume_a_token();
        format_tokens.discard_whitespace();
        if (format_tokens.has_next_token() || !format_token.is(Token::Type::Ident))
            return {};

        transaction.commit();
        auto format_name = format_token.token().ident();
        bool matches = font_format_is_supported(format_name);
        return Supports::FontFormat::create(move(format_name), matches);
    }

    // `<supports-env-fn> = env( <ident> )`
    if (first_token.is_function("env"sv)) {
        TokenStream format_tokens { first_token.function().value };
        format_tokens.discard_whitespace();
        auto variable_token = format_tokens.consume_a_token();
        format_tokens.discard_whitespace();
        if (format_tokens.has_next_token() || !variable_token.is(Token::Type::Ident))
            return {};

        transaction.commit();
        auto variable_name = variable_token.token().ident();
        // https://drafts.csswg.org/css-conditional-5/#support-definition-env
        // A CSS processor is considered to support an environment variable if the <ident> is a supported environment
        // variable.
        bool matches = environment_variable_from_string(variable_name).has_value();
        return Supports::FontFormat::create(move(variable_name), matches);
    }

    return {};
}

// https://drafts.csswg.org/css-conditional-5/#typedef-supports-decl
OwnPtr<Supports::Declaration> Parser::parse_supports_declaration(TokenStream<ComponentValue>& tokens)
{
    // `<supports-decl> = ( <declaration> )`
    // NB: Here, we only care about the <declaration> part.
    auto transaction = tokens.begin_transaction();
    tokens.discard_whitespace();
    if (auto declaration = consume_a_declaration(tokens, Nested::No, SaveOriginalText::Yes); declaration.has_value()) {
        tokens.discard_whitespace();
        if (!tokens.has_next_token()) {
            transaction.commit();
            return Supports::Declaration::create(declaration->original_full_text.release_value(), convert_to_style_property(*declaration).has_value());
        }
    }
    return {};
}

OwnPtr<BooleanExpression> Parser::parse_container_query_condition(TokenStream<ComponentValue>& tokens)
{
    return parse_boolean_expression(tokens, MatchResult::False, [this](auto& tokens) {
        return parse_container_query_feature(tokens);
    });
}

OwnPtr<BooleanExpression> Parser::parse_container_query_feature(TokenStream<ComponentValue>&)
{
    // https://drafts.csswg.org/css-conditional-5/#typedef-query-in-parens
    // <query-in-parens> = ( <container-query> )
    //                   | ( <size-feature> )
    //                   | style( <style-query> )
    //                   | scroll-state( <scroll-state-query> )
    //                   | <general-enclosed>

    // https://drafts.csswg.org/css-anchor-position-2/#container-rule-anchored
    // <query-in-parens> = ...
    //                   | anchored( <anchored-query> )

    // NB: Spec isn't yet in terms of `<boolean-condition>`, so this is the closest definition to what we want.
    //     `( <container-query> )` and `<general-enclosed>` are handled by parse_boolean_expression() already.

    // FIXME: `( <size-feature> )`
    // FIXME: `style( <style-query> )`
    // FIXME: `scroll-state( <scroll-state-query> )`
    // FIXME: `anchored( <anchored-query> )`
    return nullptr;
}

RefPtr<ContainerQuery> Parser::parse_container_query(TokenStream<ComponentValue>& tokens)
{
    if (auto condition = parse_container_query_condition(tokens))
        return ContainerQuery::create(condition.release_nonnull());
    return nullptr;
}

// https://drafts.csswg.org/mediaqueries-5/#typedef-general-enclosed
OwnPtr<GeneralEnclosed> Parser::parse_general_enclosed(TokenStream<ComponentValue>& tokens, MatchResult result)
{
    // <general-enclosed> = [ <function-token> <any-value>? ) ] | [ ( <any-value>? ) ]
    //
    // https://drafts.csswg.org/css-syntax-3/#typedef-any-value
    // "The <any-value> production is identical to <declaration-value>",
    // and <declaration-value> does not contain "<<bad-string-token>>,
    // <<bad-url-token>>, unmatched <<)-token>>, <<]-token>>, or
    // <<}-token>>".
    auto contains_only_any_value = [](auto const& values, auto&& contains_only_any_value) -> bool {
        for (auto const& value : values) {
            if (value.is_function()) {
                if (!contains_only_any_value(value.function().value, contains_only_any_value))
                    return false;
                continue;
            }

            if (value.is_block()) {
                if (!contains_only_any_value(value.block().value, contains_only_any_value))
                    return false;
                continue;
            }

            if (!value.is_token())
                continue;

            switch (value.token().type()) {
            case Token::Type::Invalid:
            case Token::Type::EndOfFile:
            case Token::Type::BadString:
            case Token::Type::BadUrl:
                // NB: Functions and blocks are emitted as component values, so any remaining bracket tokens are unmatched.
            case Token::Type::Function:
            case Token::Type::OpenCurly:
            case Token::Type::OpenParen:
            case Token::Type::OpenSquare:
            case Token::Type::CloseCurly:
            case Token::Type::CloseParen:
            case Token::Type::CloseSquare:
                return false;
            default:
                break;
            }
        }

        return true;
    };

    auto transaction = tokens.begin_transaction();
    tokens.discard_whitespace();
    auto const& first_token = tokens.consume_a_token();

    // `[ <function-token> <any-value>? ) ]`
    if (first_token.is_function()) {
        if (!contains_only_any_value(first_token.function().value, contains_only_any_value))
            return {};
        transaction.commit();
        return GeneralEnclosed::create(first_token.to_string(), result);
    }

    // `( <any-value>? )`
    if (first_token.is_block() && first_token.block().is_paren()) {
        if (!contains_only_any_value(first_token.block().value, contains_only_any_value))
            return {};
        transaction.commit();
        return GeneralEnclosed::create(first_token.to_string(), result);
    }

    return {};
}

// https://drafts.csswg.org/css-syntax/#consume-stylesheet-contents
template<typename T>
Vector<Rule> Parser::consume_a_stylesheets_contents(TokenStream<T>& input)
{
    // To consume a stylesheet’s contents from a token stream input:

    // Let rules be an initially empty list of rules.
    Vector<Rule> rules;

    // Process input:
    for (;;) {
        auto& token = input.next_token();

        // <whitespace-token>
        if (token.is(Token::Type::Whitespace)) {
            // Discard a token from input.
            input.discard_a_token();
            continue;
        }

        // <EOF-token>
        if (token.is(Token::Type::EndOfFile)) {
            // Return rules.
            return rules;
        }

        // <CDO-token>
        // <CDC-token>
        if (token.is(Token::Type::CDO) || token.is(Token::Type::CDC)) {
            // Discard a token from input.
            input.discard_a_token();
            continue;
        }

        // <at-keyword-token>
        if (token.is(Token::Type::AtKeyword)) {
            // Consume an at-rule from input. If anything is returned, append it to rules.
            if (auto maybe_at_rule = consume_an_at_rule(input); maybe_at_rule.has_value())
                rules.append(*maybe_at_rule);
            continue;
        }

        // anything else
        {
            // Consume a qualified rule from input. If a rule is returned, append it to rules.
            consume_a_qualified_rule(input).visit(
                [&](QualifiedRule qualified_rule) { rules.append(move(qualified_rule)); },
                [](auto&) {});
        }
    }
}

// https://drafts.csswg.org/css-syntax/#consume-at-rule
template<typename T>
Optional<AtRule> Parser::consume_an_at_rule(TokenStream<T>& input, Nested nested)
{
    // To consume an at-rule from a token stream input, given an optional bool nested (default false):

    // Assert: The next token is an <at-keyword-token>.
    VERIFY(input.next_token().is(Token::Type::AtKeyword));

    // Consume a token from input, and let rule be a new at-rule with its name set to the returned token’s value,
    // its prelude initially set to an empty list, and no declarations or child rules.
    AtRule rule {
        .name = ((Token)input.consume_a_token()).at_keyword(),
        .prelude = {},
        .child_rules_and_lists_of_declarations = {},
        .is_block_rule = false,
    };

    // Process input:
    for (;;) {
        auto& token = input.next_token();

        // <semicolon-token>
        // <EOF-token>
        if (token.is(Token::Type::Semicolon) || token.is(Token::Type::EndOfFile)) {
            // Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
            input.discard_a_token();
            if (is_valid_in_the_current_context(rule))
                return rule;
            return {};
        }

        // <}-token>
        if (token.is(Token::Type::CloseCurly)) {
            // If nested is true:
            if (nested == Nested::Yes) {
                // If rule is valid in the current context, return it.
                if (is_valid_in_the_current_context(rule))
                    return rule;
                // Otherwise, return nothing.
                return {};
            }
            // Otherwise, consume a token and append the result to rule’s prelude.
            else {
                rule.prelude.append(input.consume_a_token());
            }
            continue;
        }

        // <{-token>
        if (token.is(Token::Type::OpenCurly)) {
            // Consume a block from input, and assign the result to rule’s child rules.
            m_rule_context.append(rule_context_type_for_at_rule(rule.name));
            rule.child_rules_and_lists_of_declarations = consume_a_block(input);
            rule.is_block_rule = true;
            m_rule_context.take_last();

            // If rule is valid in the current context, return it. Otherwise, return nothing.
            if (is_valid_in_the_current_context(rule))
                return rule;
            return {};
        }

        // anything else
        {
            // Consume a component value from input and append the returned value to rule’s prelude.
            rule.prelude.append(consume_a_component_value(input));
        }
    }
}

// https://drafts.csswg.org/css-syntax/#consume-qualified-rule
template<typename T>
Variant<Empty, QualifiedRule, Parser::InvalidRuleError> Parser::consume_a_qualified_rule(TokenStream<T>& input, Optional<Token::Type> stop_token, Nested nested)
{
    // To consume a qualified rule, from a token stream input, given an optional token stop token and an optional bool nested (default false):

    // Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
    QualifiedRule rule {
        .prelude = {},
        .declarations = {},
        .child_rules = {},
    };

    // NOTE: Qualified rules inside @keyframes are a keyframe rule.
    //       We'll assume all others are style rules.
    auto type_of_qualified_rule = (!m_rule_context.is_empty() && m_rule_context.last() == RuleContext::AtKeyframes)
        ? RuleContext::Keyframe
        : RuleContext::Style;

    // Process input:
    for (;;) {
        auto& token = input.next_token();

        // <EOF-token>
        // stop token (if passed)
        if (token.is(Token::Type::EndOfFile) || (stop_token.has_value() && token.is(*stop_token))) {
            // This is a parse error. Return nothing.
            log_parse_error();
            return {};
        }

        // <}-token>
        if (token.is(Token::Type::CloseCurly)) {
            // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
            log_parse_error();
            if (nested == Nested::Yes)
                return {};
            rule.prelude.append(input.consume_a_token());
            continue;
        }

        // <{-token>
        if (token.is(Token::Type::OpenCurly)) {
            // If the first two non-<whitespace-token> values of rule’s prelude are an <ident-token> whose value starts with "--"
            // followed by a <colon-token>, then:
            TokenStream prelude_tokens { rule.prelude };
            prelude_tokens.discard_whitespace();
            auto& first_non_whitespace = prelude_tokens.consume_a_token();
            prelude_tokens.discard_whitespace();
            auto& second_non_whitespace = prelude_tokens.consume_a_token();
            if (first_non_whitespace.is(Token::Type::Ident) && first_non_whitespace.token().ident().starts_with_bytes("--"sv)
                && second_non_whitespace.is(Token::Type::Colon)) {
                // If nested is true, consume the remnants of a bad declaration from input, with nested set to true, and return nothing.
                if (nested == Nested::Yes) {
                    consume_the_remnants_of_a_bad_declaration(input, Nested::Yes);
                    return {};
                }

                // If nested is false, consume a block from input, and return nothing.
                (void)consume_a_block(input);
                return {};
            }

            // Otherwise, consume a block from input, and let child rules be the result.
            m_rule_context.append(type_of_qualified_rule);
            rule.child_rules = consume_a_block(input);
            m_rule_context.take_last();

            // If the first item of child rules is a list of declarations, remove it from child rules and assign it to rule’s declarations.
            if (!rule.child_rules.is_empty() && rule.child_rules.first().has<Vector<Declaration>>()) {
                auto first = rule.child_rules.take_first();
                rule.declarations = move(first.get<Vector<Declaration>>());
            }

            // If any remaining items of child rules are lists of declarations, replace them with nested declarations rules
            // containing the list as its sole child. Assign child rules to rule’s child rules.
            // NOTE: We do this later, when converting the QualifiedRule to a CSSRule type.

            // If rule is valid in the current context, return it; otherwise return an invalid rule error.
            if (is_valid_in_the_current_context(rule))
                return rule;
            return InvalidRuleError {};
        }

        // anything else
        {
            // Consume a component value from input and append the result to rule’s prelude.
            rule.prelude.append(consume_a_component_value(input));
        }
    }
}

// https://drafts.csswg.org/css-syntax/#consume-block
template<typename T>
Vector<RuleOrListOfDeclarations> Parser::consume_a_block(TokenStream<T>& input)
{
    // To consume a block, from a token stream input:

    // Assert: The next token is a <{-token>.
    VERIFY(input.next_token().is(Token::Type::OpenCurly));

    // Discard a token from input.
    input.discard_a_token();
    // Consume a block’s contents from input and let rules be the result.
    auto rules = consume_a_blocks_contents(input);
    // Discard a token from input.
    input.discard_a_token();

    // Return rules.
    return rules;
}

// https://drafts.csswg.org/css-syntax/#consume-block-contents
template<typename T>
Vector<RuleOrListOfDeclarations> Parser::consume_a_blocks_contents(TokenStream<T>& input)
{
    // To consume a block’s contents from a token stream input:

    // Let rules be an empty list, containing either rules or lists of declarations.
    Vector<RuleOrListOfDeclarations> rules;

    // Let decls be an empty list of declarations.
    Vector<Declaration> declarations;

    // Process input:
    for (;;) {
        auto& token = input.next_token();

        // <whitespace-token>
        // <semicolon-token>
        if (token.is(Token::Type::Whitespace) || token.is(Token::Type::Semicolon)) {
            // Discard a token from input.
            input.discard_a_token();
            continue;
        }

        // <EOF-token>
        // <}-token>
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseCurly)) {
            // AD-HOC: If decls is not empty, append it to rules.
            // Spec issue: https://github.com/w3c/csswg-drafts/issues/11017
            if (!declarations.is_empty())
                rules.append(move(declarations));
            // Return rules.
            return rules;
        }

        // <at-keyword-token>
        if (token.is(Token::Type::AtKeyword)) {
            // If decls is not empty, append it to rules, and set decls to a fresh empty list of declarations.
            if (!declarations.is_empty()) {
                rules.append(move(declarations));
                declarations = {};
            }

            // Consume an at-rule from input, with nested set to true.
            // If a rule was returned, append it to rules.
            if (auto at_rule = consume_an_at_rule(input, Nested::Yes); at_rule.has_value())
                rules.append({ at_rule.release_value() });

            continue;
        }

        // anything else
        {
            // OPTIMIZATION: Look ahead to determine if this can be a declaration (ident whitespace* ':').
            //               If not, skip straight to qualified rule parsing, avoiding the expensive
            //               mark/restore cycle and consume_the_remnants_of_a_bad_declaration.
            bool could_be_declaration = false;
            if (token.is(Token::Type::Ident)) {
                size_t lookahead = 1;
                while (input.peek_token(lookahead).is(Token::Type::Whitespace))
                    ++lookahead;
                could_be_declaration = input.peek_token(lookahead).is(Token::Type::Colon);
            }

            auto flush_declarations = [&] {
                if (!declarations.is_empty()) {
                    rules.append(move(declarations));
                    declarations = {};
                }
            };

            auto consume_qualified_rule = [&] {
                consume_a_qualified_rule(input, Token::Type::Semicolon, Nested::Yes).visit([](Empty&) {}, [&](InvalidRuleError&) { flush_declarations(); }, [&](QualifiedRule rule) {
                        flush_declarations();
                        rules.append({ move(rule) }); });
            };

            if (could_be_declaration) {
                // Mark input.
                input.mark();

                // Consume a declaration from input, with nested set to true.
                // If a declaration was returned, append it to decls, and discard a mark from input.
                if (auto declaration = consume_a_declaration(input, Nested::Yes); declaration.has_value()) {
                    declarations.append(declaration.release_value());
                    input.discard_a_mark();
                }

                // Otherwise, restore a mark from input, then consume a qualified rule from input,
                // with nested set to true, and <semicolon-token> as the stop token.
                else {
                    input.restore_a_mark();
                    consume_qualified_rule();
                }
            } else {
                // Not a declaration, go straight to qualified rule parsing.
                consume_qualified_rule();
            }
        }
    }
}

template<>
ComponentValue Parser::consume_a_component_value<ComponentValue>(TokenStream<ComponentValue>& tokens)
{
    // Note: This overload is called once tokens have already been converted into component values,
    //       so we do not need to do the work in the more general overload.
    return tokens.consume_a_token();
}

// 5.4.7. Consume a component value
// https://drafts.csswg.org/css-syntax/#consume-component-value
template<>
ComponentValue Parser::consume_a_component_value(TokenStream<Token>& input)
{
    // To consume a component value from a token stream input:

    // Process input:
    for (;;) {
        auto const& token = input.next_token();

        // <{-token>
        // <[-token>
        // <(-token>
        if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen)) {
            // Consume a simple block from input and return the result.
            return ComponentValue { consume_a_simple_block(input) };
        }

        // <function-token>
        if (token.is(Token::Type::Function)) {
            // Consume a function from input and return the result.
            return ComponentValue { consume_a_function(input) };
        }

        // anything else
        {
            // Consume a token from input and return the result.
            return ComponentValue { input.consume_a_token() };
        }
    }
}

template<>
void Parser::consume_a_component_value_and_do_nothing<ComponentValue>(TokenStream<ComponentValue>& tokens)
{
    // AD-HOC: To avoid unnecessary allocations, we explicitly define a "do nothing" variant that discards the result immediately.
    // Note: This overload is called once tokens have already been converted into component values,
    //       so we do not need to do the work in the more general overload.
    tokens.discard_a_token();
}

// 5.4.7. Consume a component value
// https://drafts.csswg.org/css-syntax/#consume-component-value
template<>
void Parser::consume_a_component_value_and_do_nothing(TokenStream<Token>& input)
{
    // AD-HOC: To avoid unnecessary allocations, we explicitly define a "do nothing" variant that discards the result immediately.
    // To consume a component value from a token stream input:

    // Process input:
    for (;;) {
        auto const& token = input.next_token();

        // <{-token>
        // <[-token>
        // <(-token>
        if (token.is(Token::Type::OpenCurly) || token.is(Token::Type::OpenSquare) || token.is(Token::Type::OpenParen)) {
            // Consume a simple block from input and return the result.
            consume_a_simple_block_and_do_nothing(input);
            return;
        }

        // <function-token>
        if (token.is(Token::Type::Function)) {
            // Consume a function from input and return the result.
            consume_a_function_and_do_nothing(input);
            return;
        }

        // anything else
        {
            // Consume a token from input and return the result.
            input.discard_a_token();
            return;
        }
    }
}

template<typename T>
Vector<ComponentValue> Parser::consume_a_list_of_component_values(TokenStream<T>& input, Optional<Token::Type> stop_token, Nested nested)
{
    // To consume a list of component values from a token stream input, given an optional token stop token
    // and an optional boolean nested (default false):

    // Let values be an empty list of component values.
    Vector<ComponentValue> values;

    // Process input:
    for (;;) {
        auto& token = input.next_token();

        // <eof-token>
        // stop token (if passed)
        if (token.is(Token::Type::EndOfFile) || (stop_token.has_value() && token.is(*stop_token))) {
            // Return values.
            return values;
        }

        // <}-token>
        if (token.is(Token::Type::CloseCurly)) {
            // If nested is true, return values.
            if (nested == Nested::Yes) {
                return values;
            }
            // Otherwise, this is a parse error. Consume a token from input and append the result to values.
            else {
                log_parse_error();
                values.append(input.consume_a_token());
            }
        }

        // anything else
        {
            // Consume a component value from input, and append the result to values.
            values.append(consume_a_component_value(input));
        }
    }
}

// https://drafts.csswg.org/css-syntax/#consume-simple-block
SimpleBlock Parser::consume_a_simple_block(TokenStream<Token>& input)
{
    // To consume a simple block from a token stream input:

    // Assert: the next token of input is <{-token>, <[-token>, or <(-token>.
    auto const& next = input.next_token();
    VERIFY(next.is(Token::Type::OpenCurly) || next.is(Token::Type::OpenSquare) || next.is(Token::Type::OpenParen));

    // Let ending token be the mirror variant of the next token. (E.g. if it was called with <[-token>, the ending token is <]-token>.)
    auto ending_token = input.next_token().mirror_variant();

    // Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.
    SimpleBlock block {
        .token = input.next_token(),
        .value = {},
    };

    // Discard a token from input.
    input.discard_a_token();

    // Process input:
    for (;;) {
        auto const& token = input.next_token();

        // <eof-token>
        // ending token
        if (token.is(Token::Type::EndOfFile) || token.is(ending_token)) {
            // Discard a token from input. Return block.
            // AD-HOC: Store the token instead as the "end token"
            block.end_token = input.consume_a_token();
            return block;
        }

        // anything else
        {
            // Consume a component value from input and append the result to block’s value.
            block.value.append(consume_a_component_value(input));
        }
    }
}

// https://drafts.csswg.org/css-syntax/#consume-simple-block
void Parser::consume_a_simple_block_and_do_nothing(TokenStream<Token>& input)
{
    // AD-HOC: To avoid unnecessary allocations, we explicitly define a "do nothing" variant that discards the result immediately.
    // To consume a simple block from a token stream input:

    // Assert: the next token of input is <{-token>, <[-token>, or <(-token>.
    auto const& next = input.next_token();
    VERIFY(next.is(Token::Type::OpenCurly) || next.is(Token::Type::OpenSquare) || next.is(Token::Type::OpenParen));

    // Let ending token be the mirror variant of the next token. (E.g. if it was called with <[-token>, the ending token is <]-token>.)
    auto ending_token = input.next_token().mirror_variant();

    // Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.

    // Discard a token from input.
    input.discard_a_token();

    // Process input:
    for (;;) {
        auto const& token = input.next_token();

        // <eof-token>
        // ending token
        if (token.is(Token::Type::EndOfFile) || token.is(ending_token)) {
            // Discard a token from input. Return block.
            input.discard_a_token();
            return;
        }

        // anything else
        {
            // Consume a component value from input and append the result to block’s value.
            consume_a_component_value_and_do_nothing(input);
        }
    }
}

// https://drafts.csswg.org/css-syntax/#consume-function
Function Parser::consume_a_function(TokenStream<Token>& input)
{
    // To consume a function from a token stream input:

    // Assert: The next token is a <function-token>.
    VERIFY(input.next_token().is(Token::Type::Function));

    // Consume a token from input, and let function be a new function with its name equal the returned token’s value,
    // and a value set to an empty list.
    auto name_token = ((Token)input.consume_a_token());
    Function function {
        .name = name_token.function(),
        .value = {},
        .name_token = name_token,
    };

    // Process input:
    for (;;) {
        auto const& token = input.next_token();

        // <eof-token>
        // <)-token>
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseParen)) {
            // Discard a token from input. Return function.
            // AD-HOC: Store the token instead as the "end token"
            function.end_token = input.consume_a_token();
            return function;
        }

        // anything else
        {
            // Consume a component value from input and append the result to function’s value.
            function.value.append(consume_a_component_value(input));
        }
    }
}

// https://drafts.csswg.org/css-syntax/#consume-function
void Parser::consume_a_function_and_do_nothing(TokenStream<Token>& input)
{
    // AD-HOC: To avoid unnecessary allocations, we explicitly define a "do nothing" variant that discards the result immediately.
    // To consume a function from a token stream input:

    // Assert: The next token is a <function-token>.
    VERIFY(input.next_token().is(Token::Type::Function));

    // Consume a token from input, and let function be a new function with its name equal the returned token’s value,
    // and a value set to an empty list.
    input.discard_a_token();

    // Process input:
    for (;;) {
        auto const& token = input.next_token();

        // <eof-token>
        // <)-token>
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::CloseParen)) {
            // Discard a token from input. Return function.
            input.discard_a_token();
            return;
        }

        // anything else
        {
            // Consume a component value from input and append the result to function’s value.
            consume_a_component_value_and_do_nothing(input);
        }
    }
}

// https://drafts.csswg.org/css-syntax/#consume-declaration
template<typename T>
Optional<Declaration> Parser::consume_a_declaration(TokenStream<T>& input, Nested nested, SaveOriginalText save_full_text)
{
    // To consume a declaration from a token stream input, given an optional bool nested (default false):

    // TODO: As noted in the "Implementation note" below https://drafts.csswg.org/css-syntax/#consume-block-contents
    //       there are ways we can optimise this by early-exiting.

    // Let decl be a new declaration, with an initially empty name and a value set to an empty list.
    Declaration declaration {
        .name {},
        .value {},
    };
    auto start_token_index = input.current_index();

    // 1. If the next token is an <ident-token>, consume a token from input and set decl’s name to the token’s value.
    if (input.next_token().is(Token::Type::Ident)) {
        declaration.name = ((Token)input.consume_a_token()).ident();
    }
    //    Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
    else {
        consume_the_remnants_of_a_bad_declaration(input, nested);
        return {};
    }

    // 2. Discard whitespace from input.
    input.discard_whitespace();

    // 3. If the next token is a <colon-token>, discard a token from input.
    if (input.next_token().is(Token::Type::Colon)) {
        input.discard_a_token();
    }
    //    Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
    else {
        consume_the_remnants_of_a_bad_declaration(input, nested);
        return {};
    }

    // 4. Discard whitespace from input.
    input.discard_whitespace();

    // 5. Consume a list of component values from input, with nested, and with <semicolon-token> as the stop token,
    //    and set decl’s value to the result.
    declaration.value = consume_a_list_of_component_values(input, Token::Type::Semicolon, nested);

    // 6. If the last two non-<whitespace-token>s in decl’s value are a <delim-token> with the value "!"
    //    followed by an <ident-token> with a value that is an ASCII case-insensitive match for "important",
    //    remove them from decl’s value and set decl’s important flag.
    if (declaration.value.size() >= 2) {
        // NOTE: Walk backwards from the end until we find "important"
        Optional<size_t> important_index;
        for (size_t i = declaration.value.size() - 1; i > 0; i--) {
            auto const& value = declaration.value[i];
            if (value.is_ident("important"sv)) {
                important_index = i;
                break;
            }
            if (!value.is(Token::Type::Whitespace))
                break;
        }

        // NOTE: Walk backwards from important until we find "!"
        if (important_index.has_value()) {
            Optional<size_t> bang_index;
            for (size_t i = important_index.value() - 1; i > 0; i--) {
                auto const& value = declaration.value[i];
                if (value.is_delim('!')) {
                    bang_index = i;
                    break;
                }
                if (value.is(Token::Type::Whitespace))
                    continue;
                break;
            }

            if (bang_index.has_value()) {
                declaration.value.remove(important_index.value());
                declaration.value.remove(bang_index.value());
                declaration.important = Important::Yes;
            }
        }
    }

    // 7. While the last item in decl’s value is a <whitespace-token>, remove that token.
    while (!declaration.value.is_empty() && declaration.value.last().is(Token::Type::Whitespace)) {
        declaration.value.take_last();
    }

    // See second clause of step 8.
    auto contains_a_curly_block_and_non_whitespace = [](Vector<ComponentValue> const& declaration_value) {
        bool contains_curly_block = false;
        bool contains_non_whitespace = false;
        for (auto const& value : declaration_value) {
            if (value.is_block() && value.block().is_curly()) {
                if (contains_non_whitespace)
                    return true;
                contains_curly_block = true;
                continue;
            }

            if (!value.is(Token::Type::Whitespace)) {
                if (contains_curly_block)
                    return true;
                contains_non_whitespace = true;
                continue;
            }
        }
        return false;
    };

    // 8. If decl’s name is a custom property name string, then set decl’s original text to the segment
    //    of the original source text string corresponding to the tokens of decl’s value.
    if (is_invalid_custom_property_name_string(declaration.name))
        return {};
    if (is_a_custom_property_name_string(declaration.name)) {
        // TODO: If we could reach inside the source string that the TokenStream uses, we could grab this as
        //       a single substring instead of having to reconstruct it.
        StringBuilder original_text;
        for (auto const& value : declaration.value) {
            original_text.append(value.original_source_text());
        }
        declaration.original_value_text = original_text.to_string_without_validation();
    }
    //    Otherwise, if decl’s value contains a top-level simple block with an associated token of <{-token>,
    //    and also contains any other non-<whitespace-token> value, return nothing.
    //    (That is, a top-level {}-block is only allowed as the entire value of a non-custom property.)
    else if (contains_a_curly_block_and_non_whitespace(declaration.value)) {
        return {};
    }
    //    Otherwise, if decl’s name is an ASCII case-insensitive match for "unicode-range", consume the value of
    //    a unicode-range descriptor from the segment of the original source text string corresponding to the
    //    tokens returned by the consume a list of component values call, and replace decl’s value with the result.
    else if (declaration.name.equals_ignoring_ascii_case("unicode-range"sv)) {
        // FIXME: Special unicode-range handling
    }

    // 9. If decl is valid in the current context, return it; otherwise return nothing.
    if (is_valid_in_the_current_context(declaration)) {
        // AD-HOC: Assemble source tokens.
        if (save_full_text == SaveOriginalText::Yes) {
            StringBuilder original_full_text;
            for (auto& token : input.tokens_since(start_token_index))
                original_full_text.append(token.to_string());

            declaration.original_full_text = original_full_text.to_string_without_validation();
        }
        return declaration;
    }
    return {};
}

// https://drafts.csswg.org/css-syntax/#consume-the-remnants-of-a-bad-declaration
template<typename T>
void Parser::consume_the_remnants_of_a_bad_declaration(TokenStream<T>& input, Nested nested)
{
    // To consume the remnants of a bad declaration from a token stream input, given a bool nested:

    // Process input:
    for (;;) {
        auto const& token = input.next_token();

        // <eof-token>
        // <semicolon-token>
        if (token.is(Token::Type::EndOfFile) || token.is(Token::Type::Semicolon)) {
            // Discard a token from input, and return nothing.
            input.discard_a_token();
            return;
        }

        // <}-token>
        if (token.is(Token::Type::CloseCurly)) {
            // If nested is true, return nothing. Otherwise, discard a token.
            if (nested == Nested::Yes)
                return;
            input.discard_a_token();
            continue;
        }

        // anything else
        {
            // Consume a component value from input, and do nothing.
            consume_a_component_value_and_do_nothing(input);
            continue;
        }
    }
}

CSSRule* Parser::parse_as_css_rule()
{
    if (auto maybe_rule = parse_a_rule(m_token_stream); maybe_rule.has_value())
        return convert_to_rule<CSSNestedDeclarations>(maybe_rule.value(), Nested::No);
    return {};
}

// https://drafts.csswg.org/css-syntax/#parse-rule
template<typename T>
Optional<Rule> Parser::parse_a_rule(TokenStream<T>& input)
{
    // To parse a rule from input:
    Optional<Rule> rule;

    // 1. Normalize input, and set input to the result.
    // NOTE: This is done when initializing the Parser.

    // 2. Discard whitespace from input.
    input.discard_whitespace();

    // 3. If the next token from input is an <EOF-token>, return a syntax error.
    if (input.next_token().is(Token::Type::EndOfFile)) {
        return {};
    }
    //    Otherwise, if the next token from input is an <at-keyword-token>,
    //    consume an at-rule from input, and let rule be the return value.
    else if (input.next_token().is(Token::Type::AtKeyword)) {
        rule = consume_an_at_rule(m_token_stream).map([](auto&& it) { return Rule { it }; });
    }
    //    Otherwise, consume a qualified rule from input and let rule be the return value.
    //    If nothing or an invalid rule error was returned, return a syntax error.
    else {
        consume_a_qualified_rule(input).visit(
            [&](QualifiedRule qualified_rule) { rule = move(qualified_rule); },
            [](auto&) {});

        if (!rule.has_value())
            return {};
    }

    // 4. Discard whitespace from input.
    input.discard_whitespace();

    // 5. If the next token from input is an <EOF-token>, return rule. Otherwise, return a syntax error.
    if (input.next_token().is(Token::Type::EndOfFile))
        return rule;
    return {};
}

// https://drafts.csswg.org/css-syntax/#parse-block-contents
template<typename T>
Vector<RuleOrListOfDeclarations> Parser::parse_a_blocks_contents(TokenStream<T>& input)
{
    // To parse a block’s contents from input:

    // 1. Normalize input, and set input to the result.
    // NOTE: Done by constructing the Parser.

    // 2. Consume a block’s contents from input, and return the result.
    return consume_a_blocks_contents(input);
}

Optional<StyleProperty> Parser::parse_as_supports_condition()
{
    m_rule_context.append(RuleContext::SupportsCondition);
    auto maybe_declaration = parse_a_declaration(m_token_stream);
    m_rule_context.take_last();
    if (maybe_declaration.has_value()) {
        if (auto maybe_property_and_name = convert_to_style_property(maybe_declaration.release_value()); maybe_property_and_name.has_value())
            return maybe_property_and_name->property;
    }
    return {};
}

// https://drafts.csswg.org/css-syntax/#parse-declaration
template<typename T>
Optional<Declaration> Parser::parse_a_declaration(TokenStream<T>& input)
{
    // To parse a declaration from input:

    // 1. Normalize input, and set input to the result.
    // Note: This is done when initializing the Parser.

    // 2. Discard whitespace from input.
    input.discard_whitespace();

    // 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
    if (auto declaration = consume_a_declaration(input); declaration.has_value())
        return declaration.release_value();
    // FIXME: Syntax error
    return {};
}

Optional<ComponentValue> Parser::parse_as_component_value()
{
    return parse_a_component_value(m_token_stream);
}

// https://drafts.csswg.org/css-syntax/#parse-component-value
template<typename T>
Optional<ComponentValue> Parser::parse_a_component_value(TokenStream<T>& input)
{
    // To parse a component value from input:

    // 1. Normalize input, and set input to the result.
    // Note: This is done when initializing the Parser.

    // 2. Discard whitespace from input.
    input.discard_whitespace();

    // 3. If input is empty, return a syntax error.
    // FIXME: Syntax error
    if (input.is_empty())
        return {};

    // 4. Consume a component value from input and let value be the return value.
    auto value = consume_a_component_value(input);

    // 5. Discard whitespace from input.
    input.discard_whitespace();

    // 6. If input is empty, return value. Otherwise, return a syntax error.
    if (input.is_empty())
        return move(value);
    // FIXME: Syntax error
    return {};
}

// https://drafts.csswg.org/css-syntax/#parse-list-of-component-values
template<typename T>
Vector<ComponentValue> Parser::parse_a_list_of_component_values(TokenStream<T>& input)
{
    // To parse a list of component values from input:

    // 1. Normalize input, and set input to the result.
    // Note: This is done when initializing the Parser.

    // 2. Consume a list of component values from input, and return the result.
    return consume_a_list_of_component_values(input);
}

// https://drafts.csswg.org/css-syntax/#parse-comma-separated-list-of-component-values
template<typename T>
Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<T>& input)
{
    // To parse a comma-separated list of component values from input:

    // 1. Normalize input, and set input to the result.
    // Note: This is done when initializing the Parser.

    // 2. Let groups be an empty list.
    Vector<Vector<ComponentValue>> groups;

    // 3. While input is not empty:
    bool just_consumed_comma = false;
    while (!input.is_empty()) {

        // 1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
        groups.append(consume_a_list_of_component_values(input, Token::Type::Comma));

        // 2. Discard a token from input.
        just_consumed_comma = input.consume_a_token().is(Token::Type::Comma);
    }

    // AD-HOC: Also append an empty group if there was a trailing comma.
    // Some related spec discussion: https://github.com/w3c/csswg-drafts/issues/11254
    if (just_consumed_comma)
        groups.append({});

    // 4. Return groups.
    return groups;
}

// https://drafts.csswg.org/cssom/#parse-a-css-declaration-block
Parser::PropertiesAndCustomProperties Parser::parse_as_property_declaration_block()
{
    auto expand_shorthands = [&](Vector<StyleProperty>& properties) -> Vector<StyleProperty> {
        Vector<StyleProperty> expanded_properties;
        for (auto& property : properties) {
            if (property_is_shorthand(property.property_id)) {
                StyleComputer::for_each_property_expanding_shorthands(property.property_id, *property.value, [&](PropertyID longhand_property_id, StyleValue const& longhand_value) {
                    expanded_properties.append(CSS::StyleProperty {
                        .important = property.important,
                        .property_id = longhand_property_id,
                        .value = longhand_value,
                    });
                });
            } else {
                expanded_properties.append(property);
            }
        }
        return expanded_properties;
    };

    // 1. Let declarations be the returned declarations from invoking parse a block’s contents with string.
    auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream);

    // 2. Let parsed declarations be a new empty list.
    PropertiesAndCustomProperties parsed_declarations;

    // 3. For each item declaration in declarations, follow these substeps:
    for (auto const& rule_or_list : declarations_and_at_rules) {
        if (rule_or_list.has<Rule>())
            continue;

        auto& rule_declarations = rule_or_list.get<Vector<Declaration>>();
        for (auto const& declaration : rule_declarations) {
            // 1. Let parsed declaration be the result of parsing declaration according to the appropriate CSS
            //    specifications, dropping parts that are said to be ignored. If the whole declaration is dropped, let
            //    parsed declaration be null.
            // 2. If parsed declaration is not null, append it to parsed declarations.
            extract_property(declaration, parsed_declarations);
        }
    }
    parsed_declarations.properties = expand_shorthands(parsed_declarations.properties);

    // 4. Return parsed declarations.
    return parsed_declarations;
}

// https://drafts.csswg.org/cssom/#parse-a-css-declaration-block
Vector<Descriptor> Parser::parse_as_descriptor_declaration_block(AtRuleID at_rule_id)
{
    auto context_type = [at_rule_id] {
        switch (at_rule_id) {
        case AtRuleID::FontFace:
            return RuleContext::AtFontFace;
        case AtRuleID::Function:
            return RuleContext::AtFunction;
        case AtRuleID::Page:
            return RuleContext::AtPage;
        case AtRuleID::Property:
            return RuleContext::AtProperty;
        case AtRuleID::CounterStyle:
            // NB: We don't actually have a `CSSDescriptors` for `@counter-style` so this function shouldn't ever be
            //     called with `AtRuleID::CounterStyle`.
            VERIFY_NOT_REACHED();
        }
        VERIFY_NOT_REACHED();
    }();

    // 1. Let declarations be the returned declarations from invoking parse a block’s contents with string.
    m_rule_context.append(context_type);
    auto declarations_and_at_rules = parse_a_blocks_contents(m_token_stream);
    m_rule_context.take_last();

    // 2. Let parsed declarations be a new empty list.
    Vector<Descriptor> parsed_declarations;

    // 3. For each item declaration in declarations, follow these substeps:
    for (auto const& rule_or_list : declarations_and_at_rules) {
        if (rule_or_list.has<Rule>())
            continue;

        auto& rule_declarations = rule_or_list.get<Vector<Declaration>>();
        for (auto const& declaration : rule_declarations) {
            // 1. Let parsed declaration be the result of parsing declaration according to the appropriate CSS
            //    specifications, dropping parts that are said to be ignored. If the whole declaration is dropped, let
            //    parsed declaration be null.
            // 2. If parsed declaration is not null, append it to parsed declarations.
            if (auto parsed_declaration = convert_to_descriptor(at_rule_id, declaration); parsed_declaration.has_value())
                parsed_declarations.append(parsed_declaration.release_value());
        }
    }

    // 4. Return parsed declarations.
    return parsed_declarations;
}

bool Parser::is_valid_in_the_current_context(Declaration const& declaration) const
{
    // TODO: Determine if this *particular* declaration is valid here, not just declarations in general.

    // Declarations can't appear at the top level
    if (m_rule_context.is_empty())
        return false;

    switch (m_rule_context.last()) {
    case RuleContext::Unknown:
        // If the context is an unknown type, we don't accept anything.
        return false;

    case RuleContext::Style:
        // Style rules contain property declarations
        return true;

    case RuleContext::Keyframe: {
        // https://drafts.csswg.org/css-animations-1/#keyframes
        // The <declaration-list> inside of <keyframe-block> accepts any CSS property except those defined in this
        // specification, but does accept the animation-timing-function property and interprets it specially
        // NB: animation-composition is defined in CSS Animations Level 2, so it is not excluded by this rule.
        auto property = PropertyNameAndID::from_name(declaration.name);
        if (!property.has_value())
            return true;
        switch (property->id()) {
        case PropertyID::Animation:
        case PropertyID::AnimationDelay:
        case PropertyID::AnimationDirection:
        case PropertyID::AnimationDuration:
        case PropertyID::AnimationFillMode:
        case PropertyID::AnimationIterationCount:
        case PropertyID::AnimationName:
        case PropertyID::AnimationPlayState:
        case PropertyID::AnimationTimeline:
            return false;
        default:
            return true;
        }
    }

    case RuleContext::AtContainer:
    case RuleContext::AtLayer:
    case RuleContext::AtMedia:
    case RuleContext::AtSupports:
        // Grouping rules can contain declarations if they are themselves inside a style or function rule
        return m_rule_context.contains([](auto const& context) { return context == RuleContext::Style || context == RuleContext::AtFunction; });

    case RuleContext::FontFeatureValue:
        // Each feature value block accepts a list of declarations
        return true;

    case RuleContext::AtFunction:
        // @function rules contain descriptor declarations
        return true;

    case RuleContext::AtCounterStyle:
    case RuleContext::AtFontFace:
    case RuleContext::AtFontFeatureValues:
    case RuleContext::AtPage:
    case RuleContext::AtProperty:
    case RuleContext::Margin:
        // These have descriptor declarations
        return true;

    case RuleContext::AtKeyframes:
        // @keyframes can only contain keyframe rules
        return false;

    case RuleContext::SupportsCondition:
        // @supports conditions accept all declarations
        return true;
    }

    VERIFY_NOT_REACHED();
}

bool Parser::is_valid_in_the_current_context(AtRule const& at_rule) const
{
    // All at-rules can appear at the top level, except margin rules
    if (m_rule_context.is_empty())
        return !is_margin_rule_name(at_rule.name);

    // Only grouping rules can be nested within style rules
    if (m_rule_context.contains_slow(RuleContext::Style))
        return first_is_one_of(at_rule.name, "container", "layer", "media", "supports");

    if (m_rule_context.contains_slow(RuleContext::AtFunction)) {
        // https://drafts.csswg.org/css-mixins-1/#function-body
        // The body of a @function rule accepts conditional group rules
        return first_is_one_of(at_rule.name, "container", "media", "supports");
    }

    switch (m_rule_context.last()) {
    case RuleContext::Unknown:
        // If the context is an unknown type, we don't accept anything.
        return false;

    case RuleContext::Style:
        // Already handled above
        VERIFY_NOT_REACHED();

    case RuleContext::AtContainer:
    case RuleContext::AtLayer:
    case RuleContext::AtMedia:
    case RuleContext::AtSupports:
        // Grouping rules can contain anything except @import or @namespace
        return !first_is_one_of(at_rule.name, "import", "namespace");

    case RuleContext::SupportsCondition:
        // @supports cannot check for at-rules
        return false;

    case RuleContext::AtPage:
        // @page rules can contain margin rules
        return is_margin_rule_name(at_rule.name);

    case RuleContext::AtCounterStyle:
    case RuleContext::AtFontFace:
    case RuleContext::FontFeatureValue:
    case RuleContext::AtKeyframes:
    case RuleContext::Keyframe:
    case RuleContext::AtProperty:
    case RuleContext::Margin:
        // These can't contain any at-rules
        return false;
    case RuleContext::AtFontFeatureValues:
        return CSSFontFeatureValuesRule::is_font_feature_value_type_at_keyword(at_rule.name);
    case RuleContext::AtFunction:
        // Already handled above
        VERIFY_NOT_REACHED();
    }

    VERIFY_NOT_REACHED();
}

bool Parser::is_valid_in_the_current_context(QualifiedRule const&) const
{
    // TODO: Different places accept different kinds of qualified rules. How do we tell them apart? Can we?

    // Top level can contain style rules
    if (m_rule_context.is_empty())
        return true;

    switch (m_rule_context.last()) {
    case RuleContext::Unknown:
        // If the context is an unknown type, we don't accept anything.
        return false;

    case RuleContext::Style:
        // Style rules can contain style rules
        return true;

    case RuleContext::AtContainer:
    case RuleContext::AtLayer:
    case RuleContext::AtMedia:
    case RuleContext::AtSupports:
        // Grouping rules can contain style rules
        return true;

    case RuleContext::AtKeyframes:
        // @keyframes can contain keyframe rules
        return true;

    case RuleContext::SupportsCondition:
        // @supports cannot check qualified rules
        return false;

    case RuleContext::AtCounterStyle:
    case RuleContext::AtFontFace:
    case RuleContext::AtFontFeatureValues:
    case RuleContext::FontFeatureValue:
    case RuleContext::AtFunction:
    case RuleContext::AtPage:
    case RuleContext::AtProperty:
    case RuleContext::Keyframe:
    case RuleContext::Margin:
        // These can't contain qualified rules
        return false;
    }

    VERIFY_NOT_REACHED();
}

void Parser::extract_property(Declaration const& declaration, PropertiesAndCustomProperties& dest)
{
    if (auto maybe_property_and_name = convert_to_style_property(declaration); maybe_property_and_name.has_value()) {
        auto property = maybe_property_and_name->property;
        if (property.property_id == PropertyID::Custom) {
            dest.custom_properties.set(maybe_property_and_name->name, property);
        } else {
            dest.properties.append(move(property));
        }
    }
}

GC::Ref<CSSStyleProperties> Parser::convert_to_style_declaration(Vector<Declaration> const& declarations)
{
    PropertiesAndCustomProperties properties;
    PropertiesAndCustomProperties& dest = properties;
    for (auto const& declaration : declarations) {
        extract_property(declaration, dest);
    }
    return CSSStyleProperties::create(realm(), move(properties.properties), move(properties.custom_properties));
}

Optional<StylePropertyAndName> Parser::convert_to_style_property(Declaration const& declaration)
{
    auto property = PropertyNameAndID::from_name(declaration.name);

    if (!property.has_value()) {
        if (has_ignored_vendor_prefix(declaration.name)) {
            return {};
        }
        ErrorReporter::the().report(UnknownPropertyError { .property_name = declaration.name });
        return {};
    }

    auto value_token_stream = TokenStream(declaration.value);
    auto value = parse_css_value(property->id(), value_token_stream, declaration.original_value_text);
    if (value.is_error()) {
        if (value.error() == ParseError::SyntaxError) {
            ErrorReporter::the().report(InvalidPropertyError {
                .property_name = property->name(),
                .value_string = value_token_stream.dump_string(),
                .description = "Failed to parse."_string,
            });
        }
        return {};
    }

    if (property->is_custom_property())
        return StylePropertyAndName {
            StyleProperty { declaration.important, property->id(), value.release_value() },
            property->name()
        };

    return StylePropertyAndName {
        StyleProperty { declaration.important, property->id(), value.release_value() }
    };
}

RefPtr<StyleValue const> Parser::parse_source_size_value(TokenStream<ComponentValue>& tokens)
{
    if (tokens.next_token().is_ident("auto"sv)) {
        tokens.discard_a_token(); // auto
        return KeywordStyleValue::create(Keyword::Auto);
    }

    // https://html.spec.whatwg.org/multipage/images.html#valid-source-size-list
    // "A <source-size-value> that is a <length> must not be negative,
    // and must not use CSS functions other than the math functions."
    if (auto parsed = parse_length_value(tokens, non_negative_range)) {
        // FIXME: It seems odd that we disallow infinite calculated values here rather than clamping as we do for all
        //        other values - is this correct?
        if (parsed->is_calculated()) {
            // https://drafts.csswg.org/css-values-4/#calc-range
            // "the value resulting from a top-level calculation must be
            // clamped to the range allowed in the target context."
            auto raw_length = parsed->as_calculated().resolve_raw_length({});
            if (raw_length.has_value() && !isfinite(*raw_length))
                return {};
        }

        return parsed;
    }

    return {};
}

bool Parser::context_allows_quirky_length() const
{
    if (!in_quirks_mode())
        return false;

    // https://drafts.csswg.org/css-values-4/#deprecated-quirky-length
    // "When CSS is being parsed in quirks mode, <quirky-length> is a type of <length> that is only valid in certain properties:"
    // (NOTE: List skipped for brevity; quirks data is assigned in Properties.json)
    // "It is not valid in properties that include or reference these properties, such as the background shorthand,
    // or inside functional notations such as calc(), except that they must be allowed in rect() in the clip property."

    // So, it must be allowed in the top-level ValueParsingContext, and then not disallowed by any child contexts.

    Optional<PropertyID> top_level_property;
    if (!m_value_context.is_empty()) {
        top_level_property = m_value_context.first().visit(
            [](PropertyID const& property_id) -> Optional<PropertyID> { return property_id; },
            [](auto const&) -> Optional<PropertyID> { return OptionalNone {}; });
    }

    bool unitless_length_allowed = top_level_property.has_value() && property_has_quirk(top_level_property.value(), Quirk::UnitlessLength);
    for (auto i = 1u; i < m_value_context.size() && unitless_length_allowed; i++) {
        unitless_length_allowed = m_value_context[i].visit(
            [](PropertyID const& property_id) { return property_has_quirk(property_id, Quirk::UnitlessLength); },
            [top_level_property](FunctionContext const& function_context) {
                return function_context.name == "rect"sv && top_level_property == PropertyID::Clip;
            },
            [](auto const&) { return false; });
    }

    return unitless_length_allowed;
}

bool Parser::context_allows_tree_counting_functions() const
{
    for (auto context : m_value_context) {
        if (context.has<DescriptorContext>())
            return false;

        if (auto const* special_context = context.get_pointer<SpecialContext>(); special_context && first_is_one_of(*special_context, SpecialContext::CanvasContextGenericValue, SpecialContext::DOMMatrixInitString, SpecialContext::MediaCondition))
            return false;

        // TODO: Handle other contexts where tree counting functions are not allowed
    }

    return true;
}

bool Parser::context_allows_random_functions() const
{
    if (auto const* special_context = m_value_context.first().get_pointer<SpecialContext>(); special_context && first_is_one_of(*special_context, SpecialContext::CanvasContextGenericValue, SpecialContext::OnScreenCanvasContextFontValue))
        return false;

    // For now we only allow random functions within property contexts, see https://drafts.csswg.org/css-values-5/#issue-cd071f29
    // FIXME: Should this instead check that the top-level context is a property context (our current configuration
    //        allows these within DOMMatrixInitString for example)
    return m_value_context.contains([](ValueParsingContext context) { return context.has<PropertyID>(); });
}

FlyString Parser::random_value_sharing_auto_name() const
{
    auto top_level_property_context_index = m_value_context.find_first_index_if([](ValueParsingContext const& context) { return context.has<PropertyID>(); });

    auto property_name = string_from_property_id(m_value_context[top_level_property_context_index.value()].get<PropertyID>());

    return MUST(String::formatted("{} {}", property_name, m_random_function_index));
}

Vector<ComponentValue> Parser::parse_as_list_of_component_values()
{
    return parse_a_list_of_component_values(m_token_stream);
}

RefPtr<StyleValue const> Parser::parse_as_css_value(PropertyID property_id)
{
    auto component_values = parse_a_list_of_component_values(m_token_stream);
    auto tokens = TokenStream(component_values);
    auto parsed_value = parse_css_value(property_id, tokens);
    if (parsed_value.is_error())
        return nullptr;
    return parsed_value.release_value();
}

RefPtr<StyleValue const> Parser::parse_as_descriptor_value(AtRuleID at_rule_id, DescriptorNameAndID const& descriptor_name_and_id)
{
    auto component_values = parse_a_list_of_component_values(m_token_stream);
    auto tokens = TokenStream(component_values);
    auto parsed_value = parse_descriptor_value(at_rule_id, descriptor_name_and_id, tokens);
    if (parsed_value.is_error())
        return nullptr;
    return parsed_value.release_value();
}

RefPtr<StyleValue const> Parser::parse_as_type(ValueType value_type)
{
    auto component_values = parse_a_list_of_component_values(m_token_stream);
    TokenStream tokens { component_values };
    return parse_value(value_type, tokens);
}

// https://html.spec.whatwg.org/multipage/images.html#parsing-a-sizes-attribute
NonnullRefPtr<StyleValue const> Parser::parse_as_sizes_attribute(DOM::Element const& element, HTML::HTMLImageElement const* img)
{
    // When asked to parse a sizes attribute from an element element, with an img element or null img:

    // AD-HOC: If element has no sizes attribute, this algorithm always logs a parse error and then returns 100vw.
    //         The attribute is optional, so avoid spamming the debug log with false positives by just returning early.
    if (!element.has_attribute(HTML::AttributeNames::sizes))
        return LengthStyleValue::create(Length(100, LengthUnit::Vw));

    // 1. Let unparsed sizes list be the result of parsing a comma-separated list of component values
    //    from the value of element's sizes attribute (or the empty string, if the attribute is absent).
    // NOTE: The sizes attribute has already been tokenized into m_token_stream by this point.
    auto unparsed_sizes_list = parse_a_comma_separated_list_of_component_values(m_token_stream);

    // 2. Let size be null.
    RefPtr<StyleValue const> size;

    auto remove_all_consecutive_whitespace_tokens_from_the_end_of = [](auto& tokens) {
        while (!tokens.is_empty() && tokens.last().is_token() && tokens.last().token().is(Token::Type::Whitespace))
            tokens.take_last();
    };

    // 3. For each unparsed size in unparsed sizes list:
    for (auto i = 0u; i < unparsed_sizes_list.size(); i++) {
        auto& unparsed_size = unparsed_sizes_list[i];

        // 1. Remove all consecutive <whitespace-token>s from the end of unparsed size.
        //    If unparsed size is now empty, that is a parse error; continue.
        remove_all_consecutive_whitespace_tokens_from_the_end_of(unparsed_size);
        if (unparsed_size.is_empty()) {
            log_parse_error();
            ErrorReporter::the().report(InvalidValueError {
                .value_type = "sizes attribute"_fly_string,
                .value_string = m_token_stream.dump_string(),
                .description = "Failed in step 3.1; all whitespace"_string,
            });
            continue;
        }

        // 2. If the last component value in unparsed size is a valid non-negative <source-size-value>,
        //    then set size to its value and remove the component value from unparsed size.
        //    Any CSS function other than the math functions is invalid.
        //    Otherwise, there is a parse error; continue.
        auto last_value_stream = TokenStream<ComponentValue>::of_single_token(unparsed_size.last());
        if (auto source_size_value = parse_source_size_value(last_value_stream)) {
            size = source_size_value.release_nonnull();
            unparsed_size.take_last();
        } else {
            log_parse_error();
            ErrorReporter::the().report(InvalidValueError {
                .value_type = "sizes attribute"_fly_string,
                .value_string = m_token_stream.dump_string(),
                .description = "Failed in step 3.2; couldn't parse {} as a <source-size-value>"_string,
            });
            continue;
        }

        // 3. If size is auto, and img is not null, and img is being rendered, and img allows auto-sizes,
        //    then set size to the concrete object size width of img, in CSS pixels.
        // FIXME: "img is being rendered" - we just see if it has a bitmap for now
        if (size->has_auto() && img && img->current_image_frame().has_value() && img->allows_auto_sizes()) {
            // FIXME: The spec doesn't seem to tell us how to determine the concrete size of an <img>, so use the default sizing algorithm.
            //        Should this use some of the methods from FormattingContext?
            auto concrete_size = run_default_sizing_algorithm(
                img->width(), img->height(),
                { img->natural_width(), img->natural_height(), img->intrinsic_aspect_ratio() },
                // NOTE: https://html.spec.whatwg.org/multipage/rendering.html#img-contain-size
                CSSPixelSize { 300, 150 });
            size = LengthStyleValue::create(Length::make_px(concrete_size.width()));
        }

        // 4. Remove all consecutive <whitespace-token>s from the end of unparsed size.
        //    If unparsed size is now empty:
        remove_all_consecutive_whitespace_tokens_from_the_end_of(unparsed_size);
        if (unparsed_size.is_empty()) {
            // 1. If this was not the last item in unparsed sizes list, that is a parse error.
            if (i != unparsed_sizes_list.size() - 1) {
                log_parse_error();
                ErrorReporter::the().report(InvalidValueError {
                    .value_type = "sizes attribute"_fly_string,
                    .value_string = m_token_stream.dump_string(),
                    .description = MUST(String::formatted("Failed in step 3.4.1; is unparsed size #{}, count {}", i, unparsed_sizes_list.size())),
                });
            }

            // 2. If size is not auto, then return size. Otherwise, continue.
            if (!size->has_auto())
                return size.release_nonnull();
            continue;
        }

        // 5. Parse the remaining component values in unparsed size as a <media-condition>.
        //    If it does not parse correctly, or it does parse correctly but the <media-condition> evaluates to false, continue.
        TokenStream token_stream { unparsed_size };
        auto media_condition = parse_media_condition(token_stream);
        if (!media_condition)
            continue;

        // https://drafts.csswg.org/mediaqueries-5/#evaluating
        // "If the result of any of the above productions is used in any
        // context that expects a two-valued boolean, 'unknown' must be
        // converted to 'false'."
        if (m_document && !media_condition->evaluate_to_boolean({ .document = m_document }))
            continue;

        // 5. If size is not auto, then return size. Otherwise, continue.
        if (!size->has_auto())
            return size.release_nonnull();
    }

    // 4. Return 100vw.
    return LengthStyleValue::create(Length(100, LengthUnit::Vw));
}

Parser::ParseErrorOr<void> Parser::collect_arbitrary_substitution_function_presence(Vector<ComponentValue> const& component_values, SubstitutionFunctionsPresence& presence)
{
    for (auto const& component_value : component_values) {
        if (collect_arbitrary_substitution_function_presence(component_value, presence).is_error())
            return ParseError::SyntaxError;
    }

    return {};
}

Parser::ParseErrorOr<void> Parser::collect_arbitrary_substitution_function_presence(ComponentValue const& component_value, SubstitutionFunctionsPresence& presence)
{
    if (component_value.is_function()) {
        auto const& function = component_value.function();
        if (auto arbitrary_substitution_function = to_arbitrary_substitution_function(function.name); arbitrary_substitution_function.has_value()) {
            if (!parse_according_to_argument_grammar(arbitrary_substitution_function.value(), function.value).has_value())
                return ParseError::SyntaxError;

            switch (arbitrary_substitution_function.value()) {
            case ArbitrarySubstitutionFunction::Attr:
                presence.attr = true;
                break;
            case ArbitrarySubstitutionFunction::Env:
                presence.env = true;
                break;
            case ArbitrarySubstitutionFunction::If:
                presence.if_ = true;
                break;
            case ArbitrarySubstitutionFunction::Inherit:
                presence.inherit = true;
                break;
            case ArbitrarySubstitutionFunction::Var:
                presence.var = true;
                break;
            }
        }

        return collect_arbitrary_substitution_function_presence(function.value, presence);
    }

    if (component_value.is_block())
        return collect_arbitrary_substitution_function_presence(component_value.block().value, presence);

    return {};
}

bool Parser::has_ignored_vendor_prefix(StringView string)
{
    if (!string.starts_with('-'))
        return false;
    if (string.starts_with("--"sv))
        return false;
    if (string.starts_with("-libweb-"sv))
        return false;
    if (string.count('-') == 1)
        return false;
    return true;
}

template Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<Token>&, Optional<::URL::URL>);
template Parser::ParsedStyleSheet Parser::parse_a_stylesheet(TokenStream<ComponentValue>&, Optional<::URL::URL>);

template Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<Token>& input);
template Vector<Rule> Parser::parse_a_stylesheets_contents(TokenStream<ComponentValue>& input);

template RefPtr<Supports> Parser::parse_a_supports(TokenStream<ComponentValue>&);
template RefPtr<Supports> Parser::parse_a_supports(TokenStream<Token>&);

template Vector<Rule> Parser::consume_a_stylesheets_contents(TokenStream<Token>&);
template Vector<Rule> Parser::consume_a_stylesheets_contents(TokenStream<ComponentValue>&);

template Optional<AtRule> Parser::consume_an_at_rule(TokenStream<Token>&, Nested);
template Optional<AtRule> Parser::consume_an_at_rule(TokenStream<ComponentValue>&, Nested);

template Variant<Empty, QualifiedRule, Parser::InvalidRuleError> Parser::consume_a_qualified_rule(TokenStream<Token>&, Optional<Token::Type>, Nested);
template Variant<Empty, QualifiedRule, Parser::InvalidRuleError> Parser::consume_a_qualified_rule(TokenStream<ComponentValue>&, Optional<Token::Type>, Nested);

template Vector<RuleOrListOfDeclarations> Parser::consume_a_block(TokenStream<Token>&);
template Vector<RuleOrListOfDeclarations> Parser::consume_a_block(TokenStream<ComponentValue>&);

template Vector<RuleOrListOfDeclarations> Parser::consume_a_blocks_contents(TokenStream<Token>&);
template Vector<RuleOrListOfDeclarations> Parser::consume_a_blocks_contents(TokenStream<ComponentValue>&);

template Vector<ComponentValue> Parser::consume_a_list_of_component_values(TokenStream<ComponentValue>&, Optional<Token::Type>, Nested);
template Vector<ComponentValue> Parser::consume_a_list_of_component_values(TokenStream<Token>&, Optional<Token::Type>, Nested);

template Optional<Declaration> Parser::consume_a_declaration(TokenStream<Token>&, Nested, SaveOriginalText);
template Optional<Declaration> Parser::consume_a_declaration(TokenStream<ComponentValue>&, Nested, SaveOriginalText);

template void Parser::consume_the_remnants_of_a_bad_declaration(TokenStream<Token>&, Nested);
template void Parser::consume_the_remnants_of_a_bad_declaration(TokenStream<ComponentValue>&, Nested);

template Optional<Rule> Parser::parse_a_rule(TokenStream<Token>&);
template Optional<Rule> Parser::parse_a_rule(TokenStream<ComponentValue>&);

template Vector<RuleOrListOfDeclarations> Parser::parse_a_blocks_contents(TokenStream<Token>&);
template Vector<RuleOrListOfDeclarations> Parser::parse_a_blocks_contents(TokenStream<ComponentValue>&);

template Optional<Declaration> Parser::parse_a_declaration(TokenStream<Token>&);
template Optional<Declaration> Parser::parse_a_declaration(TokenStream<ComponentValue>&);

template Optional<ComponentValue> Parser::parse_a_component_value(TokenStream<Token>&);
template Optional<ComponentValue> Parser::parse_a_component_value(TokenStream<ComponentValue>&);

template Vector<ComponentValue> Parser::parse_a_list_of_component_values(TokenStream<Token>&);
template Vector<ComponentValue> Parser::parse_a_list_of_component_values(TokenStream<ComponentValue>&);

template Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<ComponentValue>&);
template Vector<Vector<ComponentValue>> Parser::parse_a_comma_separated_list_of_component_values(TokenStream<Token>&);

DOM::Document const* Parser::document() const
{
    return m_document;
}

HTML::Window const* Parser::window() const
{
    if (!m_document)
        return nullptr;
    return m_document->window();
}

JS::Realm& Parser::realm() const
{
    VERIFY(m_realm);
    return *m_realm;
}

bool Parser::in_quirks_mode() const
{
    return m_document ? m_document->in_quirks_mode() : false;
}

bool Parser::is_parsing_svg_presentation_attribute() const
{
    return m_parsing_mode == ParsingMode::SVGPresentationAttribute;
}

}
