// Copyright (c) 2015, 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/src/util/yaml.dart';
import 'package:source_span/source_span.dart';
import 'package:test/test.dart';
import 'package:yaml/src/event.dart';
import 'package:yaml/yaml.dart';

main() {
  group('yaml', () {
    group('merge', () {
      test('map', () {
        expect(
          merge(
            {
              'one': true,
              'two': false,
              'three': {
                'nested': {'four': true, 'six': true},
              },
            },
            {
              'three': {
                'nested': {'four': false, 'five': true},
                'five': true,
              },
              'seven': true,
            },
          ),
          equals(
            wrap({
              'one': true,
              'two': false,
              'three': {
                'nested': {'four': false, 'five': true, 'six': true},
                'five': true,
              },
              'seven': true,
            }),
          ),
        );
      });

      test('list', () {
        expect(merge([1, 2, 3], [2, 3, 4, 5]), equals(wrap([1, 2, 3, 4, 5])));
      });

      test('list w/ promotion', () {
        expect(
          merge(['one', 'two', 'three'], {'three': false, 'four': true}),
          equals(
            wrap({'one': true, 'two': true, 'three': false, 'four': true}),
          ),
        );
        expect(
          merge({'one': false, 'two': false}, ['one', 'three']),
          equals(wrap({'one': true, 'two': false, 'three': true})),
        );
      });

      test('map w/ list promotion', () {
        var map1 = {
          'one': ['a', 'b', 'c'],
        };
        var map2 = {
          'one': {'a': true, 'b': false},
        };
        var map3 = {
          'one': {'a': true, 'b': false, 'c': true},
        };
        expect(merge(map1, map2), wrap(map3));
      });

      test('map w/ no promotion', () {
        var map1 = {
          'one': ['a', 'b', 'c'],
        };
        var map2 = {
          'one': {'a': 'foo', 'b': 'bar'},
        };
        var map3 = {
          'one': {'a': 'foo', 'b': 'bar'},
        };
        expect(merge(map1, map2), wrap(map3));
      });

      test('map w/ no promotion (2)', () {
        var map1 = {
          'one': {'a': 'foo', 'b': 'bar'},
        };
        var map2 = {
          'one': ['a', 'b', 'c'],
        };
        var map3 = {
          'one': ['a', 'b', 'c'],
        };
        expect(merge(map1, map2), wrap(map3));
      });

      test('object', () {
        expect(merge(1, 2), 2);
        expect(merge(1, 'foo'), 'foo');
        expect(merge({'foo': 1}, 'foo'), 'foo');
      });

      test('merged order directly from yaml', () {
        YamlMap loadYaml(String content) {
          return loadYamlNode(content) as YamlMap;
        }

        var aYaml = """foo:
          a: bar""";
        var bYaml = """foo:
          b: baz""";

        var merge1Value = mergeYamlMaps(
          loadYaml(bYaml),
          loadYaml(aYaml),
        ).valueAt("foo").toString();
        for (int i = 0; i < 100; i++) {
          var merge2Value = mergeYamlMaps(
            loadYaml(bYaml),
            loadYaml(aYaml),
          ).valueAt("foo").toString();
          expect(merge2Value, merge1Value);
        }
      });

      test('merged w/promotion order directly from yaml', () {
        YamlMap loadYaml(String content) {
          return loadYamlNode(content) as YamlMap;
        }

        var aYaml = """foo:
          - a
          - b
          - c
          - d""";
        var bYaml = """foo:
          b: true""";

        var merge1Value = mergeYamlMaps(
          loadYaml(bYaml),
          loadYaml(aYaml),
        ).valueAt("foo").toString();
        for (int i = 0; i < 100; i++) {
          var merge2Value = mergeYamlMaps(
            loadYaml(bYaml),
            loadYaml(aYaml),
          ).valueAt("foo").toString();
          expect(merge2Value, merge1Value);
        }
      });
    });
  });
}

final Merger merger = Merger();

Object merge(Object o1, Object o2) =>
    merger.merge(wrap(o1), wrap(o2)).valueOrThrow;

YamlMap mergeYamlMaps(YamlMap defaults, YamlMap overrides) =>
    Merger().mergeMap(defaults, overrides);

YamlNode wrap(Object? value) {
  if (value is List) {
    var wrappedElements = value.map((e) => wrap(e)).toList();
    return YamlList.internal(
      wrappedElements,
      _FileSpanMock.instance,
      CollectionStyle.BLOCK,
    );
  } else if (value is Map) {
    Map<dynamic, YamlNode> wrappedEntries = <dynamic, YamlNode>{};
    value.forEach((k, v) {
      wrappedEntries[wrap(k)] = wrap(v);
    });
    return YamlMap.internal(
      wrappedEntries,
      _FileSpanMock.instance,
      CollectionStyle.BLOCK,
    );
  } else {
    return YamlScalar.internal(
      value,
      ScalarEvent(_FileSpanMock.instance, '', ScalarStyle.PLAIN),
    );
  }
}

class _FileSpanMock implements FileSpan {
  static final FileSpan instance = _FileSpanMock();

  @override
  noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
