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 '../ast/css.dart';
  import '../utils.dart';
  import 'parser.dart';
  
  /// A parser for `@media` queries.
  class MediaQueryParser extends Parser {
    MediaQueryParser(super.contents, {super.url, super.interpolationMap});
  
    List<CssMediaQuery> parse() {
      return wrapSpanFormatException(() {
        var queries = <CssMediaQuery>[];
        do {
          _whitespace();
          queries.add(_mediaQuery());
          _whitespace();
        } while (scanner.scanChar($comma));
        scanner.expectDone();
        return queries;
      });
    }
  
    /// Consumes a single media query.
    CssMediaQuery _mediaQuery() {
      // This is somewhat duplicated in StylesheetParser._mediaQuery.
      if (scanner.peekChar() == $lparen) {
        var conditions = [_mediaInParens()];
        _whitespace();
  
        var conjunction = true;
        if (scanIdentifier("and")) {
          expectWhitespace();
          conditions.addAll(_mediaLogicSequence("and"));
        } else if (scanIdentifier("or")) {
          expectWhitespace();
          conjunction = false;
          conditions.addAll(_mediaLogicSequence("or"));
        }
  
        return CssMediaQuery.condition(conditions, conjunction: conjunction);
      }
  
      String? modifier;
      String? type;
      var identifier1 = identifier();
  
      if (equalsIgnoreCase(identifier1, "not")) {
        expectWhitespace();
        if (!lookingAtIdentifier()) {
          // For example, "@media not (...) {"
          return CssMediaQuery.condition(["(not ${_mediaInParens()})"]);
        }
      }
  
      _whitespace();
      if (!lookingAtIdentifier()) {
        // For example, "@media screen {"
        return CssMediaQuery.type(identifier1);
      }
  
      var identifier2 = identifier();
  
      if (equalsIgnoreCase(identifier2, "and")) {
        expectWhitespace();
        // For example, "@media screen and ..."
        type = identifier1;
      } else {
        _whitespace();
        modifier = identifier1;
        type = identifier2;
        if (scanIdentifier("and")) {
          // For example, "@media only screen and ..."
          expectWhitespace();
        } else {
          // For example, "@media only screen {"
          return CssMediaQuery.type(type, modifier: modifier);
        }
      }
  
      // We've consumed either `IDENTIFIER "and"` or
      // `IDENTIFIER IDENTIFIER "and"`.
  
      if (scanIdentifier("not")) {
        // For example, "@media screen and not (...) {"
        expectWhitespace();
        return CssMediaQuery.type(
          type,
          modifier: modifier,
          conditions: ["(not ${_mediaInParens()})"],
        );
      }
  
      return CssMediaQuery.type(
        type,
        modifier: modifier,
        conditions: _mediaLogicSequence("and"),
      );
    }
  
    /// Consumes one or more `<media-in-parens>` expressions separated by
    /// [operator] and returns them.
    List<String> _mediaLogicSequence(String operator) {
      var result = <String>[];
      while (true) {
        result.add(_mediaInParens());
        _whitespace();
  
        if (!scanIdentifier(operator)) return result;
        expectWhitespace();
      }
    }
  
    /// Consumes a `<media-in-parens>` expression and returns it, parentheses
    /// included.
    String _mediaInParens() {
      scanner.expectChar($lparen, name: "media condition in parentheses");
      var result = "(${declarationValue()})";
      scanner.expectChar($rparen);
      return result;
    }
  
    /// The value of `consumeNewlines` is not relevant for this class.
    void _whitespace() {
      whitespace(consumeNewlines: true);
    }
  }
  