// 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';

import '../foundation/leak_tracking.dart';

void main() {
  group('TimeOfDay.format', () {
    testWidgetsWithLeakTracking('respects alwaysUse24HourFormat option', (WidgetTester tester) async {
      Future<String> pumpTest(bool alwaysUse24HourFormat) async {
        late String formattedValue;
        await tester.pumpWidget(MaterialApp(
          home: MediaQuery(
            data: MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat),
            child: Builder(builder: (BuildContext context) {
              formattedValue = const TimeOfDay(hour: 7, minute: 0).format(context);
              return Container();
            }),
          ),
        ));
        return formattedValue;
      }

      expect(await pumpTest(false), '7:00 AM');
      expect(await pumpTest(true), '07:00');
    });
  });

  testWidgetsWithLeakTracking('hourOfPeriod returns correct value', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/59158.
    expect(const TimeOfDay(minute: 0, hour:  0).hourOfPeriod, 12);
    expect(const TimeOfDay(minute: 0, hour:  1).hourOfPeriod,  1);
    expect(const TimeOfDay(minute: 0, hour:  2).hourOfPeriod,  2);
    expect(const TimeOfDay(minute: 0, hour:  3).hourOfPeriod,  3);
    expect(const TimeOfDay(minute: 0, hour:  4).hourOfPeriod,  4);
    expect(const TimeOfDay(minute: 0, hour:  5).hourOfPeriod,  5);
    expect(const TimeOfDay(minute: 0, hour:  6).hourOfPeriod,  6);
    expect(const TimeOfDay(minute: 0, hour:  7).hourOfPeriod,  7);
    expect(const TimeOfDay(minute: 0, hour:  8).hourOfPeriod,  8);
    expect(const TimeOfDay(minute: 0, hour:  9).hourOfPeriod,  9);
    expect(const TimeOfDay(minute: 0, hour: 10).hourOfPeriod, 10);
    expect(const TimeOfDay(minute: 0, hour: 11).hourOfPeriod, 11);
    expect(const TimeOfDay(minute: 0, hour: 12).hourOfPeriod, 12);
    expect(const TimeOfDay(minute: 0, hour: 13).hourOfPeriod,  1);
    expect(const TimeOfDay(minute: 0, hour: 14).hourOfPeriod,  2);
    expect(const TimeOfDay(minute: 0, hour: 15).hourOfPeriod,  3);
    expect(const TimeOfDay(minute: 0, hour: 16).hourOfPeriod,  4);
    expect(const TimeOfDay(minute: 0, hour: 17).hourOfPeriod,  5);
    expect(const TimeOfDay(minute: 0, hour: 18).hourOfPeriod,  6);
    expect(const TimeOfDay(minute: 0, hour: 19).hourOfPeriod,  7);
    expect(const TimeOfDay(minute: 0, hour: 20).hourOfPeriod,  8);
    expect(const TimeOfDay(minute: 0, hour: 21).hourOfPeriod,  9);
    expect(const TimeOfDay(minute: 0, hour: 22).hourOfPeriod, 10);
    expect(const TimeOfDay(minute: 0, hour: 23).hourOfPeriod, 11);
  });

  group('RestorableTimeOfDay tests', () {
    testWidgetsWithLeakTracking('value is not accessible when not registered', (WidgetTester tester) async {
      expect(() => RestorableTimeOfDay(const TimeOfDay(hour: 20, minute: 4)).value, throwsAssertionError);
    });

    testWidgetsWithLeakTracking('work when not in restoration scope', (WidgetTester tester) async {
      await tester.pumpWidget(const _RestorableWidget());

      final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));

      // Initialized to default values.
      expect(state.timeOfDay.value, const TimeOfDay(hour: 10, minute: 5));

      // Modify values.
      state.setProperties(() {
        state.timeOfDay.value = const TimeOfDay(hour: 2, minute: 2);
      });
      await tester.pump();

      expect(state.timeOfDay.value, const TimeOfDay(hour: 2, minute: 2));
    });

    testWidgets('restart and restore', (WidgetTester tester) async {
      await tester.pumpWidget(const RootRestorationScope(
        restorationId: 'root-child',
        child: _RestorableWidget(),
      ));

      _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));

      // Initialized to default values.
      expect(state.timeOfDay.value, const TimeOfDay(hour: 10, minute: 5));

      // Modify values.
      state.setProperties(() {
        state.timeOfDay.value = const TimeOfDay(hour: 2, minute: 2);
      });
      await tester.pump();

      expect(state.timeOfDay.value, const TimeOfDay(hour: 2, minute: 2));

      // Restores to previous values.
      await tester.restartAndRestore();
      final _RestorableWidgetState oldState = state;
      state = tester.state(find.byType(_RestorableWidget));
      expect(state, isNot(same(oldState)));

      expect(state.timeOfDay.value, const TimeOfDay(hour: 2, minute: 2));
    });

    testWidgetsWithLeakTracking('restore to older state', (WidgetTester tester) async {
      await tester.pumpWidget(const RootRestorationScope(
        restorationId: 'root-child',
        child: _RestorableWidget(),
      ));

      final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));

      // Modify values.
      state.setProperties(() {
        state.timeOfDay.value = const TimeOfDay(hour: 2, minute: 2);
      });
      await tester.pump();

      final TestRestorationData restorationData = await tester.getRestorationData();

      // Modify values.
      state.setProperties(() {
        state.timeOfDay.value = const TimeOfDay(hour: 4, minute: 4);
      });
      await tester.pump();

      // Restore to previous.
      await tester.restoreFrom(restorationData);
      expect(state.timeOfDay.value, const TimeOfDay(hour: 2, minute: 2));

      // Restore to empty data will re-initialize to default values.
      await tester.restoreFrom(TestRestorationData.empty);
      expect(state.timeOfDay.value, const TimeOfDay(hour: 10, minute: 5));
    });

    testWidgetsWithLeakTracking('call notifiers when value changes', (WidgetTester tester) async {
      await tester.pumpWidget(const RootRestorationScope(
        restorationId: 'root-child',
        child: _RestorableWidget(),
      ));

      final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget));

      final List<String> notifyLog = <String>[];

      state.timeOfDay.addListener(() {
        notifyLog.add('hello world');
      });

      state.setProperties(() {
        state.timeOfDay.value = const TimeOfDay(hour: 2, minute: 2);
      });
      expect(notifyLog.single, 'hello world');
      notifyLog.clear();
      await tester.pump();

      // Does not notify when set to same value.
      state.setProperties(() {
        state.timeOfDay.value = const TimeOfDay(hour: 2, minute: 2);
      });

      expect(notifyLog, isEmpty);
    });
  });
}

class _RestorableWidget extends StatefulWidget {
  const _RestorableWidget();

  @override
  State<_RestorableWidget> createState() => _RestorableWidgetState();
}

class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMixin {
  final RestorableTimeOfDay timeOfDay = RestorableTimeOfDay(const TimeOfDay(hour: 10, minute: 5));

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(timeOfDay, 'time_of_day');
  }

  void setProperties(VoidCallback callback) {
    setState(callback);
  }

  @override
  Widget build(BuildContext context) {
    return const SizedBox();
  }

  @override
  String get restorationId => 'widget';
}
