// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/src/clients/dart_style/rewrite_cascade.dart';
import 'package:analyzer/src/test_utilities/find_node.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(RewriteCascadeTest);
  });
}

@reflectiveTest
class RewriteCascadeTest {
  test_fixCascadeByParenthesizingTarget() {
    const pairs = {
      'c ? a : b..method();': '(c ? a : b)..method();',
      'a ?? b..method();': '(a ?? b)..method();',
      'a && b..method();': '(a && b)..method();',
      'a || b..method();': '(a || b)..method();',
      'a == b..method();': '(a == b)..method();',
      'a != b..method();': '(a != b)..method();',
      'a < b..method();': '(a < b)..method();',
      'a > b..method();': '(a > b)..method();',
      'a <= b..method();': '(a <= b)..method();',
      'a >= b..method();': '(a >= b)..method();',
      'a ^ b..method();': '(a ^ b)..method();',
      'a | b..method();': '(a | b)..method();',
      'a << b..method();': '(a << b)..method();',
      'a >> b..method();': '(a >> b)..method();',
      'a + b..method();': '(a + b)..method();',
      'a - b..method();': '(a - b)..method();',
      'a * b..method();': '(a * b)..method();',
      'a / b..method();': '(a / b)..method();',
      'a ~/ b..method();': '(a ~/ b)..method();',
      '-a..method();': '(-a)..method();',
      '!a..method();': '(!a)..method();',
      '~a..method();': '(~a)..method();',
      'a++..method();': '(a++)..method();',
      'a--..method();': '(a--)..method();',
    };

    void assertSingle({required String input, required String expected}) {
      var statement = _parseStringToFindNode('''
void f() {
  $input
}
''').expressionStatement(input);
      var result = fixCascadeByParenthesizingTarget(
        expressionStatement: statement,
        cascadeExpression: statement.expression as CascadeExpression,
      );
      expect(result.toSource(), expected);
      expect(result.semicolon, same(statement.semicolon));
    }

    for (var entry in pairs.entries) {
      assertSingle(input: entry.key, expected: entry.value);
    }
  }

  test_insertCascadeTargetIntoExpression() {
    void assertSingle({required String input, required String expected}) {
      var statement = _parseStringToFindNode('''
void f() {
  $input;
    }
    ''').expressionStatement(input);
      var cascadeExpression = statement.expression as CascadeExpression;
      var result = insertCascadeTargetIntoExpression(
        expression: cascadeExpression.cascadeSections.single,
        cascadeTarget: cascadeExpression.target,
      );
      expect(result.toSource(), expected);
    }

    const pairs = {
      'obj..method()': 'obj.method()',
      'obj..getter': 'obj.getter',
      'obj..setter = 3': 'obj.setter = 3',
      'obj..[subscript] = 3': 'obj[subscript] = 3',
      'obj?..[subscript] = 3': 'obj?[subscript] = 3',
      'obj..index[subscript] = 3': 'obj.index[subscript] = 3',
      'obj..index?[subscript] = 3': 'obj.index?[subscript] = 3',
      // Nested
      'obj..foo().bar().method()': 'obj.foo().bar().method()',
      'obj..foo.bar.getter': 'obj.foo.bar.getter',
      'obj..foo.bar.setter = 0': 'obj.foo.bar.setter = 0',
    };

    for (var entry in pairs.entries) {
      assertSingle(input: entry.key, expected: entry.value);
    }
  }

  FindNode _parseStringToFindNode(String content) {
    var parseResult = parseString(
      content: content,
      featureSet: FeatureSet.latestLanguageVersion(),
    );
    return FindNode(parseResult.content, parseResult.unit);
  }
}
