content: // Copyright 2016 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:charcode/charcode.dart';
  import 'package:source_span/source_span.dart';
  
  import '../../../interpolation_buffer.dart';
  // dart-lang/sdk#52535
  // ignore: unused_import
  import '../../../util/character.dart';
  import '../../../visitor/interface/expression.dart';
  import '../expression.dart';
  import '../interpolation.dart';
  
  /// A string literal.
  ///
  /// {@category AST}
  final class StringExpression extends Expression {
    /// Interpolation that, when evaluated, produces the contents of this string.
    ///
    /// If this is a quoted string, escapes are resolved and quotes are not
    /// included in this text (unlike [asInterpolation]). If it's an unquoted
    /// string, escapes are *not* resolved.
    final Interpolation text;
  
    /// Whether `this` has quotes.
    final bool hasQuotes;
  
    FileSpan get span => text.span;
  
    /// Returns Sass source for a quoted string that, when evaluated, will have
    /// [text] as its contents.
    static String quoteText(String text) {
      var quote = _bestQuote([text]);
      var buffer = StringBuffer();
      buffer.writeCharCode(quote);
      _quoteInnerText(text, quote, buffer, static: true);
      buffer.writeCharCode(quote);
      return buffer.toString();
    }
  
    StringExpression(this.text, {bool quotes = false}) : hasQuotes = quotes;
  
    /// Returns a string expression with no interpolation.
    StringExpression.plain(String text, FileSpan span, {bool quotes = false})
        : text = Interpolation.plain(text, span),
          hasQuotes = quotes;
  
    T accept<T>(ExpressionVisitor<T> visitor) =>
        visitor.visitStringExpression(this);
  
    /// Interpolation that, when evaluated, produces the syntax of this string.
    ///
    /// Unlike [text], his doesn't resolve escapes and does include quotes for
    /// quoted strings.
    ///
    /// If [static] is true, this escapes any `#{` sequences in the string. If
    /// [quote] is passed, it uses that character as the quote mark; otherwise, it
    /// determines the best quote to add by looking at the string.
    Interpolation asInterpolation({bool static = false, int? quote}) {
      if (!hasQuotes) return text;
  
      quote ??= _bestQuote(text.contents.whereType<String>());
      var buffer = InterpolationBuffer();
      buffer.writeCharCode(quote);
      for (var i = 0; i < text.contents.length; i++) {
        var value = text.contents[i];
        assert(value is Expression || value is String);
        switch (value) {
          case Expression():
            buffer.add(value, text.spanForElement(i));
          case String():
            _quoteInnerText(value, quote, buffer, static: static);
        }
      }
      buffer.writeCharCode(quote);
  
      return buffer.interpolation(text.span);
    }
  
    /// Writes to [buffer] the contents of a string (without quotes) that
    /// evaluates to [text] according to Sass's parsing logic.
    ///
    /// This always adds an escape sequence before [quote]. If [static] is true,
    /// it also escapes any `#{` sequences in the string.
    static void _quoteInnerText(
      String text,
      int quote,
      StringSink buffer, {
      bool static = false,
    }) {
      for (var i = 0; i < text.length; i++) {
        switch (text.codeUnitAt(i)) {
          case int(isNewline: true):
            buffer.writeCharCode($backslash);
            buffer.writeCharCode($a);
            if (i != text.length - 1) {
              if (text.codeUnitAt(i + 1)
                  case int(isWhitespace: true) || int(isHex: true)) {
                buffer.writeCharCode($space);
              }
            }
  
          case $backslash && var codeUnit:
          case var codeUnit when codeUnit == quote:
          case $hash && var codeUnit
              when static &&
                  i < text.length - 1 &&
                  text.codeUnitAt(i + 1) == $lbrace:
            buffer.writeCharCode($backslash);
            buffer.writeCharCode(codeUnit);
  
          case var codeUnit:
            buffer.writeCharCode(codeUnit);
        }
      }
    }
  
    /// Returns the code unit for the best quote to use when converting [strings]
    /// to Sass source.
    static int _bestQuote(Iterable<String> strings) {
      var containsDoubleQuote = false;
      for (var value in strings) {
        for (var codeUnit in value.codeUnits) {
          if (codeUnit == $single_quote) return $double_quote;
          if (codeUnit == $double_quote) containsDoubleQuote = true;
        }
      }
      return containsDoubleQuote ? $single_quote : $double_quote;
    }
  
    String toString() => asInterpolation().toString();
  }
  