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

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

const String tooltipText = 'TIP';

void main() {
  testWidgets('Tooltip does not build MouseRegion when mouse is detected and in TooltipVisibility with visibility = false', (WidgetTester tester) async {
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    addTearDown(() async {
      if (gesture != null) {
        return gesture.removePointer();
      }
    });
    await gesture.addPointer();
    await gesture.moveTo(const Offset(1.0, 1.0));
    await tester.pump();
    await gesture.moveTo(Offset.zero);

    await tester.pumpWidget(
      const MaterialApp(
        home: TooltipVisibility(
          visible: false,
          child: Tooltip(
            message: tooltipText,
            child: SizedBox(
              width: 100.0,
              height: 100.0,
            ),
          ),
        ),
      ),
    );

    expect(find.descendant(of: find.byType(Tooltip), matching: find.byType(MouseRegion)), findsNothing);
  });

  testWidgets('Tooltip does not show when hovered when in TooltipVisibility with visible = false', (WidgetTester tester) async {
    const Duration waitDuration = Duration.zero;
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    addTearDown(() async {
      if (gesture != null) {
        return gesture.removePointer();
      }
    });
    await gesture.addPointer();
    await gesture.moveTo(const Offset(1.0, 1.0));
    await tester.pump();
    await gesture.moveTo(Offset.zero);

    await tester.pumpWidget(
      const MaterialApp(
        home: Center(
          child: TooltipVisibility(
            visible: false,
            child: Tooltip(
              message: tooltipText,
              waitDuration: waitDuration,
              child: SizedBox(
                width: 100.0,
                height: 100.0,
              ),
            ),
          ),
        ),
      ),
    );

    final Finder tooltip = find.byType(Tooltip);
    await gesture.moveTo(Offset.zero);
    await tester.pump();
    await gesture.moveTo(tester.getCenter(tooltip));
    await tester.pump();
    // Wait for it to appear.
    await tester.pump(waitDuration);
    expect(find.text(tooltipText), findsNothing);
  });

  testWidgets('Tooltip shows when hovered when in TooltipVisibility with visible = true', (WidgetTester tester) async {
    const Duration waitDuration = Duration.zero;
    TestGesture? gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    addTearDown(() async {
      if (gesture != null) {
        return gesture.removePointer();
      }
    });
    await gesture.addPointer();
    await gesture.moveTo(const Offset(1.0, 1.0));
    await tester.pump();
    await gesture.moveTo(Offset.zero);

    await tester.pumpWidget(
      const MaterialApp(
        home: Center(
          child: TooltipVisibility(
            visible: true,
            child: Tooltip(
              message: tooltipText,
              waitDuration: waitDuration,
              child: SizedBox(
                width: 100.0,
                height: 100.0,
              ),
            ),
          ),
        ),
      ),
    );

    final Finder tooltip = find.byType(Tooltip);
    await gesture.moveTo(Offset.zero);
    await tester.pump();
    await gesture.moveTo(tester.getCenter(tooltip));
    await tester.pump();
    // Wait for it to appear.
    await tester.pump(waitDuration);
    expect(find.text(tooltipText), findsOneWidget);

    // Wait for it to disappear.
    await gesture.moveTo(Offset.zero);
    await tester.pumpAndSettle();
    await gesture.removePointer();
    gesture = null;
    expect(find.text(tooltipText), findsNothing);
  });

  testWidgets('Tooltip does not build GestureDetector when in TooltipVisibility with visibility = false', (WidgetTester tester) async {
    await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap, false);

    expect(find.byType(GestureDetector), findsNothing);
  });

  testWidgets('Tooltip triggers on tap when trigger mode is tap and in TooltipVisibility with visible = true', (WidgetTester tester) async {
    await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap, true);

    final Finder tooltip = find.byType(Tooltip);
    expect(find.text(tooltipText), findsNothing);

    await testGestureTap(tester, tooltip);
    expect(find.text(tooltipText), findsOneWidget);
  });

  testWidgets('Tooltip does not trigger manually when in TooltipVisibility with visible = false', (WidgetTester tester) async {
    final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
    await tester.pumpWidget(
      MaterialApp(
        home: TooltipVisibility(
          visible: false,
          child: Tooltip(
            key: tooltipKey,
            message: tooltipText,
            child: const SizedBox(width: 100.0, height: 100.0),
          ),
        ),
      ),
    );

    tooltipKey.currentState?.ensureTooltipVisible();
    await tester.pump();
    expect(find.text(tooltipText), findsNothing);
  });

  testWidgets('Tooltip triggers manually when in TooltipVisibility with visible = true', (WidgetTester tester) async {
    final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
    await tester.pumpWidget(
      MaterialApp(
        home: TooltipVisibility(
          visible: true,
          child: Tooltip(
            key: tooltipKey,
            message: tooltipText,
            child: const SizedBox(width: 100.0, height: 100.0),
          ),
        ),
      ),
    );

    tooltipKey.currentState?.ensureTooltipVisible();
    await tester.pump();
    expect(find.text(tooltipText), findsOneWidget);
  });
}

Future<void> setWidgetForTooltipMode(WidgetTester tester, TooltipTriggerMode triggerMode, bool visibility) async {
  await tester.pumpWidget(
    MaterialApp(
      home: TooltipVisibility(
        visible: visibility,
        child: Tooltip(
          message: tooltipText,
          triggerMode: triggerMode,
          child: const SizedBox(width: 100.0, height: 100.0),
        ),
      ),
    ),
  );
}

Future<void> testGestureTap(WidgetTester tester, Finder tooltip) async {
  await tester.tap(tooltip);
  await tester.pump(const Duration(milliseconds: 10));
}
