import 'package:meta/meta.dart';

import '../../core/parser.dart';
import '../combinator/optional.dart';
import '../combinator/sequence.dart';
import '../misc/epsilon.dart';
import '../repeater/possessive.dart';

extension SeparatedByParserExtension<T> on Parser<T> {
  /// Returns a parser that consumes the receiver one or more times separated
  /// by the [separator] parser. The resulting parser returns a flat list of
  /// the parse results of the receiver interleaved with the parse result of the
  /// separator parser. The type parameter `R` defines the type of the returned
  /// list.
  ///
  /// If the optional argument [includeSeparators] is set to `false`, then the
  /// separators themselves are not included in the parse result. If the
  /// optional argument [optionalSeparatorAtStart] or [optionalSeparatorAtEnd]
  /// is set to `true` the parser also accepts optional separators at the start
  /// and/or end.
  ///
  /// For example, the parser `digit().separatedBy(char('-'))` returns a parser
  /// that consumes input like `'1-2-3'` and returns a list of the elements and
  /// separators: `['1', '-', '2', '-', '3']`.
  @Deprecated('Use `SeparatedRepeatingParserExtension.plusSeparated` for a '
      'better optimized and strongly typed implementation that provides the '
      'elements and separators separately')
  @useResult
  Parser<List<R>> separatedBy<R>(
    Parser separator, {
    bool includeSeparators = true,
    bool optionalSeparatorAtStart = false,
    bool optionalSeparatorAtEnd = false,
  }) =>
      seq4(
              optionalSeparatorAtStart
                  ? separator.optional()
                  : epsilonWith(null),
              this,
              seq2(separator, this).star(),
              optionalSeparatorAtEnd ? separator.optional() : epsilonWith(null))
          .map4(
        (separatorAtStart, firstElement, otherElements, separatorAtEnd) {
          final result = <R>[];
          if (includeSeparators &&
              optionalSeparatorAtStart &&
              separatorAtStart != null) {
            result.add(separatorAtStart as R);
          }
          result.add(firstElement as R);
          for (final tuple in otherElements) {
            if (includeSeparators) {
              result.add(tuple.first as R);
            }
            result.add(tuple.second as R);
          }
          if (includeSeparators &&
              optionalSeparatorAtEnd &&
              separatorAtEnd != null) {
            result.add(separatorAtEnd as R);
          }
          return result;
        },
      );
}
