content: // Copyright 2019 Google LLC. Use of this source code is governed by an
  // MIT-style license that can be found in the LICENSE file or at
  // https://opensource.org/licenses/MIT.
  
  import 'dart:collection';
  import 'ast/node.dart';
  import 'ast/sass.dart';
  import 'configured_value.dart';
  import 'util/limited_map_view.dart';
  import 'util/map.dart';
  import 'util/unprefixed_map_view.dart';
  
  /// A set of variables meant to configure a module by overriding its
  /// `!default` declarations.
  ///
  /// A configuration may be either *implicit*, meaning that it's either empty or
  /// created by importing a file containing a `@forward` rule; or *explicit*,
  /// meaning that it's created by passing a `with` clause to a `@use` rule.
  /// Explicit configurations have spans associated with them and are represented
  /// by the [ExplicitConfiguration] subclass.
  final class Configuration {
    /// A map from variable names (without `$`) to values.
    ///
    /// This map may not be modified directly. To remove a value from this
    /// configuration, use the [remove] method.
    Map<String, ConfiguredValue> get values => UnmodifiableMapView(_values);
    final Map<String, ConfiguredValue> _values;
  
    /// Creates an implicit configuration with the given [values].
    Configuration.implicit(this._values) : __originalConfiguration = null;
  
    /// The backing value for [_originalConfiguration].
    ///
    /// This is null if [_originalConfiguration] refers to itself since `this`
    /// can't be assigned to a final field.
    final Configuration? __originalConfiguration;
  
    /// The configuration from which this was modified with `@forward ... with`.
    ///
    /// This reference serves as an opaque ID.
    Configuration get _originalConfiguration => __originalConfiguration ?? this;
  
    /// Returns whether `this` and [that] [Configuration]s have the same
    /// [_originalConfiguration].
    ///
    /// An implicit configuration will always return `false` because it was not
    /// created through another configuration.
    ///
    /// [ExplicitConfiguration]s and configurations created [throughForward]
    /// will be considered to have the same original config if they were created
    /// as a copy from the same base configuration.
    bool sameOriginal(Configuration that) =>
        identical(_originalConfiguration, that._originalConfiguration);
  
    /// The empty configuration, which indicates that the module has not been
    /// configured.
    ///
    /// Empty configurations are always considered implicit, since they are
    /// ignored if the module has already been loaded.
    const Configuration.empty()
        : _values = const {},
          __originalConfiguration = null;
  
    bool get isEmpty => values.isEmpty;
  
    /// Removes a variable with [name] from this configuration, returning it.
    ///
    /// If no such variable exists in this configuration, returns null.
    ConfiguredValue? remove(String name) => isEmpty ? null : _values.remove(name);
  
    /// Creates a new configuration from this one based on a `@forward` rule.
    Configuration throughForward(ForwardRule forward) {
      if (isEmpty) return this;
      var newValues = _values;
  
      // Only allow variables that are visible through the `@forward` to be
      // configured. These views support [Map.remove] so we can mark when a
      // configuration variable is used by removing it even when the underlying
      // map is wrapped.
      if (forward.prefix case var prefix?) {
        newValues = UnprefixedMapView(newValues, prefix);
      }
  
      if (forward.shownVariables case var shownVariables?) {
        newValues = LimitedMapView.safelist(newValues, shownVariables);
      } else if (forward.hiddenVariables case var hiddenVariables?
          when hiddenVariables.isNotEmpty) {
        newValues = LimitedMapView.blocklist(newValues, hiddenVariables);
      }
      return _withValues(newValues);
    }
  
    /// Returns a copy of `this` [Configuration] with the given [values] map.
    ///
    /// The copy will have the same [_originalConfiguration] as `this` config.
    Configuration _withValues(Map<String, ConfiguredValue> values) =>
        Configuration._(values, _originalConfiguration);
  
    /// Creates a [Configuration] with the given [_values] map and an
    /// [_originalConfiguration] reference.
    Configuration._(this._values, this.__originalConfiguration);
  
    String toString() =>
        "(" +
        [for (var (name, value) in values.pairs) "\$$name: $value"].join(",") +
        ")";
  }
  
  /// A [Configuration] that was created with an explicit `with` clause of a
  /// `@use` rule.
  ///
  /// Both types of configuration pass through `@forward` rules, but explicit
  /// configurations will cause an error if attempting to use them on a module
  /// that has already been loaded, while implicit configurations will be
  /// silently ignored in this case.
  final class ExplicitConfiguration extends Configuration {
    /// The node whose span indicates where the configuration was declared.
    final AstNode nodeWithSpan;
  
    /// Creates a base [ExplicitConfiguration] with a [values] map and a
    /// [nodeWithSpan].
    ExplicitConfiguration(super.values, this.nodeWithSpan) : super.implicit();
  
    /// Creates an [ExplicitConfiguration] with a [values] map, a [nodeWithSpan]
    /// and if this is a copy a reference to the [_originalConfiguration].
    ExplicitConfiguration._(
      Map<String, ConfiguredValue> values,
      this.nodeWithSpan,
      Configuration? originalConfiguration,
    ) : super._(values, originalConfiguration);
  
    /// Returns a copy of `this` with the given [values] map.
    ///
    /// The copy will have the same [_originalConfiguration] as `this` config.
    Configuration _withValues(Map<String, ConfiguredValue> values) =>
        ExplicitConfiguration._(values, nodeWithSpan, _originalConfiguration);
  }
  