// 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.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';

void main() {
  group('SafeArea', () {
    testWidgetsWithLeakTracking('SafeArea - basic', (WidgetTester tester) async {
      await tester.pumpWidget(
        const MediaQuery(
          data: MediaQueryData(padding: EdgeInsets.all(20.0)),
          child: SafeArea(
            left: false,
            child: Placeholder(),
          ),
        ),
      );
      expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(0.0, 20.0));
      expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
    });

    testWidgetsWithLeakTracking('SafeArea - with minimums', (WidgetTester tester) async {
      await tester.pumpWidget(
        const MediaQuery(
          data: MediaQueryData(padding: EdgeInsets.all(20.0)),
          child: SafeArea(
            top: false,
            minimum: EdgeInsets.fromLTRB(0.0, 10.0, 20.0, 30.0),
            child: Placeholder(),
          ),
        ),
      );
      expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 10.0));
      expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 570.0));
    });

    testWidgetsWithLeakTracking('SafeArea - nested', (WidgetTester tester) async {
      await tester.pumpWidget(
        const MediaQuery(
          data: MediaQueryData(padding: EdgeInsets.all(20.0)),
          child: SafeArea(
            top: false,
            child: SafeArea(
              right: false,
              child: Placeholder(),
            ),
          ),
        ),
      );
      expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0));
      expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
    });

    testWidgetsWithLeakTracking('SafeArea - changing', (WidgetTester tester) async {
      const Widget child = SafeArea(
        bottom: false,
        child: SafeArea(
          left: false,
          bottom: false,
          child: Placeholder(),
        ),
      );
      await tester.pumpWidget(
        const MediaQuery(
          data: MediaQueryData(padding: EdgeInsets.all(20.0)),
          child: child,
        ),
      );
      expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0));
      expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 600.0));
      await tester.pumpWidget(
        const MediaQuery(
          data: MediaQueryData(padding: EdgeInsets.only(
            left: 100.0,
            top: 30.0,
            bottom: 40.0,
          )),
          child: child,
        ),
      );
      expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(100.0, 30.0));
      expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
    });

    testWidgetsWithLeakTracking('SafeArea - properties', (WidgetTester tester) async {
      final SafeArea child = SafeArea(
        right: false,
        bottom: false,
        child: Container(),
      );
      final DiagnosticPropertiesBuilder properties = DiagnosticPropertiesBuilder();
      child.debugFillProperties(properties);

      expect(properties.properties.any((DiagnosticsNode n) => n is FlagProperty && n.toString() == 'avoid left padding'), true);
      expect(properties.properties.any((DiagnosticsNode n) => n is FlagProperty && n.toString() == 'avoid right padding'), false);
      expect(properties.properties.any((DiagnosticsNode n) => n is FlagProperty && n.toString() == 'avoid top padding'), true);
      expect(properties.properties.any((DiagnosticsNode n) => n is FlagProperty && n.toString() == 'avoid bottom padding'), false);
    });

    group('SafeArea maintains bottom viewPadding when specified for consumed bottom padding', () {
      Widget boilerplate(Widget child) {
        return Localizations(
          locale: const Locale('en', 'us'),
          delegates: const <LocalizationsDelegate<dynamic>>[
            DefaultWidgetsLocalizations.delegate,
            DefaultMaterialLocalizations.delegate,
          ],
          child: Directionality(textDirection: TextDirection.ltr, child: child),
        );
      }

      testWidgetsWithLeakTracking('SafeArea alone.', (WidgetTester tester) async {
        final Widget child = boilerplate(const SafeArea(
          maintainBottomViewPadding: true,
          child: Column(
            children: <Widget>[
              Expanded(child: Placeholder()),
            ],
          ),
        ));

        await tester.pumpWidget(
          MediaQuery(
            data: const MediaQueryData(
              padding: EdgeInsets.symmetric(vertical: 20.0),
              viewPadding: EdgeInsets.only(bottom: 20.0),
            ),
            child: child,
          ),
        );
        final Offset initialPoint = tester.getCenter(find.byType(Placeholder));
        // Consume bottom padding - as if by the keyboard opening
        await tester.pumpWidget(
          MediaQuery(
            data: const MediaQueryData(
              padding: EdgeInsets.only(top: 20.0),
              viewPadding: EdgeInsets.only(bottom: 20.0),
              viewInsets: EdgeInsets.only(bottom: 300.0),
            ),
            child: child,
          ),
        );
        final Offset finalPoint = tester.getCenter(find.byType(Placeholder));
        expect(initialPoint, finalPoint);
      });

      testWidgetsWithLeakTracking('SafeArea alone - partial ViewInsets consume Padding', (WidgetTester tester) async {
        final Widget child = boilerplate(const SafeArea(
          maintainBottomViewPadding: true,
          child: Column(
            children: <Widget>[
              Expanded(child: Placeholder()),
            ],
          ),
        ));

        await tester.pumpWidget(
          MediaQuery(
            data: const MediaQueryData(
              viewPadding: EdgeInsets.only(bottom: 20.0),
            ),
            child: child,
          ),
        );
        final Offset initialPoint = tester.getCenter(find.byType(Placeholder));
        // Consume bottom padding - as if by the keyboard opening
        await tester.pumpWidget(
          MediaQuery(
            data: const MediaQueryData(
              viewPadding: EdgeInsets.only(bottom: 20.0),
              viewInsets: EdgeInsets.only(bottom: 10.0),
            ),
            child: child,
          ),
        );
        final Offset finalPoint = tester.getCenter(find.byType(Placeholder));
        expect(initialPoint, finalPoint);
      });

      testWidgetsWithLeakTracking('SafeArea with nested Scaffold', (WidgetTester tester) async {
        final Widget child = boilerplate(const SafeArea(
          maintainBottomViewPadding: true,
          child: Scaffold(
            resizeToAvoidBottomInset: false,
            body: Column(
              children: <Widget>[
                Expanded(child: Placeholder()),
              ],
            ),
          ),
        ));

        await tester.pumpWidget(
          MediaQuery(
            data: const MediaQueryData(
              padding: EdgeInsets.symmetric(vertical: 20.0),
              viewPadding: EdgeInsets.only(bottom: 20.0),
            ),
            child: child,
          ),
        );
        final Offset initialPoint = tester.getCenter(find.byType(Placeholder));
        // Consume bottom padding - as if by the keyboard opening
        await tester.pumpWidget(
          MediaQuery(
            data: const MediaQueryData(
              padding: EdgeInsets.only(top: 20.0),
              viewPadding: EdgeInsets.only(bottom: 20.0),
              viewInsets: EdgeInsets.only(bottom: 300.0),
            ),
            child: child,
          ),
        );
        final Offset finalPoint = tester.getCenter(find.byType(Placeholder));
        expect(initialPoint, finalPoint);
      });

      testWidgetsWithLeakTracking('SafeArea with nested Scaffold  - partial ViewInsets consume Padding', (WidgetTester tester) async {
        final Widget child = boilerplate(const SafeArea(
          maintainBottomViewPadding: true,
          child: Scaffold(
            resizeToAvoidBottomInset: false,
            body: Column(
              children: <Widget>[
                Expanded(child: Placeholder()),
              ],
            ),
          ),
        ));

        await tester.pumpWidget(
          MediaQuery(
            data: const MediaQueryData(
              viewPadding: EdgeInsets.only(bottom: 20.0),
            ),
            child: child,
          ),
        );
        final Offset initialPoint = tester.getCenter(find.byType(Placeholder));
        // Consume bottom padding - as if by the keyboard opening
        await tester.pumpWidget(
          MediaQuery(
            data: const MediaQueryData(
              viewPadding: EdgeInsets.only(bottom: 20.0),
              viewInsets: EdgeInsets.only(bottom: 10.0),
            ),
            child: child,
          ),
        );
        final Offset finalPoint = tester.getCenter(find.byType(Placeholder));
        expect(initialPoint, finalPoint);
      });
    });
  });

  group('SliverSafeArea', () {
    Widget buildWidget(EdgeInsets mediaPadding, Widget sliver) {
      late final ViewportOffset offset;
      addTearDown(() => offset.dispose());

      return MediaQuery(
        data: MediaQueryData(padding: mediaPadding),
        child: Directionality(
          textDirection: TextDirection.ltr,
          child: Viewport(
            offset: offset = ViewportOffset.fixed(0.0),
            slivers: <Widget>[
              const SliverToBoxAdapter(child: SizedBox(width: 800.0, height: 100.0, child: Text('before'))),
              sliver,
              const SliverToBoxAdapter(child: SizedBox(width: 800.0, height: 100.0, child: Text('after'))),
            ],
          ),
        ),
      );
    }

    void verify(WidgetTester tester, List<Rect> expectedRects) {
      final List<Rect> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Rect>(
        (RenderBox target) {
          final Offset topLeft = target.localToGlobal(Offset.zero);
          final Offset bottomRight = target.localToGlobal(target.size.bottomRight(Offset.zero));
          return Rect.fromPoints(topLeft, bottomRight);
        },
      ).toList();
      expect(testAnswers, equals(expectedRects));
    }

    testWidgetsWithLeakTracking('SliverSafeArea - basic', (WidgetTester tester) async {
      await tester.pumpWidget(
        buildWidget(
          const EdgeInsets.all(20.0),
          const SliverSafeArea(
            left: false,
            sliver: SliverToBoxAdapter(child: SizedBox(width: 800.0, height: 100.0, child: Text('padded'))),
          ),
        ),
      );
      verify(tester, <Rect>[
        const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
        const Rect.fromLTWH(0.0, 120.0, 780.0, 100.0),
        const Rect.fromLTWH(0.0, 240.0, 800.0, 100.0),
      ]);
    });

    testWidgetsWithLeakTracking('SliverSafeArea - basic', (WidgetTester tester) async {
      await tester.pumpWidget(
        buildWidget(
          const EdgeInsets.all(20.0),
          const SliverSafeArea(
            top: false,
            minimum: EdgeInsets.fromLTRB(0.0, 10.0, 20.0, 30.0),
            sliver: SliverToBoxAdapter(child: SizedBox(width: 800.0, height: 100.0, child: Text('padded'))),
          ),
        ),
      );
      verify(tester, <Rect>[
        const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
        const Rect.fromLTWH(20.0, 110.0, 760.0, 100.0),
        const Rect.fromLTWH(0.0, 240.0, 800.0, 100.0),
      ]);
    });

    testWidgetsWithLeakTracking('SliverSafeArea - nested', (WidgetTester tester) async {
      await tester.pumpWidget(
        buildWidget(
          const EdgeInsets.all(20.0),
          const SliverSafeArea(
            top: false,
            sliver: SliverSafeArea(
              right: false,
              sliver: SliverToBoxAdapter(child: SizedBox(width: 800.0, height: 100.0, child: Text('padded'))),
            ),
          ),
        ),
      );
      verify(tester, <Rect>[
        const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
        const Rect.fromLTWH(20.0, 120.0, 760.0, 100.0),
        const Rect.fromLTWH(0.0, 240.0, 800.0, 100.0),
      ]);
    });

    testWidgetsWithLeakTracking('SliverSafeArea - changing', (WidgetTester tester) async {
      const Widget sliver = SliverSafeArea(
        bottom: false,
        sliver: SliverSafeArea(
          left: false,
          bottom: false,
          sliver: SliverToBoxAdapter(child: SizedBox(width: 800.0, height: 100.0, child: Text('padded'))),
        ),
      );
      await tester.pumpWidget(
        buildWidget(
          const EdgeInsets.all(20.0),
          sliver,
        ),
      );
      verify(tester, <Rect>[
        const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
        const Rect.fromLTWH(20.0, 120.0, 760.0, 100.0),
        const Rect.fromLTWH(0.0, 220.0, 800.0, 100.0),
      ]);

      await tester.pumpWidget(
        buildWidget(
          const EdgeInsets.only(
            left: 100.0,
            top: 30.0,
            bottom: 40.0,
          ),
          sliver,
        ),
      );
      verify(tester, <Rect>[
        const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
        const Rect.fromLTWH(100.0, 130.0, 700.0, 100.0),
        const Rect.fromLTWH(0.0, 230.0, 800.0, 100.0),
      ]);
    });
  });

  testWidgetsWithLeakTracking('SliverSafeArea - properties', (WidgetTester tester) async {
    const SliverSafeArea child = SliverSafeArea(
      right: false,
      bottom: false,
      sliver: SliverToBoxAdapter(child: SizedBox(width: 800.0, height: 100.0, child: Text('padded'))),
    );
    final DiagnosticPropertiesBuilder properties = DiagnosticPropertiesBuilder();
    child.debugFillProperties(properties);

    expect(properties.properties.any((DiagnosticsNode n) => n is FlagProperty && n.toString() == 'avoid left padding'), true);
    expect(properties.properties.any((DiagnosticsNode n) => n is FlagProperty && n.toString() == 'avoid right padding'), false);
    expect(properties.properties.any((DiagnosticsNode n) => n is FlagProperty && n.toString() == 'avoid top padding'), true);
    expect(properties.properties.any((DiagnosticsNode n) => n is FlagProperty && n.toString() == 'avoid bottom padding'), false);
  });
}