import 'dart:ui' show SemanticsAction, Tristate;

import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart' show SemanticsNode;
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/focus/card_focus_scope.dart';
import 'package:plezy/focus/focusable_wrapper.dart';
import 'package:plezy/focus/input_mode_tracker.dart';

void main() {
  Finder chromeIn(Type type) => find.descendant(of: find.byType(FocusableWrapper), matching: find.byType(type));

  Widget buildWrapper() => Scaffold(
    body: FocusableWrapper(onSelect: () {}, child: const SizedBox(width: 10, height: 10)),
  );

  testWidgets('pointer mode builds no focus chrome around the child', (tester) async {
    await tester.pumpWidget(MaterialApp(home: buildWrapper()));

    expect(chromeIn(Transform), findsNothing);
    expect(chromeIn(AnimatedContainer), findsNothing);
    expect(chromeIn(Focus), findsWidgets);
  });

  testWidgets('keyboard mode builds the scale/border chrome', (tester) async {
    await tester.pumpWidget(InputModeTracker(child: MaterialApp(home: buildWrapper())));

    await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
    await tester.pump();

    expect(chromeIn(AnimatedBuilder), findsOneWidget);
    expect(chromeIn(AnimatedContainer), findsOneWidget);
  });

  testWidgets('focusing in pointer mode works without a pre-built controller', (tester) async {
    final node = FocusNode(debugLabel: 'card');
    addTearDown(node.dispose);

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: FocusableWrapper(focusNode: node, onSelect: () {}, child: const SizedBox(width: 10, height: 10)),
        ),
      ),
    );

    // The AnimationController is created lazily on first focus; gaining and
    // losing focus in pointer mode must not throw.
    node.requestFocus();
    await tester.pump();
    node.unfocus();
    await tester.pump();

    expect(tester.takeException(), isNull);
  });

  testWidgets('context menu key does not suppress the next select', (tester) async {
    final node = FocusNode(debugLabel: 'card');
    addTearDown(node.dispose);
    var selected = 0;
    var longPressed = 0;

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: FocusableWrapper(
            focusNode: node,
            onSelect: () => selected++,
            onLongPress: () => longPressed++,
            child: const SizedBox(width: 10, height: 10),
          ),
        ),
      ),
    );
    node.requestFocus();
    await tester.pump();

    await tester.sendKeyEvent(LogicalKeyboardKey.contextMenu);
    await tester.sendKeyEvent(LogicalKeyboardKey.enter);

    expect(longPressed, 1);
    expect(selected, 1);
  });

  testWidgets('focus scale animation keeps child semantics geometry stable', (tester) async {
    final semantics = tester.ensureSemantics();
    final node = FocusNode(debugLabel: 'card');
    addTearDown(node.dispose);

    await tester.pumpWidget(
      InputModeTracker(
        child: MaterialApp(
          home: Scaffold(
            body: Center(
              child: FocusableWrapper(
                focusNode: node,
                focusScale: 1.2,
                delegateFocusBorder: true,
                child: CardFocusBorder(
                  child: Semantics(label: 'card content', child: SizedBox(width: 100, height: 100)),
                ),
              ),
            ),
          ),
        ),
      ),
    );
    await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
    node.unfocus();
    await tester.pumpAndSettle();
    node.requestFocus();
    await tester.pump();
    final semanticsOwner = tester.binding.rootPipelineOwner.semanticsOwner!;
    var semanticsUpdates = 0;
    void countSemanticsUpdate() => semanticsUpdates++;
    semanticsOwner.addListener(countSemanticsUpdate);

    await tester.pump(const Duration(milliseconds: 16));
    semanticsUpdates = 0;
    await tester.pump(const Duration(milliseconds: 16));

    expect(semanticsUpdates, 0);
    semanticsOwner.removeListener(countSemanticsUpdate);
    semantics.dispose();
  });

  testWidgets('semantic label replaces child semantics by default', (tester) async {
    final semantics = tester.ensureSemantics();
    var activations = 0;
    var childActivations = 0;

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: FocusableWrapper(
            semanticLabel: 'Open details',
            semanticValue: 'Ready',
            onSelect: () => activations++,
            child: Semantics(
              label: 'Decorative artwork',
              value: 'Decorative state',
              button: true,
              onTap: () => childActivations++,
              child: const Text('Poster'),
            ),
          ),
        ),
      ),
    );

    final finder = find.bySemanticsLabel('Open details');
    expect(finder, findsOneWidget);
    expect(find.bySemanticsLabel('Decorative artwork'), findsNothing);
    expect(find.bySemanticsLabel('Poster'), findsNothing);

    final node = tester.getSemantics(finder);
    final data = node.getSemanticsData();
    expect(data.value, 'Ready');
    expect(data.flagsCollection.isButton, isTrue);
    expect(data.flagsCollection.isEnabled, Tristate.isTrue);
    expect(data.hasAction(SemanticsAction.tap), isTrue);
    expect(_semanticTapNodeCount(tester), 1);

    node.owner!.performAction(node.id, SemanticsAction.tap);
    expect(activations, 1);
    expect(childActivations, 0);
    semantics.dispose();
  });

  testWidgets('merge mode supplements non-interactive child semantics', (tester) async {
    final semantics = tester.ensureSemantics();

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: FocusableWrapper(
            semanticLabel: 'Expand',
            excludeChildSemantics: false,
            onSelect: () {},
            child: Semantics(value: 'Available offline', child: const Text('Visible synopsis')),
          ),
        ),
      ),
    );

    final finder = find.bySemanticsLabel(RegExp('Expand'));
    expect(finder, findsOneWidget);
    expect(find.bySemanticsLabel(RegExp('Visible synopsis')), findsOneWidget);

    final data = tester.getSemantics(finder).getSemanticsData();
    expect(data.label, contains('Expand'));
    expect(data.label, contains('Visible synopsis'));
    expect(data.value, 'Available offline');
    expect(data.flagsCollection.isButton, isTrue);
    expect(data.flagsCollection.isEnabled, Tristate.isTrue);
    expect(data.hasAction(SemanticsAction.tap), isTrue);
    expect(_semanticTapNodeCount(tester), 1);
    semantics.dispose();
  });
}

int _semanticTapNodeCount(WidgetTester tester) {
  var count = 0;
  void visit(SemanticsNode node) {
    if (node.getSemanticsData().hasAction(SemanticsAction.tap)) count++;
    node.visitChildren((child) {
      visit(child);
      return true;
    });
  }

  visit(tester.binding.renderViews.single.owner!.semanticsOwner!.rootSemanticsNode!);
  return count;
}
