// 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_test/flutter_test.dart';

class TestWidget extends StatefulWidget {
  const TestWidget({
    super.key,
    required this.child,
    required this.persistentState,
    required this.syncedState,
  });

  final Widget child;
  final int persistentState;
  final int syncedState;

  @override
  TestWidgetState createState() => TestWidgetState();
}

class TestWidgetState extends State<TestWidget> {
  late int persistentState;
  late int syncedState;
  int updates = 0;

  @override
  void initState() {
    super.initState();
    persistentState = widget.persistentState;
    syncedState = widget.syncedState;
  }

  @override
  void didUpdateWidget(TestWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    syncedState = widget.syncedState;
    // we explicitly do NOT sync the persistentState from the new instance
    // because we're using that to track whether we got recreated
    updates += 1;
  }

  @override
  Widget build(BuildContext context) => widget.child;
}

void main() {

  testWidgets('no change', (WidgetTester tester) async {
    await tester.pumpWidget(
      ColoredBox(
        color: Colors.blue,
        child: ColoredBox(
          color: Colors.blue,
          child: TestWidget(
            persistentState: 1,
            syncedState: 0,
            child: Container(),
          ),
        ),
      ),
    );

    final TestWidgetState state = tester.state(find.byType(TestWidget));

    expect(state.persistentState, equals(1));
    expect(state.updates, equals(0));

    await tester.pumpWidget(
      ColoredBox(
        color: Colors.blue,
        child: ColoredBox(
          color: Colors.blue,
          child: TestWidget(
            persistentState: 2,
            syncedState: 0,
            child: Container(),
          ),
        ),
      ),
    );

    expect(state.persistentState, equals(1));
    expect(state.updates, equals(1));

    await tester.pumpWidget(Container());
  });

  testWidgets('remove one', (WidgetTester tester) async {
    await tester.pumpWidget(
      ColoredBox(
        color: Colors.blue,
        child: ColoredBox(
          color: Colors.blue,
          child: TestWidget(
            persistentState: 10,
            syncedState: 0,
            child: Container(),
          ),
        ),
      ),
    );

    TestWidgetState state = tester.state(find.byType(TestWidget));

    expect(state.persistentState, equals(10));
    expect(state.updates, equals(0));

    await tester.pumpWidget(
      ColoredBox(
        color: Colors.green,
        child: TestWidget(
          persistentState: 11,
          syncedState: 0,
          child: Container(),
        ),
      ),
    );

    state = tester.state(find.byType(TestWidget));

    expect(state.persistentState, equals(11));
    expect(state.updates, equals(0));

    await tester.pumpWidget(Container());
  });

  testWidgets('swap instances around', (WidgetTester tester) async {
    const Widget a = TestWidget(persistentState: 0x61, syncedState: 0x41, child: Text('apple', textDirection: TextDirection.ltr));
    const Widget b = TestWidget(persistentState: 0x62, syncedState: 0x42, child: Text('banana', textDirection: TextDirection.ltr));
    await tester.pumpWidget(const Column());

    final GlobalKey keyA = GlobalKey();
    final GlobalKey keyB = GlobalKey();

    await tester.pumpWidget(
      Column(
        children: <Widget>[
          Container(
            key: keyA,
            child: a,
          ),
          Container(
            key: keyB,
            child: b,
          ),
        ],
      ),
    );

    TestWidgetState first, second;

    first = tester.state(find.byWidget(a));
    second = tester.state(find.byWidget(b));

    expect(first.widget, equals(a));
    expect(first.persistentState, equals(0x61));
    expect(first.syncedState, equals(0x41));
    expect(second.widget, equals(b));
    expect(second.persistentState, equals(0x62));
    expect(second.syncedState, equals(0x42));

    await tester.pumpWidget(
      Column(
        children: <Widget>[
          Container(
            key: keyA,
            child: a,
          ),
          Container(
            key: keyB,
            child: b,
          ),
        ],
      ),
    );

    first = tester.state(find.byWidget(a));
    second = tester.state(find.byWidget(b));

    // same as before
    expect(first.widget, equals(a));
    expect(first.persistentState, equals(0x61));
    expect(first.syncedState, equals(0x41));
    expect(second.widget, equals(b));
    expect(second.persistentState, equals(0x62));
    expect(second.syncedState, equals(0x42));

    // now we swap the nodes over
    // since they are both "old" nodes, they shouldn't sync with each other even though they look alike

    await tester.pumpWidget(
      Column(
        children: <Widget>[
          Container(
            key: keyA,
            child: b,
          ),
          Container(
            key: keyB,
            child: a,
          ),
        ],
      ),
    );

    first = tester.state(find.byWidget(b));
    second = tester.state(find.byWidget(a));

    expect(first.widget, equals(b));
    expect(first.persistentState, equals(0x61));
    expect(first.syncedState, equals(0x42));
    expect(second.widget, equals(a));
    expect(second.persistentState, equals(0x62));
    expect(second.syncedState, equals(0x41));
  });
}