/*
 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
 * (C) 2002-2003 Dirk Mueller (mueller@kde.org)
 * Copyright (C) 2002, 2005, 2006, 2008, 2012 Apple Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

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

#include <ranges>

#include "third_party/blink/renderer/core/css/css_grouping_rule.h"
#include "third_party/blink/renderer/core/css/css_property_value_set.h"
#include "third_party/blink/renderer/core/css/css_rule_list.h"
#include "third_party/blink/renderer/core/css/css_selector.h"
#include "third_party/blink/renderer/core/css/css_style_sheet.h"
#include "third_party/blink/renderer/core/css/cssom/declared_style_property_map.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/style_rule.h"
#include "third_party/blink/renderer/core/css/style_rule_css_style_declaration.h"
#include "third_party/blink/renderer/core/css/style_sheet_contents.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/disallow_new_wrapper.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/wtf/text/strcat.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"

namespace blink {

using SelectorTextCache = HeapHashMap<WeakMember<const CSSStyleRule>, String>;

static SelectorTextCache& GetSelectorTextCache() {
  using SelectorTextCacheHolder = DisallowNewWrapper<SelectorTextCache>;
  DEFINE_STATIC_LOCAL(Persistent<SelectorTextCacheHolder>, cache,
                      (MakeGarbageCollected<SelectorTextCacheHolder>()));
  return cache->Value();
}

CSSStyleRule::CSSStyleRule(StyleRule* style_rule,
                           CSSStyleSheet* parent,
                           wtf_size_t position_hint)
    : CSSRule(parent),
      style_rule_(style_rule),
      style_map_(MakeGarbageCollected<DeclaredStylePropertyMap>(this)),
      position_hint_(position_hint),
      child_rule_cssom_wrappers_(
          style_rule->ChildRules() ? style_rule->ChildRules()->size() : 0) {}

CSSStyleRule::~CSSStyleRule() = default;

CSSStyleDeclaration* CSSStyleRule::style() const {
  if (!properties_cssom_wrapper_) {
    properties_cssom_wrapper_ =
        MakeGarbageCollected<StyleRuleCSSStyleDeclaration>(
            style_rule_->MutableProperties(), const_cast<CSSStyleRule*>(this));
  }
  return properties_cssom_wrapper_.Get();
}

String CSSStyleRule::selectorText() const {
  if (HasCachedSelectorText()) {
    DCHECK(GetSelectorTextCache().Contains(this));
    return GetSelectorTextCache().at(this);
  }

  DCHECK(!GetSelectorTextCache().Contains(this));
  String text = style_rule_->SelectorsText();
  GetSelectorTextCache().Set(this, text);
  SetHasCachedSelectorText(true);
  return text;
}

void CSSStyleRule::setSelectorText(const ExecutionContext* execution_context,
                                   const String& selector_text) {
  CSSStyleSheet::RuleMutationScope mutation_scope(this);

  const auto* context = MakeGarbageCollected<CSSParserContext>(
      ParserContext(execution_context->GetSecureContextMode()));
  CSSStyleSheet* parent_stylesheet = parentStyleSheet();
  StyleSheetContents* parent_contents =
      parent_stylesheet ? parent_stylesheet->Contents() : nullptr;
  HeapVector<CSSSelector> arena;

  NestingContext nesting_context = CalculateNestingContext(parentRule());
  base::span<CSSSelector> selector_vector =
      CSSParser::ParseSelector(context, nesting_context.nesting_type,
                               nesting_context.parent_rule_for_nesting,
                               parent_contents, selector_text, arena);
  if (selector_vector.empty()) {
    return;
  }

  StyleRule* new_style_rule = StyleRule::Create(
      selector_vector, style_rule_->Properties().ImmutableCopyIfNeeded());
  if (GCedHeapVector<Member<StyleRuleBase>>* child_rules =
          style_rule_->ChildRules()) {
    for (StyleRuleBase* child_rule : *child_rules) {
      new_style_rule->AddChildRule(child_rule->Clone(
          new_style_rule, /*mixin_parameter_bindings=*/nullptr));
    }
  }

  position_hint_ = ReplaceChildRuleInParentIfExists(
      /*old_rule=*/style_rule_, new_style_rule, position_hint_);

  if (parent_contents) {
    parent_contents->NotifyRuleChanged(style_rule_);
    parent_contents->NotifyRuleChanged(new_style_rule);
  }

  // Updates style_rule_, as well as any inner CSSOM wrappers.
  Reattach(new_style_rule);

  if (HasCachedSelectorText()) {
    GetSelectorTextCache().erase(this);
    SetHasCachedSelectorText(false);
  }
}

String CSSStyleRule::cssText() const {
  // Referring to https://drafts.csswg.org/cssom-1/#serialize-a-css-rule:

  // Step 1.
  StringBuilder result;
  result.Append(selectorText());
  result.Append(" {");

  // Step 2.
  String decls = style_rule_->Properties().AsText();

  // Step 3.
  StringBuilder rules;
  unsigned size = length();
  for (unsigned i = 0; i < size; ++i) {
    // Step 6.2 for rules.
    String item_text = ItemInternal(i)->cssText();
    if (!item_text.empty()) {
      rules.Append("\n  ");
      rules.Append(item_text);
    }
  }

  // Step 4.
  if (decls.empty() && rules.empty()) {
    result.Append(" }");
    return result.ReleaseString();
  }

  // Step 5.
  if (rules.empty()) {
    result.Append(' ');
    result.Append(decls);
    result.Append(" }");
    return result.ReleaseString();
  }

  // Step 6.
  if (!decls.empty()) {
    // Step 6.2 for decls (we don't do 6.1 explicitly).
    result.Append("\n  ");
    result.Append(decls);
  }

  // Step 6.2 for rules was done above.
  result.Append(rules);

  result.Append("\n}");
  return result.ReleaseString();
}

void CSSStyleRule::Reattach(StyleRuleBase* rule) {
  DCHECK(rule);
  style_rule_ = To<StyleRule>(rule);
  if (properties_cssom_wrapper_) {
    properties_cssom_wrapper_->Reattach(style_rule_->MutableProperties());
  }
  if (style_rule_->ChildRules()) {
    CHECK_EQ(style_rule_->ChildRules()->size(),
             child_rule_cssom_wrappers_.size());
    for (auto [child_rule, child_wrapper] : std::views::zip(
             *style_rule_->ChildRules(), child_rule_cssom_wrappers_)) {
      if (child_wrapper) {
        child_wrapper->Reattach(child_rule);
      }
    }
  }
}

void CSSStyleRule::Trace(Visitor* visitor) const {
  visitor->Trace(style_rule_);
  visitor->Trace(properties_cssom_wrapper_);
  visitor->Trace(style_map_);
  visitor->Trace(child_rule_cssom_wrappers_);
  visitor->Trace(rule_list_cssom_wrapper_);
  CSSRule::Trace(visitor);
}

unsigned CSSStyleRule::length() const {
  if (style_rule_->ChildRules()) {
    return style_rule_->ChildRules()->size();
  } else {
    return 0;
  }
}

CSSRule* CSSStyleRule::Item(unsigned index, bool trigger_use_counters) const {
  if (index >= length()) {
    return nullptr;
  }
  DCHECK_EQ(child_rule_cssom_wrappers_.size(),
            style_rule_->ChildRules()->size());
  Member<CSSRule>& rule = child_rule_cssom_wrappers_[index];
  if (!rule) {
    rule = (*style_rule_->ChildRules())[index]->CreateCSSOMWrapper(
        index, const_cast<CSSStyleRule*>(this), trigger_use_counters);
  }
  return rule.Get();
}

CSSRuleList* CSSStyleRule::cssRules() const {
  if (!rule_list_cssom_wrapper_) {
    rule_list_cssom_wrapper_ =
        MakeGarbageCollected<LiveCSSRuleList<CSSStyleRule>>(
            const_cast<CSSStyleRule*>(this));
  }
  return rule_list_cssom_wrapper_.Get();
}

unsigned CSSStyleRule::insertRule(const ExecutionContext* execution_context,
                                  const String& rule_string,
                                  unsigned index,
                                  ExceptionState& exception_state) {
  if (style_rule_->ChildRules() == nullptr) {
    // Implicitly zero rules.
    if (index > 0) {
      exception_state.ThrowDOMException(
          DOMExceptionCode::kIndexSizeError,
          StrCat(
              {"the index ", String::Number(index),
               " must be less than or equal to the length of the rule list."}));
      return 0;
    }
    style_rule_->EnsureChildRules();
  }

  DCHECK_EQ(child_rule_cssom_wrappers_.size(),
            style_rule_->ChildRules()->size());

  StyleRuleBase* new_rule = ParseRuleForInsert(
      execution_context, rule_string, index, style_rule_->ChildRules()->size(),
      *this, exception_state);

  if (new_rule == nullptr) {
    // Already raised an exception above.
    return 0;
  } else {
    CSSStyleSheet::RuleMutationScope mutation_scope(this);
    style_rule_->WrapperInsertRule(parentStyleSheet(), index, new_rule);
    child_rule_cssom_wrappers_.insert(index, Member<CSSRule>(nullptr));
    return index;
  }
}

void CSSStyleRule::deleteRule(unsigned index, ExceptionState& exception_state) {
  if (style_rule_->ChildRules() == nullptr ||
      index >= style_rule_->ChildRules()->size()) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kIndexSizeError,
        StrCat({"the index ", String::Number(index),
                " is greated than the length of the rule list."}));
    return;
  }

  DCHECK_EQ(child_rule_cssom_wrappers_.size(),
            style_rule_->ChildRules()->size());

  CSSStyleSheet::RuleMutationScope mutation_scope(this);

  style_rule_->WrapperRemoveRule(parentStyleSheet(), index);

  if (child_rule_cssom_wrappers_[index]) {
    child_rule_cssom_wrappers_[index]->SetParentRule(nullptr);
  }
  child_rule_cssom_wrappers_.EraseAt(index);
}

void CSSStyleRule::QuietlyInsertRule(const ExecutionContext* execution_context,
                                     const String& rule,
                                     unsigned index) {
  style_rule_->EnsureChildRules();
  ParseAndQuietlyInsertRule(execution_context, rule, index,
                            /*parent_rule=*/*this, *style_rule_->ChildRules(),
                            child_rule_cssom_wrappers_);
}

void CSSStyleRule::QuietlyDeleteRule(unsigned index) {
  CHECK(style_rule_->ChildRules());
  blink::QuietlyDeleteRule(index, *style_rule_->ChildRules(),
                           child_rule_cssom_wrappers_);
}

}  // namespace blink
