// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// TODO(LongCatIsLooong): Remove this file once textScaleFactor is removed.
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('TextStyle', () {
    test('getTextStyle is backward compatible', () {
      expect(
        const TextStyle(fontSize: 14).getTextStyle(textScaleFactor: 2.0).toString(),
        contains('fontSize: 28'),
      );
    }, skip: kIsWeb); // [intended] CkTextStyle doesn't have a custom toString implementation.
  });
  group('TextPainter', () {
    test('textScaleFactor translates to textScaler', () {
      final TextPainter textPainter = TextPainter(
        text: const TextSpan(text: 'text'),
        textDirection: TextDirection.ltr,
        textScaleFactor: 42,
      );

      expect(textPainter.textScaler, const TextScaler.linear(42.0));

      // Linear TextScaler translates to textScaleFactor.
      textPainter.textScaler = const TextScaler.linear(12.0);
      expect(textPainter.textScaleFactor, 12.0);

      textPainter.textScaleFactor = 10;
      expect(textPainter.textScaler, const TextScaler.linear(10));
    });
  });

  group('MediaQuery', () {
    test('specifying both textScaler and textScalingFactor asserts', () {
      expect(
        () => MediaQueryData(textScaleFactor: 2, textScaler: const TextScaler.linear(2.0)),
        throwsAssertionError,
      );
    });

    test('copyWith is backward compatible', () {
      const MediaQueryData data = MediaQueryData(textScaler: TextScaler.linear(2.0));

      final MediaQueryData data1 = data.copyWith(textScaleFactor: 42);
      expect(data1.textScaler, const TextScaler.linear(42));
      expect(data1.textScaleFactor, 42);

      final MediaQueryData data2 = data.copyWith(textScaler: TextScaler.noScaling);
      expect(data2.textScaler, TextScaler.noScaling);
      expect(data2.textScaleFactor, 1.0);
    });

    test('copyWith specifying both textScaler and textScalingFactor asserts', () {
      const MediaQueryData data = MediaQueryData();
       expect(
        () => data.copyWith(textScaleFactor: 2, textScaler: const TextScaler.linear(2.0)),
        throwsAssertionError,
      );
    });

    testWidgets('MediaQuery.textScaleFactorOf overriding compatibility', (WidgetTester tester) async {
      late final double outsideTextScaleFactor;
      late final TextScaler outsideTextScaler;
      late final double insideTextScaleFactor;
      late final TextScaler insideTextScaler;

      await tester.pumpWidget(
        Builder(
          builder: (BuildContext context) {
            outsideTextScaleFactor = MediaQuery.textScaleFactorOf(context);
            outsideTextScaler = MediaQuery.textScalerOf(context);
            return MediaQuery(
              data: const MediaQueryData(
                textScaleFactor: 4.0,
              ),
              child: Builder(
                builder: (BuildContext context) {
                  insideTextScaleFactor = MediaQuery.textScaleFactorOf(context);
                  insideTextScaler = MediaQuery.textScalerOf(context);
                  return Container();
                },
              ),
            );
          },
        ),
      );

      // Overriding textScaleFactor should work for unmigrated widgets that are
      // still using MediaQuery.textScaleFactorOf. Also if a unmigrated widget
      // overrides MediaQuery.textScaleFactor, migrated widgets in the subtree
      // should get the correct TextScaler.
      expect(outsideTextScaleFactor, 1.0);
      expect(outsideTextScaler.textScaleFactor, 1.0);
      expect(outsideTextScaler, TextScaler.noScaling);
      expect(insideTextScaleFactor, 4.0);
      expect(insideTextScaler.textScaleFactor, 4.0);
      expect(insideTextScaler, const TextScaler.linear(4.0));
    });

    testWidgets('textScaleFactor overriding backward compatibility', (WidgetTester tester) async {
      late final double outsideTextScaleFactor;
      late final TextScaler outsideTextScaler;
      late final double insideTextScaleFactor;
      late final TextScaler insideTextScaler;

      await tester.pumpWidget(
        Builder(
          builder: (BuildContext context) {
            outsideTextScaleFactor = MediaQuery.textScaleFactorOf(context);
            outsideTextScaler = MediaQuery.textScalerOf(context);
            return MediaQuery(
              data: const MediaQueryData(textScaler: TextScaler.linear(4.0)),
              child: Builder(
                builder: (BuildContext context) {
                  insideTextScaleFactor = MediaQuery.textScaleFactorOf(context);
                  insideTextScaler = MediaQuery.textScalerOf(context);
                  return Container();
                },
              ),
            );
          },
        ),
      );

      expect(outsideTextScaleFactor, 1.0);
      expect(outsideTextScaler.textScaleFactor, 1.0);
      expect(outsideTextScaler, TextScaler.noScaling);
      expect(insideTextScaleFactor, 4.0);
      expect(insideTextScaler.textScaleFactor, 4.0);
      expect(insideTextScaler, const TextScaler.linear(4.0));
    });
  });

  group('RenderObjects backward compatibility', () {
    test('RenderEditable', () {
      final RenderEditable renderObject = RenderEditable(
        backgroundCursorColor: const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00),
        textDirection: TextDirection.ltr,
        cursorColor: const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00),
        offset: ViewportOffset.zero(),
        textSelectionDelegate: _FakeEditableTextState(),
        text: const TextSpan(
          text: 'test',
          style: TextStyle(height: 1.0, fontSize: 10.0),
        ),
        startHandleLayerLink: LayerLink(),
        endHandleLayerLink: LayerLink(),
        selection: const TextSelection.collapsed(offset: 0),
      );
      expect(renderObject.textScaleFactor, 1.0);

      renderObject.textScaleFactor = 3.0;
      expect(renderObject.textScaleFactor, 3.0);
      expect(renderObject.textScaler, const TextScaler.linear(3.0));

      renderObject.textScaler = const TextScaler.linear(4.0);
      expect(renderObject.textScaleFactor, 4.0);
    });

    test('RenderParagraph', () {
      final RenderParagraph renderObject = RenderParagraph(
        const TextSpan(
          text: 'test',
          style: TextStyle(height: 1.0, fontSize: 10.0),
        ),
        textDirection: TextDirection.ltr,
      );
      expect(renderObject.textScaleFactor, 1.0);

      renderObject.textScaleFactor = 3.0;
      expect(renderObject.textScaleFactor, 3.0);
      expect(renderObject.textScaler, const TextScaler.linear(3.0));

      renderObject.textScaler = const TextScaler.linear(4.0);
      expect(renderObject.textScaleFactor, 4.0);
    });
  });

  group('Widgets backward compatibility', () {
    testWidgets('RichText', (WidgetTester tester) async {
      await tester.pumpWidget(
        RichText(
          textDirection: TextDirection.ltr,
          text: const TextSpan(),
          textScaleFactor: 2.0,
        ),
      );

      expect(
        tester.renderObject<RenderParagraph>(find.byType(RichText)).textScaler,
        const TextScaler.linear(2.0),
      );
      expect(tester.renderObject<RenderParagraph>(find.byType(RichText)).textScaleFactor, 2.0);
    });

    testWidgets('Text', (WidgetTester tester) async {
      await tester.pumpWidget(
        const Text(
          'text',
          textDirection: TextDirection.ltr,
          textScaleFactor: 2.0,
        ),
      );

      expect(
        tester.renderObject<RenderParagraph>(find.text('text')).textScaler,
        const TextScaler.linear(2.0),
      );
    });

    testWidgets('EditableText', (WidgetTester tester) async {
      final TextEditingController controller = TextEditingController();
      addTearDown(controller.dispose);
      final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Node');
      addTearDown(focusNode.dispose);
      const TextStyle textStyle = TextStyle();
      const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00);
      await tester.pumpWidget(
        MediaQuery(
          data: const MediaQueryData(),
          child: Directionality(
            textDirection: TextDirection.rtl,
            child: EditableText(
              backgroundCursorColor: cursorColor,
              controller: controller,
              focusNode: focusNode,
              style: textStyle,
              cursorColor: cursorColor,
              textScaleFactor: 2.0,
            ),
          ),
        ),
      );

      final RenderEditable renderEditable = tester.allRenderObjects.whereType<RenderEditable>().first;
      expect(
        renderEditable.textScaler,
        const TextScaler.linear(2.0),
      );
    });
  });
}

class _FakeEditableTextState with TextSelectionDelegate {
  @override
  TextEditingValue textEditingValue = TextEditingValue.empty;

  TextSelection? selection;

  @override
  void hideToolbar([bool hideHandles = true]) { }

  @override
  void userUpdateTextEditingValue(TextEditingValue value, SelectionChangedCause cause) {
    selection = value.selection;
  }

  @override
  void bringIntoView(TextPosition position) { }

  @override
  void cutSelection(SelectionChangedCause cause) { }

  @override
  Future<void> pasteText(SelectionChangedCause cause) {
    return Future<void>.value();
  }

  @override
  void selectAll(SelectionChangedCause cause) { }

  @override
  void copySelection(SelectionChangedCause cause) { }
}
