// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/core/css/css_test_helpers.h"

#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_css_style_sheet_init.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_property_definition.h"
#include "third_party/blink/renderer/core/css/css_custom_ident_value.h"
#include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
#include "third_party/blink/renderer/core/css/css_rule_list.h"
#include "third_party/blink/renderer/core/css/css_style_sheet.h"
#include "third_party/blink/renderer/core/css/css_syntax_definition.h"
#include "third_party/blink/renderer/core/css/css_syntax_string_parser.h"
#include "third_party/blink/renderer/core/css/css_variable_data.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_local_context.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_token_stream.h"
#include "third_party/blink/renderer/core/css/parser/css_property_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_selector_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
#include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
#include "third_party/blink/renderer/core/css/properties/longhand.h"
#include "third_party/blink/renderer/core/css/property_registration.h"
#include "third_party/blink/renderer/core/css/property_registry.h"
#include "third_party/blink/renderer/core/css/rule_set.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/css/style_sheet_contents.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/execution_context/security_context.h"
#include "third_party/blink/renderer/core/style/computed_style_constants.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"

namespace blink {
namespace css_test_helpers {

TestStyleSheet::~TestStyleSheet() = default;

TestStyleSheet::TestStyleSheet() {
  document_ = Document::CreateForTest(execution_context_.GetExecutionContext());
  style_sheet_ = CreateStyleSheet(*document_);
}

CSSRuleList* TestStyleSheet::CssRules() {
  DummyExceptionStateForTesting exception_state;
  CSSRuleList* result = style_sheet_->cssRules(exception_state);
  EXPECT_FALSE(exception_state.HadException());
  return result;
}

RuleSet& TestStyleSheet::GetRuleSet() {
  RuleSet& rule_set = style_sheet_->Contents()->EnsureRuleSet(
      MediaQueryEvaluator(document_->GetFrame()), /*mixins=*/{});
  rule_set.CompactRulesIfNeeded();
  return rule_set;
}

void TestStyleSheet::AddCSSRules(const String& css_text, bool is_empty_sheet) {
  unsigned sheet_length = style_sheet_->length();
  style_sheet_->Contents()->ParseString(css_text);
  if (!is_empty_sheet) {
    ASSERT_GT(style_sheet_->length(), sheet_length);
  } else {
    ASSERT_EQ(style_sheet_->length(), sheet_length);
  }
}

CSSStyleSheet* CreateStyleSheet(Document& document) {
  return CSSStyleSheet::CreateInline(
      document, NullUrl(), TextPosition::MinimumPosition(), Utf8Encoding());
}

RuleSet* CreateRuleSet(Document& document, String text) {
  DummyExceptionStateForTesting exception_state;
  auto* init = CSSStyleSheetInit::Create();
  auto* media_query_evaluator =
      MakeGarbageCollected<MediaQueryEvaluator>(document.GetFrame());
  auto* sheet = CSSStyleSheet::Create(document, init, exception_state);
  sheet->replaceSync(text, exception_state);
  return &sheet->Contents()->EnsureRuleSet(*media_query_evaluator,
                                           /*mixins=*/{});
}

PropertyRegistration* CreatePropertyRegistration(const String& name,
                                                 String syntax,
                                                 const CSSValue* initial_value,
                                                 bool is_inherited) {
  auto syntax_definition = CSSSyntaxStringParser(syntax).Parse();
  DCHECK(syntax_definition);
  DCHECK(syntax_definition->IsUniversal() || initial_value);
  return MakeGarbageCollected<PropertyRegistration>(
      AtomicString(name), *syntax_definition, is_inherited, initial_value);
}

PropertyRegistration* CreateLengthRegistration(const String& name, int px) {
  const CSSValue* initial =
      CSSNumericLiteralValue::Create(px, CSSPrimitiveValue::UnitType::kPixels);
  return CreatePropertyRegistration(name, "<length>", initial,
                                    false /* is_inherited */);
}

void RegisterProperty(Document& document,
                      const String& name,
                      const String& syntax,
                      const std::optional<String>& initial_value,
                      bool is_inherited) {
  DummyExceptionStateForTesting exception_state;
  RegisterProperty(document, name, syntax, initial_value, is_inherited,
                   exception_state);
  ASSERT_FALSE(exception_state.HadException());
}

void RegisterProperty(Document& document,
                      const String& name,
                      const String& syntax,
                      const std::optional<String>& initial_value,
                      bool is_inherited,
                      ExceptionState& exception_state) {
  DCHECK(!initial_value || !initial_value.value().IsNull());
  PropertyDefinition* property_definition = PropertyDefinition::Create();
  property_definition->setName(name);
  property_definition->setSyntax(syntax);
  property_definition->setInherits(is_inherited);
  if (initial_value) {
    property_definition->setInitialValue(initial_value.value());
  }
  PropertyRegistration::registerProperty(document.GetExecutionContext(),
                                         property_definition, exception_state);
}

void DeclareProperty(Document& document,
                     const String& name,
                     const String& syntax,
                     const std::optional<String>& initial_value,
                     bool is_inherited) {
  StringBuilder builder;
  builder.Append("@property ");
  builder.Append(name);
  builder.Append(" { ");

  // syntax:
  builder.Append("syntax:\"");
  builder.Append(syntax);
  builder.Append("\";");

  // initial-value:
  if (initial_value.has_value()) {
    builder.Append("initial-value:");
    builder.Append(initial_value.value());
    builder.Append(";");
  }

  // inherits:
  builder.Append("inherits:");
  builder.Append(String::Boolean(is_inherited));
  builder.Append(";");

  builder.Append(" }");

  auto* rule =
      DynamicTo<StyleRuleProperty>(ParseRule(document, builder.ToString()));
  if (!rule) {
    return;
  }
  auto* registration = PropertyRegistration::MaybeCreateForDeclaredProperty(
      document, AtomicString(name), *rule);
  if (!registration) {
    return;
  }
  document.EnsurePropertyRegistry().DeclareProperty(AtomicString(name),
                                                    *registration);
  document.GetStyleEngine().PropertyRegistryChanged();
}

CSSVariableData* CreateVariableData(String s) {
  bool is_animation_tainted = false;
  bool is_attr_tainted = false;
  bool needs_variable_resolution = false;
  return CSSVariableData::Create(
      s, is_animation_tainted, is_attr_tainted,
      CSSVariableData::HasReferences(needs_variable_resolution));
}

const CSSValue* CreateCustomIdent(const char* s) {
  return MakeGarbageCollected<CSSCustomIdentValue>(AtomicString(s));
}

const CSSValue* ParseLonghand(Document& document,
                              const CSSProperty& property,
                              const String& value) {
  const auto* longhand = DynamicTo<Longhand>(property);
  if (!longhand) {
    return nullptr;
  }

  const auto* context = MakeGarbageCollected<CSSParserContext>(document);
  CSSParserLocalContext local_context =
      CSSParserLocalContext::CreateWithoutPropertyForTest();

  CSSParserTokenStream stream(value);
  return longhand->ParseSingleValue(stream, *context, local_context);
}

const CSSPropertyValueSet* ParseDeclarationBlock(const String& block_text,
                                                 CSSParserMode mode) {
  auto* set = MakeGarbageCollected<MutableCSSPropertyValueSet>(mode);
  set->ParseDeclarationList(block_text, SecureContextMode::kSecureContext,
                            nullptr);
  return set;
}

StyleRuleBase* ParseRule(Document& document, String text) {
  return ParseNestedRule(document, text, CSSNestingType::kNone,
                         /*parent_rule_for_nesting=*/nullptr);
}

StyleRuleBase* ParseNestedRule(Document& document,
                               String text,
                               CSSNestingType nesting_type,
                               StyleRule* parent_rule_for_nesting) {
  auto* sheet = CSSStyleSheet::CreateInline(
      document, NullUrl(), TextPosition::MinimumPosition(), Utf8Encoding());
  const auto* context = MakeGarbageCollected<CSSParserContext>(document);
  return CSSParser::ParseRule(context, sheet->Contents(), nesting_type,
                              parent_rule_for_nesting, text);
}

const CSSValue* ParseValue(Document& document, String syntax, String value) {
  auto syntax_definition = CSSSyntaxStringParser(syntax).Parse();
  if (!syntax_definition.has_value()) {
    return nullptr;
  }
  const auto* context = MakeGarbageCollected<CSSParserContext>(document);
  CSSParserLocalContext local_context =
      CSSParserLocalContext::CreateWithoutPropertyForTest();
  return syntax_definition->Parse(value, *context, local_context,
                                  /* is_animation_tainted */ false);
}

CSSSelectorList* ParseSelectorList(const String& string) {
  return ParseSelectorList(string, CSSNestingType::kNone,
                           /*parent_rule_for_nesting=*/nullptr);
}

CSSSelectorList* ParseSelectorList(const String& string,
                                   CSSNestingType nesting_type,
                                   const StyleRule* parent_rule_for_nesting) {
  auto* context = MakeGarbageCollected<CSSParserContext>(
      kHTMLStandardMode, SecureContextMode::kInsecureContext);
  auto* sheet = MakeGarbageCollected<StyleSheetContents>(context);
  CSSParserTokenStream stream(string);
  HeapVector<CSSSelector> arena;
  base::span<CSSSelector> vector = CSSSelectorParser::ParseSelector(
      stream, context, nesting_type, parent_rule_for_nesting,
      /* semicolon_aborts_nested_selector */ false, sheet, arena);
  return CSSSelectorList::AdoptSelectorVector(vector);
}

String ToString(PseudoId pseudo_id) {
  switch (pseudo_id) {
    case kPseudoIdNone:
      return "kPseudoIdNone";
    case kPseudoIdFirstLine:
      return "kPseudoIdFirstLine";
    case kPseudoIdFirstLetter:
      return "kPseudoIdFirstLetter";
    case kPseudoIdCheckMark:
      return "kPseudoIdCheckMark";
    case kPseudoIdBefore:
      return "kPseudoIdBefore";
    case kPseudoIdAfter:
      return "kPseudoIdAfter";
    case kPseudoIdExpandIcon:
      return "kPseudoIdExpandIcon";
    case kPseudoIdPickerIcon:
      return "kPseudoIdPickerIcon";
    case kPseudoIdInterestButton:
      return "kPseudoIdInterestButton";
    case kPseudoIdMarker:
      return "kPseudoIdMarker";
    case kPseudoIdBackdrop:
      return "kPseudoIdBackdrop";
    case kPseudoIdSelection:
      return "kPseudoIdSelection";
    case kPseudoIdScrollbar:
      return "kPseudoIdScrollbar";
    case kPseudoIdScrollMarker:
      return "kPseudoIdScrollMarker";
    case kPseudoIdScrollMarkerGroup:
      return "kPseudoIdScrollMarkerGroup";
    case kPseudoIdScrollButton:
      return "kPseudoIdScrollButton";
    case kPseudoIdScrollButtonBlockStart:
      return "kPseudoIdScrollButtonBlockStart";
    case kPseudoIdScrollButtonInlineStart:
      return "kPseudoIdScrollButtonInlineStart";
    case kPseudoIdScrollButtonInlineEnd:
      return "kPseudoIdScrollButtonInlineEnd";
    case kPseudoIdScrollButtonBlockEnd:
      return "kPseudoIdScrollButtonBlockEnd";
    case kPseudoIdColumn:
      return "kPseudoIdColumn";
    case kPseudoIdSearchText:
      return "kPseudoIdSearchText";
    case kPseudoIdTargetText:
      return "kPseudoIdTargetText";
    case kPseudoIdHighlight:
      return "kPseudoIdHighlight";
    case kPseudoIdSpellingError:
      return "kPseudoIdSpellingError";
    case kPseudoIdGrammarError:
      return "kPseudoIdGrammarError";
    case kPseudoIdViewTransition:
      return "kPseudoIdViewTransition";
    case kPseudoIdViewTransitionGroup:
      return "kPseudoIdViewTransitionGroup";
    case kPseudoIdViewTransitionGroupChildren:
      return "kPseudoIdViewTransitionGroupChildren";
    case kPseudoIdViewTransitionImagePair:
      return "kPseudoIdViewTransitionImagePair";
    case kPseudoIdViewTransitionOld:
      return "kPseudoIdViewTransitionOld";
    case kPseudoIdViewTransitionNew:
      return "kPseudoIdViewTransitionNew";
    case kPseudoIdFirstLineInherited:
      return "kPseudoIdFirstLineInherited";
    case kPseudoIdScrollbarThumb:
      return "kPseudoIdScrollbarThumb";
    case kPseudoIdScrollbarButton:
      return "kPseudoIdScrollbarButton";
    case kPseudoIdScrollbarTrack:
      return "kPseudoIdScrollbarTrack";
    case kPseudoIdScrollbarTrackPiece:
      return "kPseudoIdScrollbarTrackPiece";
    case kPseudoIdScrollbarCorner:
      return "kPseudoIdScrollbarCorner";
    case kPseudoIdScrollMarkerGroupAfter:
      return "kPseudoIdScrollMarkerGroupAfter";
    case kPseudoIdScrollMarkerGroupBefore:
      return "kPseudoIdScrollMarkerGroupBefore";
    case kPseudoIdResizer:
      return "kPseudoIdResizer";
    case kPseudoIdInputListButton:
      return "kPseudoIdInputListButton";
    case kPseudoIdPlaceholder:
      return "kPseudoIdPlaceholder";
    case kPseudoIdFileSelectorButton:
      return "kPseudoIdFileSelectorButton";
    case kPseudoIdDetailsContent:
      return "kPseudoIdDetailsContent";
    case kPseudoIdPickerSelect:
      return "kPseudoIdPickerSelect";
    case kPseudoIdSelectListbox:
      return "kPseudoIdSelectListbox";
    case kPseudoIdPermissionIcon:
      return "kPseudoIdPermissionIcon";
    case kAfterLastInternalPseudoId:
      return "kAfterLastInternalPseudoId";
    case kPseudoIdOverscrollAreaParent:
      return "kPseudoIdOverscrollAreaParent";
    case kPseudoIdOverscrollBackdrop:
      return "kPseudoIdOverscrollBackdrop";
    case kPseudoIdSkeleton:
      return "kPseudoIdSkeleton";
    case kPseudoIdInvalid:
      return "kPseudoIdInvalid";
  }
}

String ToString(const PseudoIdFlags& flags) {
  StringBuilder builder;

  builder.Append("[");

  for (int i = PseudoIdFlags::kFirstValid; i <= PseudoIdFlags::kLastValid;
       ++i) {
    PseudoId pseudo_id = static_cast<PseudoId>(i);
    if (flags.Has(pseudo_id)) {
      if (builder.length() > 1) {
        builder.Append(", ");
      }
      builder.Append(css_test_helpers::ToString(pseudo_id));
    }
  }

  builder.Append("]");

  return builder.ToString();
}

}  // namespace css_test_helpers
}  // namespace blink
