// 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 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart';

import 'material3.dart';
import 'recorder.dart';

/// Measures the cost of semantics when constructing screens containing
/// Material 3 widgets.
class BenchMaterial3Semantics extends WidgetBuildRecorder {
  BenchMaterial3Semantics() : super(name: benchmarkName);

  static const String benchmarkName = 'bench_material3_semantics';

  @override
  Future<void> setUpAll() async {
    FlutterTimeline.debugCollectionEnabled = true;
    super.setUpAll();
    SemanticsBinding.instance.ensureSemantics();
  }

  @override
  Future<void> tearDownAll() async {
    FlutterTimeline.debugReset();
  }

  @override
  void frameDidDraw() {
    // Only record frames that show the widget. Frames that remove the widget
    // are not interesting.
    if (showWidget) {
      final AggregatedTimings timings = FlutterTimeline.debugCollect();
      final AggregatedTimedBlock semanticsBlock = timings.getAggregated('SEMANTICS');
      final AggregatedTimedBlock getFragmentBlock = timings.getAggregated('Semantics.GetFragment');
      final AggregatedTimedBlock compileChildrenBlock = timings.getAggregated('Semantics.compileChildren');
      profile!.addTimedBlock(semanticsBlock, reported: true);
      profile!.addTimedBlock(getFragmentBlock, reported: true);
      profile!.addTimedBlock(compileChildrenBlock, reported: true);
    }

    super.frameDidDraw();
    FlutterTimeline.debugReset();
  }

  @override
  Widget createWidget() {
    return const SingleColumnMaterial3Components();
  }
}

/// Measures the cost of semantics when scrolling screens containing Material 3
/// widgets.
///
/// The implementation uses a ListView that jumps the scroll position between
/// 0 and 1 every frame. Such a small delta is not enough for lazy rendering to
/// add/remove widgets, but its enough to trigger the framework to recompute
/// some of the semantics.
///
/// The expected output numbers of this benchmarks should be very small as
/// scrolling a list view should be a matter of shifting some widgets and
/// updating the projected clip imposed by the viewport. As of June 2023, the
/// numbers are not great. Semantics consumes >50% of frame time.
class BenchMaterial3ScrollSemantics extends WidgetRecorder {
  BenchMaterial3ScrollSemantics() : super(name: benchmarkName);

  static const String benchmarkName = 'bench_material3_scroll_semantics';

  @override
  Future<void> setUpAll() async {
    FlutterTimeline.debugCollectionEnabled = true;
    super.setUpAll();
    SemanticsBinding.instance.ensureSemantics();
  }

  @override
  Future<void> tearDownAll() async {
    FlutterTimeline.debugReset();
  }

  @override
  void frameDidDraw() {
    final AggregatedTimings timings = FlutterTimeline.debugCollect();
    final AggregatedTimedBlock semanticsBlock = timings.getAggregated('SEMANTICS');
    final AggregatedTimedBlock getFragmentBlock = timings.getAggregated('Semantics.GetFragment');
    final AggregatedTimedBlock compileChildrenBlock = timings.getAggregated('Semantics.compileChildren');
    profile!.addTimedBlock(semanticsBlock, reported: true);
    profile!.addTimedBlock(getFragmentBlock, reported: true);
    profile!.addTimedBlock(compileChildrenBlock, reported: true);

    super.frameDidDraw();
    FlutterTimeline.debugReset();
  }

  @override
  Widget createWidget() => _ScrollTest();
}

class _ScrollTest extends StatefulWidget {
  @override
  State<_ScrollTest> createState() => _ScrollTestState();
}

class _ScrollTestState extends State<_ScrollTest> with SingleTickerProviderStateMixin {
  late final Ticker ticker;
  late final ScrollController scrollController;

  @override
  void initState() {
    super.initState();

    scrollController = ScrollController();

    bool forward = true;

    // A one-off timer is necessary to allow the framework to measure the
    // available scroll extents before the scroll controller can be exercised
    // to change the scroll position.
    Timer.run(() {
      ticker = createTicker((_) {
        scrollController.jumpTo(forward ? 1 : 0);
        forward = !forward;
      });
      ticker.start();
    });
  }

  @override
  void dispose() {
    ticker.dispose();
    scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SingleColumnMaterial3Components(
      scrollController: scrollController,
    );
  }
}
