// 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/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('SharedAppData basics', (WidgetTester tester) async {
    int columnBuildCount = 0;
    int child1BuildCount = 0;
    int child2BuildCount = 0;
    late void Function(BuildContext context) setSharedAppDataValue;

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: SharedAppData(
          child: Builder(
            builder: (BuildContext context) {
              columnBuildCount += 1;
              return GestureDetector(
                behavior: HitTestBehavior.opaque,
                onTap: () {
                  setSharedAppDataValue.call(context);
                },
                child: Column(
                  children: <Widget>[
                    Builder(
                      builder: (BuildContext context) {
                        child1BuildCount += 1;
                        return Text(SharedAppData.getValue<String, String>(context, 'child1Text', () => 'null'));
                      },
                    ),
                    Builder(
                      builder: (BuildContext context) {
                        child2BuildCount += 1;
                        return Text(SharedAppData.getValue<String, String>(context, 'child2Text', () => 'null'));
                      }
                    ),
                  ],
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(columnBuildCount, 1);
    expect(child1BuildCount, 1);
    expect(child2BuildCount, 1);
    expect(find.text('null').evaluate().length, 2);

    // SharedAppData.setValue<String, String>(context, 'child1Text', 'child1')
    // causes the first Text widget to be rebuilt with its text to be
    // set to 'child1'. Nothing else is rebuilt.
    setSharedAppDataValue = (BuildContext context) {
      SharedAppData.setValue<String, String>(context, 'child1Text', 'child1');
    };
    await tester.tap(find.byType(GestureDetector));
    await tester.pump();
    expect(columnBuildCount, 1);
    expect(child1BuildCount, 2);
    expect(child2BuildCount, 1);
    expect(find.text('child1'), findsOneWidget);
    expect(find.text('null'), findsOneWidget);

    // SharedAppData.setValue<String, String>(context, 'child2Text', 'child1')
    // causes the second Text widget to be rebuilt with its text to be
    // set to 'child2'. Nothing else is rebuilt.
    setSharedAppDataValue = (BuildContext context) {
      SharedAppData.setValue<String, String>(context, 'child2Text', 'child2');
    };
    await tester.tap(find.byType(GestureDetector));
    await tester.pump();
    expect(columnBuildCount, 1);
    expect(child1BuildCount, 2);
    expect(child2BuildCount, 2);
    expect(find.text('child1'), findsOneWidget);
    expect(find.text('child2'), findsOneWidget);

    // Resetting a key's value to the same value does not
    // cause any widgets to be rebuilt.
    setSharedAppDataValue = (BuildContext context) {
      SharedAppData.setValue<String, String>(context, 'child1Text', 'child1');
      SharedAppData.setValue<String, String>(context, 'child2Text', 'child2');
    };
    await tester.tap(find.byType(GestureDetector));
    await tester.pump();
    expect(columnBuildCount, 1);
    expect(child1BuildCount, 2);
    expect(child2BuildCount, 2);

    // More of the same, resetting the values to null..

    setSharedAppDataValue = (BuildContext context) {
      SharedAppData.setValue<String, String>(context, 'child1Text', 'null');
    };
    await tester.tap(find.byType(GestureDetector));
    await tester.pump();
    expect(columnBuildCount, 1);
    expect(child1BuildCount, 3);
    expect(child2BuildCount, 2);
    expect(find.text('null'), findsOneWidget);
    expect(find.text('child2'), findsOneWidget);

    setSharedAppDataValue = (BuildContext context) {
      SharedAppData.setValue<String, String>(context, 'child2Text', 'null');
    };
    await tester.tap(find.byType(GestureDetector));
    await tester.pump();
    expect(columnBuildCount, 1);
    expect(child1BuildCount, 3);
    expect(child2BuildCount, 3);
    expect(find.text('null').evaluate().length, 2);
  });

  testWidgets('WidgetsApp SharedAppData ', (WidgetTester tester) async {
    int parentBuildCount = 0;
    int childBuildCount = 0;

    await tester.pumpWidget(
      WidgetsApp(
        color: const Color(0xff00ff00),
        builder: (BuildContext context, Widget? child) {
          parentBuildCount += 1;
          return GestureDetector(
            behavior: HitTestBehavior.opaque,
            onTap: () {
              SharedAppData.setValue<String, String>(context, 'childText', 'child');
            },
            child: Center(
              child: Builder(
                builder: (BuildContext context) {
                  childBuildCount += 1;
                  return Text(SharedAppData.getValue<String, String>(context, 'childText', () => 'null'));
                },
              ),
            ),
          );
        },
      ),
    );

    expect(find.text('null'), findsOneWidget);
    expect(parentBuildCount, 1);
    expect(childBuildCount, 1);

    await tester.tap(find.byType(GestureDetector));
    await tester.pump();
    expect(parentBuildCount, 1);
    expect(childBuildCount, 2);
    expect(find.text('child'), findsOneWidget);
  });

  testWidgets('WidgetsApp SharedAppData Shadowing', (WidgetTester tester) async {
    int innerTapCount = 0;
    int outerTapCount = 0;

    await tester.pumpWidget(
      WidgetsApp(
        color: const Color(0xff00ff00),
        builder: (BuildContext context, Widget? child) {
          return GestureDetector(
            behavior: HitTestBehavior.opaque,
            onTap: () {
              outerTapCount += 1;
              SharedAppData.setValue<String, String>(context, 'childText', 'child');
            },
            child: Center(
              child: SharedAppData(
                child: Builder(
                  builder: (BuildContext context) {
                    return GestureDetector(
                      onTap: () {
                        innerTapCount += 1;
                        SharedAppData.setValue<String, String>(context, 'childText', 'child');
                      },
                      child: Text(SharedAppData.getValue<String, String>(context, 'childText', () => 'null')),
                    );
                  },
                ),
              ),
            ),
          );
        },
      ),
    );

    expect(find.text('null'), findsOneWidget);

    await tester.tapAt(const Offset(10, 10));
    await tester.pump();
    expect(outerTapCount, 1);
    expect(innerTapCount, 0);
    expect(find.text('null'), findsOneWidget);

    await tester.tap(find.text('null'));
    await tester.pump();
    expect(outerTapCount, 1);
    expect(innerTapCount, 1);
    expect(find.text('child'), findsOneWidget);
  });
}