// Copyright (c) 2014, 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.

// ignore_for_file: prefer_interpolation_to_compose_strings

import 'package:source_span/source_span.dart';
import 'package:source_span/src/colors.dart' as colors;
import 'package:term_glyph/term_glyph.dart' as glyph;
import 'package:test/test.dart';

void main() {
  late bool oldAscii;
  setUpAll(() {
    oldAscii = glyph.ascii;
    glyph.ascii = true;
  });

  tearDownAll(() {
    glyph.ascii = oldAscii;
  });

  late SourceFile file;
  setUp(() {
    file = SourceFile.fromString('''
foo bar baz
whiz bang boom
zip zap zop
''');
  });

  test('points to the span in the source', () {
    expect(file.span(4, 7).highlight(), equals("""
  ,
1 | foo bar baz
  |     ^^^
  '"""));
  });

  test('gracefully handles a missing source URL', () {
    final span = SourceFile.fromString('foo bar baz').span(4, 7);
    expect(span.highlight(), equals("""
  ,
1 | foo bar baz
  |     ^^^
  '"""));
  });

  group('highlights a point span', () {
    test('in the middle of a line', () {
      expect(file.location(4).pointSpan().highlight(), equals("""
  ,
1 | foo bar baz
  |     ^
  '"""));
    });

    test('at the beginning of the file', () {
      expect(file.location(0).pointSpan().highlight(), equals("""
  ,
1 | foo bar baz
  | ^
  '"""));
    });

    test('at the beginning of a line', () {
      expect(file.location(12).pointSpan().highlight(), equals("""
  ,
2 | whiz bang boom
  | ^
  '"""));
    });

    test('at the end of a line', () {
      expect(file.location(11).pointSpan().highlight(), equals("""
  ,
1 | foo bar baz
  |            ^
  '"""));
    });

    test('at the end of the file', () {
      expect(file.location(38).pointSpan().highlight(), equals("""
  ,
3 | zip zap zop
  |            ^
  '"""));
    });

    test('after the end of the file', () {
      expect(file.location(39).pointSpan().highlight(), equals("""
  ,
4 | 
  | ^
  '"""));
    });

    test('at the end of the file with no trailing newline', () {
      file = SourceFile.fromString('zip zap zop');
      expect(file.location(10).pointSpan().highlight(), equals("""
  ,
1 | zip zap zop
  |           ^
  '"""));
    });

    test('after the end of the file with no trailing newline', () {
      file = SourceFile.fromString('zip zap zop');
      expect(file.location(11).pointSpan().highlight(), equals("""
  ,
1 | zip zap zop
  |            ^
  '"""));
    });

    test('in an empty file', () {
      expect(SourceFile.fromString('').location(0).pointSpan().highlight(),
          equals("""
  ,
1 | 
  | ^
  '"""));
    });

    test('on an empty line', () {
      final file = SourceFile.fromString('foo\n\nbar');
      expect(file.location(4).pointSpan().highlight(), equals("""
  ,
2 | 
  | ^
  '"""));
    });
  });

  test('highlights a single-line file without a newline', () {
    expect(SourceFile.fromString('foo bar').span(0, 7).highlight(), equals("""
  ,
1 | foo bar
  | ^^^^^^^
  '"""));
  });

  test('highlights text including a trailing newline', () {
    expect(file.span(8, 12).highlight(), equals("""
  ,
1 | foo bar baz
  |         ^^^
  '"""));
  });

  test('highlights a single empty line', () {
    expect(
        SourceFile.fromString('foo\n\nbar').span(4, 5).highlight(), equals("""
  ,
2 | 
  | ^
  '"""));
  });

  test('highlights a trailing newline', () {
    expect(file.span(11, 12).highlight(), equals("""
  ,
1 | foo bar baz
  |            ^
  '"""));
  });

  group('with a multiline span', () {
    test('highlights the middle of the first and last lines', () {
      expect(file.span(4, 34).highlight(), equals("""
  ,
1 |   foo bar baz
  | ,-----^
2 | | whiz bang boom
3 | | zip zap zop
  | '-------^
  '"""));
    });

    test('works when it begins at the end of a line', () {
      expect(file.span(11, 34).highlight(), equals("""
  ,
1 |   foo bar baz
  | ,------------^
2 | | whiz bang boom
3 | | zip zap zop
  | '-------^
  '"""));
    });

    test('works when it ends at the beginning of a line', () {
      expect(file.span(4, 28).highlight(), equals("""
  ,
1 |   foo bar baz
  | ,-----^
2 | | whiz bang boom
3 | | zip zap zop
  | '-^
  '"""));
    });

    test('highlights the full first line', () {
      expect(file.span(0, 34).highlight(), equals("""
  ,
1 | / foo bar baz
2 | | whiz bang boom
3 | | zip zap zop
  | '-------^
  '"""));
    });

    test("highlights the full first line even if it's indented", () {
      final file = SourceFile.fromString('''
  foo bar baz
  whiz bang boom
  zip zap zop
''');

      expect(file.span(2, 38).highlight(), equals("""
  ,
1 | /   foo bar baz
2 | |   whiz bang boom
3 | |   zip zap zop
  | '-------^
  '"""));
    });

    test("highlights the full first line if it's empty", () {
      final file = SourceFile.fromString('''
foo

bar
''');

      expect(file.span(4, 9).highlight(), equals(r"""
  ,
2 | / 
3 | \ bar
  '"""));
    });

    test('highlights the full last line', () {
      expect(file.span(4, 27).highlight(), equals(r"""
  ,
1 |   foo bar baz
  | ,-----^
2 | \ whiz bang boom
  '"""));
    });

    test('highlights the full last line with no trailing newline', () {
      expect(file.span(4, 26).highlight(), equals(r"""
  ,
1 |   foo bar baz
  | ,-----^
2 | \ whiz bang boom
  '"""));
    });

    test('highlights the full last line with a trailing Windows newline', () {
      final file = SourceFile.fromString('''
foo bar baz\r
whiz bang boom\r
zip zap zop\r
''');

      expect(file.span(4, 29).highlight(), equals(r"""
  ,
1 |   foo bar baz
  | ,-----^
2 | \ whiz bang boom
  '"""));
    });

    test('highlights the full last line at the end of the file', () {
      expect(file.span(4, 39).highlight(), equals(r"""
  ,
1 |   foo bar baz
  | ,-----^
2 | | whiz bang boom
3 | \ zip zap zop
  '"""));
    });

    test(
        'highlights the full last line at the end of the file with no trailing '
        'newline', () {
      final file = SourceFile.fromString('''
foo bar baz
whiz bang boom
zip zap zop''');

      expect(file.span(4, 38).highlight(), equals(r"""
  ,
1 |   foo bar baz
  | ,-----^
2 | | whiz bang boom
3 | \ zip zap zop
  '"""));
    });

    test("highlights the full last line if it's empty", () {
      final file = SourceFile.fromString('''
foo

bar
''');

      expect(file.span(0, 5).highlight(), equals(r"""
  ,
1 | / foo
2 | \ 
  '"""));
    });

    test('highlights multiple empty lines', () {
      final file = SourceFile.fromString('foo\n\n\n\nbar');
      expect(file.span(4, 7).highlight(), equals(r"""
  ,
2 | / 
3 | | 
4 | \ 
  '"""));
    });

    // Regression test for #32
    test('highlights the end of a line and an empty line', () {
      final file = SourceFile.fromString('foo\n\n');
      expect(file.span(3, 5).highlight(), equals(r"""
  ,
1 |   foo
  | ,----^
2 | \ 
  '"""));
    });
  });

  group('prints tabs as spaces', () {
    group('in a single-line span', () {
      test('before the highlighted section', () {
        final span = SourceFile.fromString('foo\tbar baz').span(4, 7);

        expect(span.highlight(), equals("""
  ,
1 | foo    bar baz
  |        ^^^
  '"""));
      });

      test('within the highlighted section', () {
        final span = SourceFile.fromString('foo bar\tbaz bang').span(4, 11);

        expect(span.highlight(), equals("""
  ,
1 | foo bar    baz bang
  |     ^^^^^^^^^^
  '"""));
      });

      test('after the highlighted section', () {
        final span = SourceFile.fromString('foo bar\tbaz').span(4, 7);

        expect(span.highlight(), equals("""
  ,
1 | foo bar    baz
  |     ^^^
  '"""));
      });
    });

    group('in a multi-line span', () {
      test('before the highlighted section', () {
        final span = SourceFile.fromString('''
foo\tbar baz
whiz bang boom
''').span(4, 21);

        expect(span.highlight(), equals("""
  ,
1 |   foo    bar baz
  | ,--------^
2 | | whiz bang boom
  | '---------^
  '"""));
      });

      test('within the first highlighted line', () {
        final span = SourceFile.fromString('''
foo bar\tbaz
whiz bang boom
''').span(4, 21);

        expect(span.highlight(), equals("""
  ,
1 |   foo bar    baz
  | ,-----^
2 | | whiz bang boom
  | '---------^
  '"""));
      });

      test('at the beginning of the first highlighted line', () {
        final span = SourceFile.fromString('''
foo bar\tbaz
whiz bang boom
''').span(7, 21);

        expect(span.highlight(), equals("""
  ,
1 |   foo bar    baz
  | ,--------^
2 | | whiz bang boom
  | '---------^
  '"""));
      });

      test('within a middle highlighted line', () {
        final span = SourceFile.fromString('''
foo bar baz
whiz\tbang boom
zip zap zop
''').span(4, 34);

        expect(span.highlight(), equals("""
  ,
1 |   foo bar baz
  | ,-----^
2 | | whiz    bang boom
3 | | zip zap zop
  | '-------^
  '"""));
      });

      test('within the last highlighted line', () {
        final span = SourceFile.fromString('''
foo bar baz
whiz\tbang boom
''').span(4, 21);

        expect(span.highlight(), equals("""
  ,
1 |   foo bar baz
  | ,-----^
2 | | whiz    bang boom
  | '------------^
  '"""));
      });

      test('at the end of the last highlighted line', () {
        final span = SourceFile.fromString('''
foo bar baz
whiz\tbang boom
''').span(4, 17);

        expect(span.highlight(), equals("""
  ,
1 |   foo bar baz
  | ,-----^
2 | | whiz    bang boom
  | '--------^
  '"""));
      });

      test('after the highlighted section', () {
        final span = SourceFile.fromString('''
foo bar baz
whiz bang\tboom
''').span(4, 21);

        expect(span.highlight(), equals("""
  ,
1 |   foo bar baz
  | ,-----^
2 | | whiz bang    boom
  | '---------^
  '"""));
      });
    });
  });

  group('supports lines of preceding and following context for a span', () {
    test('within a single line', () {
      final span = SourceSpanWithContext(
          SourceLocation(20, line: 2, column: 5, sourceUrl: 'foo.dart'),
          SourceLocation(27, line: 2, column: 12, sourceUrl: 'foo.dart'),
          'foo bar',
          'previous\nlines\n-----foo bar-----\nfollowing line\n');

      expect(span.highlight(), equals("""
  ,
1 | previous
2 | lines
3 | -----foo bar-----
  |      ^^^^^^^
4 | following line
  '"""));
    });

    test('covering a full line', () {
      final span = SourceSpanWithContext(
          SourceLocation(15, line: 2, column: 0, sourceUrl: 'foo.dart'),
          SourceLocation(33, line: 3, column: 0, sourceUrl: 'foo.dart'),
          '-----foo bar-----\n',
          'previous\nlines\n-----foo bar-----\nfollowing line\n');

      expect(span.highlight(), equals("""
  ,
1 | previous
2 | lines
3 | -----foo bar-----
  | ^^^^^^^^^^^^^^^^^
4 | following line
  '"""));
    });

    test('covering multiple full lines', () {
      final span = SourceSpanWithContext(
          SourceLocation(15, line: 2, column: 0, sourceUrl: 'foo.dart'),
          SourceLocation(23, line: 4, column: 0, sourceUrl: 'foo.dart'),
          'foo\nbar\n',
          'previous\nlines\nfoo\nbar\nfollowing line\n');

      expect(span.highlight(), equals(r"""
  ,
1 |   previous
2 |   lines
3 | / foo
4 | \ bar
5 |   following line
  '"""));
    });
  });

  group('colors', () {
    test("doesn't colorize if color is false", () {
      expect(file.span(4, 7).highlight(color: false), equals("""
  ,
1 | foo bar baz
  |     ^^^
  '"""));
    });

    test('colorizes if color is true', () {
      expect(file.span(4, 7).highlight(color: true), equals('''
${colors.blue}  ,${colors.none}
${colors.blue}1 |${colors.none} foo ${colors.red}bar${colors.none} baz
${colors.blue}  |${colors.none} ${colors.red}    ^^^${colors.none}
${colors.blue}  '${colors.none}'''));
    });

    test("uses the given color if it's passed", () {
      expect(file.span(4, 7).highlight(color: colors.yellow), equals('''
${colors.blue}  ,${colors.none}
${colors.blue}1 |${colors.none} foo ${colors.yellow}bar${colors.none} baz
${colors.blue}  |${colors.none} ${colors.yellow}    ^^^${colors.none}
${colors.blue}  '${colors.none}'''));
    });

    test('colorizes a multiline span', () {
      expect(file.span(4, 34).highlight(color: true), equals('''
${colors.blue}  ,${colors.none}
${colors.blue}1 |${colors.none}   foo ${colors.red}bar baz${colors.none}
${colors.blue}  |${colors.none} ${colors.red},${colors.none}${colors.red}-----^${colors.none}
${colors.blue}2 |${colors.none} ${colors.red}|${colors.none} ${colors.red}whiz bang boom${colors.none}
${colors.blue}3 |${colors.none} ${colors.red}|${colors.none} ${colors.red}zip zap${colors.none} zop
${colors.blue}  |${colors.none} ${colors.red}'${colors.none}${colors.red}-------^${colors.none}
${colors.blue}  '${colors.none}'''));
    });

    test('colorizes a multiline span that highlights full lines', () {
      expect(file.span(0, 39).highlight(color: true), equals('''
${colors.blue}  ,${colors.none}
${colors.blue}1 |${colors.none} ${colors.red}/${colors.none} ${colors.red}foo bar baz${colors.none}
${colors.blue}2 |${colors.none} ${colors.red}|${colors.none} ${colors.red}whiz bang boom${colors.none}
${colors.blue}3 |${colors.none} ${colors.red}\\${colors.none} ${colors.red}zip zap zop${colors.none}
${colors.blue}  '${colors.none}'''));
    });
  });

  group('line numbers have appropriate padding', () {
    test('with line number 9', () {
      expect(
          SourceFile.fromString('\n' * 8 + 'foo bar baz\n')
              .span(8, 11)
              .highlight(),
          equals("""
  ,
9 | foo bar baz
  | ^^^
  '"""));
    });

    test('with line number 10', () {
      expect(
          SourceFile.fromString('\n' * 9 + 'foo bar baz\n')
              .span(9, 12)
              .highlight(),
          equals("""
   ,
10 | foo bar baz
   | ^^^
   '"""));
    });
  });
}
