content: // Copyright 2019 Google Inc. 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 'package:collection/collection.dart';
  import 'package:source_span/source_span.dart';
  
  import '../../../util/span.dart';
  import '../../../visitor/interface/statement.dart';
  import '../configured_variable.dart';
  import '../dependency.dart';
  import '../expression/string.dart';
  import '../statement.dart';
  
  /// A `@forward` rule.
  ///
  /// {@category AST}
  final class ForwardRule extends Statement implements SassDependency {
    /// The URI of the module to forward.
    ///
    /// If this is relative, it's relative to the containing file.
    final Uri url;
  
    /// The set of mixin and function names that may be accessed from the
    /// forwarded module.
    ///
    /// If this is empty, no mixins or functions may be accessed. If it's `null`,
    /// it imposes no restrictions on which mixins and function may be accessed.
    ///
    /// If this is non-`null`, [hiddenMixinsAndFunctions] and [hiddenVariables]
    /// are guaranteed to both be `null` and [shownVariables] is guaranteed to be
    /// non-`null`.
    final Set<String>? shownMixinsAndFunctions;
  
    /// The set of variable names (without `$`) that may be accessed from the
    /// forwarded module.
    ///
    /// If this is empty, no variables may be accessed. If it's `null`, it imposes
    /// no restrictions on which variables may be accessed.
    ///
    /// If this is non-`null`, [hiddenMixinsAndFunctions] and [hiddenVariables]
    /// are guaranteed to both be `null` and [shownMixinsAndFunctions] is
    /// guaranteed to be non-`null`.
    final Set<String>? shownVariables;
  
    /// The set of mixin and function names that may not be accessed from the
    /// forwarded module.
    ///
    /// If this is empty, any mixins or functions may be accessed. If it's `null`,
    /// it imposes no restrictions on which mixins or functions may be accessed.
    ///
    /// If this is non-`null`, [shownMixinsAndFunctions] and [shownVariables] are
    /// guaranteed to both be `null` and [hiddenVariables] is guaranteed to be
    /// non-`null`.
    final Set<String>? hiddenMixinsAndFunctions;
  
    /// The set of variable names (without `$`) that may be accessed from the
    /// forwarded module.
    ///
    /// If this is empty, any variables may be accessed. If it's `null`, it
    /// imposes no restrictions on which variables may be accessed.
    ///
    /// If this is non-`null`, [shownMixinsAndFunctions] and [shownVariables] are
    /// guaranteed to both be `null` and [hiddenMixinsAndFunctions] is guaranteed
    /// to be non-`null`.
    final Set<String>? hiddenVariables;
  
    /// The prefix to add to the beginning of the names of members of the used
    /// module, or `null` if member names are used as-is.
    final String? prefix;
  
    /// A list of variable assignments used to configure the loaded modules.
    final List<ConfiguredVariable> configuration;
  
    final FileSpan span;
  
    FileSpan get urlSpan => span.withoutInitialAtRule().initialQuoted();
  
    /// Creates a `@forward` rule that allows all members to be accessed.
    ForwardRule(
      this.url,
      this.span, {
      this.prefix,
      Iterable<ConfiguredVariable>? configuration,
    })  : shownMixinsAndFunctions = null,
          shownVariables = null,
          hiddenMixinsAndFunctions = null,
          hiddenVariables = null,
          configuration =
              configuration == null ? const [] : List.unmodifiable(configuration);
  
    /// Creates a `@forward` rule that allows only members included in
    /// [shownMixinsAndFunctions] and [shownVariables] to be accessed.
    ForwardRule.show(
      this.url,
      Iterable<String> shownMixinsAndFunctions,
      Iterable<String> shownVariables,
      this.span, {
      this.prefix,
      Iterable<ConfiguredVariable>? configuration,
    })  : shownMixinsAndFunctions = UnmodifiableSetView(
            Set.of(shownMixinsAndFunctions),
          ),
          shownVariables = UnmodifiableSetView(Set.of(shownVariables)),
          hiddenMixinsAndFunctions = null,
          hiddenVariables = null,
          configuration =
              configuration == null ? const [] : List.unmodifiable(configuration);
  
    /// Creates a `@forward` rule that allows only members not included in
    /// [hiddenMixinsAndFunctions] and [hiddenVariables] to be accessed.
    ForwardRule.hide(
      this.url,
      Iterable<String> hiddenMixinsAndFunctions,
      Iterable<String> hiddenVariables,
      this.span, {
      this.prefix,
      Iterable<ConfiguredVariable>? configuration,
    })  : shownMixinsAndFunctions = null,
          shownVariables = null,
          hiddenMixinsAndFunctions = UnmodifiableSetView(
            Set.of(hiddenMixinsAndFunctions),
          ),
          hiddenVariables = UnmodifiableSetView(Set.of(hiddenVariables)),
          configuration =
              configuration == null ? const [] : List.unmodifiable(configuration);
  
    T accept<T>(StatementVisitor<T> visitor) => visitor.visitForwardRule(this);
  
    String toString() {
      var buffer = StringBuffer(
        "@forward ${StringExpression.quoteText(url.toString())}",
      );
  
      var shownMixinsAndFunctions = this.shownMixinsAndFunctions;
      var hiddenMixinsAndFunctions = this.hiddenMixinsAndFunctions;
      if (shownMixinsAndFunctions != null) {
        buffer
          ..write(" show ")
          ..write(_memberList(shownMixinsAndFunctions, shownVariables!));
      } else if (hiddenMixinsAndFunctions != null &&
          hiddenMixinsAndFunctions.isNotEmpty) {
        buffer
          ..write(" hide ")
          ..write(_memberList(hiddenMixinsAndFunctions, hiddenVariables!));
      }
  
      var prefix = this.prefix;
      if (prefix != null) buffer.write(" as $prefix*");
  
      if (configuration.isNotEmpty) {
        buffer.write(" with (${configuration.join(", ")})");
      }
  
      buffer.write(";");
      return buffer.toString();
    }
  
    /// Returns a combined list of names of the given members.
    String _memberList(
      Iterable<String> mixinsAndFunctions,
      Iterable<String> variables,
    ) =>
        [...mixinsAndFunctions, for (var name in variables) "\$$name"].join(", ");
  }
  