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

#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CONTAINER_SELECTOR_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CONTAINER_SELECTOR_H_

#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/layout/geometry/axis.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
#include "third_party/blink/renderer/platform/text/writing_mode.h"
#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"

namespace blink {

class Element;
class ConditionalExpNode;

// Not to be confused with regular selectors. This refers to container
// selection by e.g. a given name, or by implicit container selection
// according to the queried features.
//
// https://drafts.csswg.org/css-contain-3/#container-rule
class CORE_EXPORT ContainerSelector {
 public:
  ContainerSelector() = default;
  explicit ContainerSelector(HashTableDeletedValueType) {
    HashTraits<AtomicString>::ConstructDeletedValue(name_);
  }
  // Used for the purpose of finding the closest container for container units.
  explicit ContainerSelector(PhysicalAxes physical_axes)
      : physical_axes_(physical_axes) {}
  // Used for the purpose of finding the closest container for container units
  // and looking up the closest container matching a certain container-type for
  // the inspector (InspectorDOMAgent::getContainerForNode()).
  ContainerSelector(AtomicString name,
                    PhysicalAxes physical_axes,
                    LogicalAxes logical_axes,
                    bool scroll_state,
                    bool anchored_query)
      : name_(std::move(name)),
        physical_axes_(physical_axes),
        logical_axes_(logical_axes),
        has_sticky_query_(scroll_state),
        has_snap_query_(scroll_state),
        has_scrollable_query_(scroll_state),
        has_scrolled_query_(scroll_state),
        has_anchored_query_(anchored_query) {}
  ContainerSelector(AtomicString name, const ConditionalExpNode* query);

  enum FeatureFlag {
    kFeatureUnknown = 1 << 1,
    kFeatureWidth = 1 << 2,
    kFeatureHeight = 1 << 3,
    kFeatureInlineSize = 1 << 4,
    kFeatureBlockSize = 1 << 5,
    kFeatureStyle = 1 << 6,
    kFeatureSticky = 1 << 7,
    kFeatureSnap = 1 << 8,
    kFeatureScrollable = 1 << 9,
    kFeatureScrolled = 1 << 10,
    kFeatureAnchored = 1 << 11,
  };
  using FeatureFlags = unsigned;
  static FeatureFlags CollectFeatureFlags(const ConditionalExpNode& root);

  bool IsHashTableDeletedValue() const {
    return HashTraits<AtomicString>::IsDeletedValue(name_);
  }

  bool operator==(const ContainerSelector& o) const {
    return (name_ == o.name_) && (physical_axes_ == o.physical_axes_) &&
           (logical_axes_ == o.logical_axes_) &&
           (has_style_query_ == o.has_style_query_) &&
           (has_sticky_query_ == o.has_sticky_query_) &&
           (has_snap_query_ == o.has_snap_query_) &&
           (has_scrollable_query_ == o.has_scrollable_query_) &&
           (has_scrolled_query_ == o.has_scrolled_query_) &&
           (has_anchored_query_ == o.has_anchored_query_);
  }

  unsigned GetHash() const;

  const AtomicString& Name() const { return name_; }

  // Given the specified writing mode, return the EContainerTypes required
  // for this selector to match.
  unsigned Type(WritingMode) const;

  bool SelectsSizeContainers() const {
    return physical_axes_ != kPhysicalAxesNone ||
           logical_axes_ != kLogicalAxesNone;
  }

  bool SelectsNamedContainers() const { return !name_.IsNull(); }
  bool SelectsStyleContainers() const { return has_style_query_; }
  bool SelectsStickyContainers() const { return has_sticky_query_; }
  bool SelectsSnapContainers() const { return has_snap_query_; }
  bool SelectsScrollableContainers() const { return has_scrollable_query_; }
  bool SelectsScrolledContainers() const { return has_scrolled_query_; }
  bool SelectsScrollStateContainers() const {
    return SelectsStickyContainers() || SelectsSnapContainers() ||
           SelectsScrollableContainers() || SelectsScrolledContainers();
  }
  bool SelectsAnchoredContainers() const { return has_anchored_query_; }
  bool HasUnknownFeature() const { return has_unknown_feature_; }
  bool SelectsAnyContainer() const {
    return !HasUnknownFeature() &&
           (SelectsNamedContainers() || SelectsSizeContainers() ||
            SelectsStyleContainers() || SelectsScrollStateContainers() ||
            SelectsAnchoredContainers());
  }
  bool SelectsNameOnlyContainers() const {
    return !HasUnknownFeature() && SelectsNamedContainers() &&
           !SelectsSizeContainers() && !SelectsStyleContainers() &&
           !SelectsScrollStateContainers() && !SelectsAnchoredContainers();
  }
  bool SelectsStyleOrNameOnlyContainers() const {
    return SelectsStyleContainers() || SelectsNameOnlyContainers();
  }

  PhysicalAxes GetPhysicalAxes() const { return physical_axes_; }
  LogicalAxes GetLogicalAxes() const { return logical_axes_; }

 private:
  AtomicString name_;
  PhysicalAxes physical_axes_{kPhysicalAxesNone};
  LogicalAxes logical_axes_{kLogicalAxesNone};
  bool has_style_query_{false};
  bool has_sticky_query_{false};
  bool has_snap_query_{false};
  bool has_scrollable_query_{false};
  bool has_scrolled_query_{false};
  bool has_anchored_query_{false};
  bool has_unknown_feature_{false};
};

template <>
struct HashTraits<blink::ContainerSelector>
    : SimpleClassHashTraits<blink::ContainerSelector> {
  static unsigned GetHash(const blink::ContainerSelector& selector) {
    return selector.GetHash();
  }
  static constexpr bool kSafeToCompareToEmptyOrDeleted =
      HashTraits<blink::AtomicString>::kSafeToCompareToEmptyOrDeleted;
  static const bool kEmptyValueIsZero = false;
};

using ContainerSelectorCache = HeapHashMap<ContainerSelector, Member<Element>>;

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CONTAINER_SELECTOR_H_
