ifndef SASS_EXTENDER_H define SASS_EXTENDER_H

include <set> include <map> include <string>

include “ast_helpers.hpp” include “ast_fwd_decl.hpp” include “operation.hpp” include “extension.hpp” include “backtrace.hpp” include “ordered_map.hpp”

namespace Sass {

// ##########################################################################
// Different hash map types used by extender
// ##########################################################################

// This is special (ptrs!)
typedef std::unordered_set<
  ComplexSelectorObj,
  ObjPtrHash,
  ObjPtrEquality
> ExtCplxSelSet;

typedef std::unordered_set<
  SimpleSelectorObj,
  ObjHash,
  ObjEquality
> ExtSmplSelSet;

typedef std::unordered_set<
  SelectorListObj,
  ObjPtrHash,
  ObjPtrEquality
> ExtListSelSet;

typedef std::unordered_map<
  SimpleSelectorObj,
  ExtListSelSet,
  ObjHash,
  ObjEquality
> ExtSelMap;

typedef ordered_map<
  ComplexSelectorObj,
  Extension,
  ObjHash,
  ObjEquality
> ExtSelExtMapEntry;

typedef std::unordered_map<
  SimpleSelectorObj,
  ExtSelExtMapEntry,
  ObjHash,
  ObjEquality
> ExtSelExtMap;

typedef std::unordered_map <
  SimpleSelectorObj,
  sass::vector<
    Extension
  >,
  ObjHash,
  ObjEquality
> ExtByExtMap;

class Extender : public Operation_CRTP<void, Extender> {

public:

  enum ExtendMode { TARGETS, REPLACE, NORMAL, };

private:

  // ##########################################################################
  // The mode that controls this extender's behavior.
  // ##########################################################################
  ExtendMode mode;

  // ##########################################################################
  // Shared backtraces with context and expander. Needed the throw
  // errors when e.g. extending across media query boundaries.
  // ##########################################################################
  Backtraces& traces;

  // ##########################################################################
  // A map from all simple selectors in the stylesheet to the rules that
  // contain them.This is used to find which rules an `@extend` applies to.
  // ##########################################################################
  ExtSelMap selectors;

  // ##########################################################################
  // A map from all extended simple selectors
  // to the sources of those extensions.
  // ##########################################################################
  ExtSelExtMap extensions;

  // ##########################################################################
  // A map from all simple selectors in extenders to
  // the extensions that those extenders define.
  // ##########################################################################
  ExtByExtMap extensionsByExtender;

  // ##########################################################################
  // A map from CSS rules to the media query contexts they're defined in.
  // This tracks the contexts in which each style rule is defined.
  // If a rule is defined at the top level, it doesn't have an entry.
  // ##########################################################################
  ordered_map<
    SelectorListObj,
    CssMediaRuleObj,
    ObjPtrHash,
    ObjPtrEquality
  > mediaContexts;

  // ##########################################################################
  // A map from [SimpleSelector]s to the specificity of their source selectors.
  // This tracks the maximum specificity of the [ComplexSelector] that originally 
  // contained each [SimpleSelector]. This allows us to ensure we don't trim any
  // selectors that need to exist to satisfy the [second law that of extend][].
  // [second law of extend]: https://github.com/sass/sass/issues/324#issuecomment-4607184
  // ##########################################################################
  std::unordered_map<
    SimpleSelectorObj,
    size_t,
    ObjPtrHash,
    ObjPtrEquality
  > sourceSpecificity;

  // ##########################################################################
  // A set of [ComplexSelector]s that were originally part of their
  // component [SelectorList]s, as opposed to being added by `@extend`.
  // This allows us to ensure that we don't trim any selectors
  // that need to exist to satisfy the [first law of extend][].
  // ##########################################################################
  ExtCplxSelSet originals;

public:

  // Constructor without default [mode].
  // [traces] are needed to throw errors.
  Extender(Backtraces& traces);

  // ##########################################################################
  // Constructor with specific [mode].
  // [traces] are needed to throw errors.
  // ##########################################################################
  Extender(ExtendMode mode, Backtraces& traces);

  // ##########################################################################
  // Empty desctructor
  // ##########################################################################
  ~Extender() {};

  // ##########################################################################
  // Extends [selector] with [source] extender and [targets] extendees.
  // This works as though `source {@extend target}` were written in the
  // stylesheet, with the exception that [target] can contain compound
  // selectors which must be extended as a unit.
  // ##########################################################################
  static SelectorListObj extend(
    SelectorListObj& selector,
    const SelectorListObj& source,
    const SelectorListObj& target,
    Backtraces& traces);

  // ##########################################################################
  // Returns a copy of [selector] with [targets] replaced by [source].
  // ##########################################################################
  static SelectorListObj replace(
    SelectorListObj& selector,
    const SelectorListObj& source,
    const SelectorListObj& target,
    Backtraces& traces);

  // ##########################################################################
  // Adds [selector] to this extender, with [selectorSpan] as the span covering
  // the selector and [ruleSpan] as the span covering the entire style rule.
  // Extends [selector] using any registered extensions, then returns an empty
  // [ModifiableCssStyleRule] with the resulting selector. If any more relevant
  // extensions are added, the returned rule is automatically updated.
  // The [mediaContext] is the media query context in which the selector was
  // defined, or `null` if it was defined at the top level of the document.
  // ##########################################################################
  void addSelector(
    const SelectorListObj& selector,
    const CssMediaRuleObj& mediaContext);

  // ##########################################################################
  // Registers the [SimpleSelector]s in [list]
  // to point to [rule] in [selectors].
  // ##########################################################################
  void registerSelector(
    const SelectorListObj& list,
    const SelectorListObj& rule);

  // ##########################################################################
  // Adds an extension to this extender. The [extender] is the selector for the
  // style rule in which the extension is defined, and [target] is the selector
  // passed to `@extend`. The [extend] provides the extend span and indicates 
  // whether the extension is optional. The [mediaContext] defines the media query
  // context in which the extension is defined. It can only extend selectors
  // within the same context. A `null` context indicates no media queries.
  // ##########################################################################
  void addExtension(
    const SelectorListObj& extender,
    const SimpleSelectorObj& target,
    const CssMediaRuleObj& mediaQueryContext,
    bool is_optional = false);

  // ##########################################################################
  // The set of all simple selectors in style rules handled
  // by this extender. This includes simple selectors that
  // were added because of downstream extensions.
  // ##########################################################################
  ExtSmplSelSet getSimpleSelectors() const;

  // ##########################################################################
  // Check for extends that have not been satisfied.
  // Returns true if any non-optional extension did not
  // extend any selector. Updates the passed reference
  // to point to that Extension for further analysis.
  // ##########################################################################
  bool checkForUnsatisfiedExtends(
    Extension& unsatisfied) const;

private:

  // ##########################################################################
  // A helper function for [extend] and [replace].
  // ##########################################################################
  static SelectorListObj extendOrReplace(
    SelectorListObj& selector,
    const SelectorListObj& source,
    const SelectorListObj& target,
    const ExtendMode mode,
    Backtraces& traces);

  // ##########################################################################
  // Returns an extension that combines [left] and [right]. Throws 
  // a [SassException] if [left] and [right] have incompatible 
  // media contexts. Throws an [ArgumentError] if [left]
  // and [right] don't have the same extender and target.
  // ##########################################################################
  static Extension mergeExtension(
    const Extension& lhs,
    const Extension& rhs);

  // ##########################################################################
  // Extend [extensions] using [newExtensions].
  // ##########################################################################
  // Note: dart-sass throws an error in here
  // ##########################################################################
  void extendExistingStyleRules(
    const ExtListSelSet& rules,
    const ExtSelExtMap& newExtensions);

  // ##########################################################################
  // Extend [extensions] using [newExtensions]. Note that this does duplicate
  // some work done by [_extendExistingStyleRules],  but it's necessary to
  // expand each extension's extender separately without reference to the full
  // selector list, so that relevant results don't get trimmed too early.
  // Returns `null` (Note: empty map) if there are no extensions to add.
  // ##########################################################################
  ExtSelExtMap extendExistingExtensions(
    // Taking in a reference here makes MSVC debug stuck!?
    const sass::vector<Extension>& extensions,
    const ExtSelExtMap& newExtensions);

  // ##########################################################################
  // Extends [list] using [extensions].
  // ##########################################################################
  SelectorListObj extendList(
    const SelectorListObj& list,
    const ExtSelExtMap& extensions,
    const CssMediaRuleObj& mediaContext);

  // ##########################################################################
  // Extends [complex] using [extensions], and
  // returns the contents of a [SelectorList].
  // ##########################################################################
  sass::vector<ComplexSelectorObj> extendComplex(
    // Taking in a reference here makes MSVC debug stuck!?
    const ComplexSelectorObj& list,
    const ExtSelExtMap& extensions,
    const CssMediaRuleObj& mediaQueryContext);

  // ##########################################################################
  // Returns a one-off [Extension] whose
  // extender is composed solely of [simple].
  // ##########################################################################
  Extension extensionForSimple(
    const SimpleSelectorObj& simple) const;

  // ##########################################################################
  // Returns a one-off [Extension] whose extender is composed
  // solely of a compound selector containing [simples].
  // ##########################################################################
  Extension extensionForCompound(
    // Taking in a reference here makes MSVC debug stuck!?
    const sass::vector<SimpleSelectorObj>& simples) const;

  // ##########################################################################
  // Extends [compound] using [extensions], and returns the
  // contents of a [SelectorList]. The [inOriginal] parameter
  // indicates whether this is in an original complex selector,
  // meaning that [compound] should not be trimmed out.
  // ##########################################################################
  sass::vector<ComplexSelectorObj> extendCompound(
    const CompoundSelectorObj& compound,
    const ExtSelExtMap& extensions,
    const CssMediaRuleObj& mediaQueryContext,
    bool inOriginal = false);

  // ##########################################################################
  // Extends [simple] without extending the
  // contents of any selector pseudos it contains.
  // ##########################################################################
  sass::vector<Extension> extendWithoutPseudo(
    const SimpleSelectorObj& simple,
    const ExtSelExtMap& extensions,
    ExtSmplSelSet* targetsUsed) const;

  // ##########################################################################
  // Extends [simple] and also extending the
  // contents of any selector pseudos it contains.
  // ##########################################################################
  sass::vector<sass::vector<Extension>> extendSimple(
    const SimpleSelectorObj& simple,
    const ExtSelExtMap& extensions,
    const CssMediaRuleObj& mediaQueryContext,
    ExtSmplSelSet* targetsUsed);

  // ##########################################################################
  // Inner loop helper for [extendPseudo] function
  // ##########################################################################
  static sass::vector<ComplexSelectorObj> extendPseudoComplex(
    const ComplexSelectorObj& complex,
    const PseudoSelectorObj& pseudo,
    const CssMediaRuleObj& mediaQueryContext);

  // ##########################################################################
  // Extends [pseudo] using [extensions], and returns
  // a list of resulting pseudo selectors.
  // ##########################################################################
  sass::vector<PseudoSelectorObj> extendPseudo(
    const PseudoSelectorObj& pseudo,
    const ExtSelExtMap& extensions,
    const CssMediaRuleObj& mediaQueryContext);

  // ##########################################################################
  // Rotates the element in list from [start] (inclusive) to [end] (exclusive)
  // one index higher, looping the final element back to [start].
  // ##########################################################################
  static void rotateSlice(
    sass::vector<ComplexSelectorObj>& list,
    size_t start, size_t end);

  // ##########################################################################
  // Removes elements from [selectors] if they're subselectors of other
  // elements. The [isOriginal] callback indicates which selectors are
  // original to the document, and thus should never be trimmed.
  // ##########################################################################
  sass::vector<ComplexSelectorObj> trim(
    const sass::vector<ComplexSelectorObj>& selectors,
    const ExtCplxSelSet& set) const;

  // ##########################################################################
  // Returns the maximum specificity of the given [simple] source selector.
  // ##########################################################################
  size_t maxSourceSpecificity(const SimpleSelectorObj& simple) const;

  // ##########################################################################
  // Returns the maximum specificity for sources that went into producing [compound].
  // ##########################################################################
  size_t maxSourceSpecificity(const CompoundSelectorObj& compound) const;

  // ##########################################################################
  // Helper function used as callbacks on lists
  // ##########################################################################
  static bool dontTrimComplex(
    const ComplexSelector* complex2,
    const ComplexSelector* complex1,
    const size_t maxSpecificity);

  // ##########################################################################
  // Helper function used as callbacks on lists
  // ##########################################################################
  static bool hasExactlyOne(const ComplexSelectorObj& vec);
  static bool hasMoreThanOne(const ComplexSelectorObj& vec);

};

}

endif