import 'dart:math';

import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';

import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';

import 'emoji_menu.dart';

class EmojiHandler extends StatefulWidget {
  const EmojiHandler({
    super.key,
    required this.editorState,
    required this.menuService,
    required this.onDismiss,
    required this.onSelectionUpdate,
    required this.onEmojiSelect,
    this.cancelBySpaceHandler,
    this.initialSearchText = '',
  });

  final EditorState editorState;
  final EmojiMenuService menuService;
  final VoidCallback onDismiss;
  final VoidCallback onSelectionUpdate;
  final SelectEmojiItemHandler onEmojiSelect;
  final String initialSearchText;
  final bool Function()? cancelBySpaceHandler;

  @override
  State<EmojiHandler> createState() => _EmojiHandlerState();
}

class _EmojiHandlerState extends State<EmojiHandler> {
  final focusNode = FocusNode(debugLabel: 'emoji_menu_handler');
  final scrollController = ScrollController();
  late EmojiData emojiData;
  final List<Emoji> searchedEmojis = [];
  bool loaded = false;
  int invalidCounter = 0;
  late int startOffset;
  late String _search = widget.initialSearchText;
  double emojiHeight = 36.0;
  String lastSearchedQuery = '';
  final configuration = EmojiPickerConfiguration(
    defaultSkinTone: lastSelectedEmojiSkinTone ?? EmojiSkinTone.none,
  );

  int get startCharAmount => widget.initialSearchText.length;

  set search(String search) {
    _search = search;
    _doSearch();
  }

  final ValueNotifier<int> selectedIndexNotifier = ValueNotifier(0);

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback(
      (_) => focusNode.requestFocus(),
    );

    startOffset =
        (widget.editorState.selection?.endIndex ?? 0) - startCharAmount;

    if (kCachedEmojiData != null) {
      loadEmojis(kCachedEmojiData!);
    } else {
      EmojiData.builtIn().then(
        (value) {
          kCachedEmojiData = value;
          loadEmojis(value);
        },
      );
    }
  }

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

  @override
  Widget build(BuildContext context) {
    final noEmojis = searchedEmojis.isEmpty;
    return Focus(
      focusNode: focusNode,
      onKeyEvent: onKeyEvent,
      child: Container(
        constraints: const BoxConstraints(maxHeight: 392, maxWidth: 360),
        padding: noEmojis
            ? EdgeInsets.zero
            : const EdgeInsets.symmetric(vertical: 16),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(6.0),
          color: Theme.of(context).cardColor,
          boxShadow: [
            BoxShadow(
              blurRadius: 5,
              spreadRadius: 1,
              color: Colors.black.withAlpha(25),
            ),
          ],
        ),
        child: loaded ? buildEmojis() : buildLoading(),
      ),
    );
  }

  Widget buildLoading() {
    return SizedBox(
      width: 400,
      height: 40,
      child: Center(
        child: SizedBox.square(
          dimension: 20,
          child: CircularProgressIndicator(),
        ),
      ),
    );
  }

  Widget buildEmojis() {
    final noEmojis = searchedEmojis.isEmpty;
    if (noEmojis) {
      return SizedBox(
        width: 400,
        height: emojiHeight,
        child: Center(
          child: FlowyText.regular(LocaleKeys.inlineActions_noResults.tr()),
        ),
      );
    }
    return SizedBox(
      height:
          (searchedEmojis.length / configuration.perLine).ceil() * emojiHeight,
      child: GridView.builder(
        controller: scrollController,
        itemCount: searchedEmojis.length,
        padding: const EdgeInsets.symmetric(horizontal: 16),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: configuration.perLine,
        ),
        itemBuilder: (context, index) {
          final currentEmoji = searchedEmojis[index];
          final emojiId = currentEmoji.id;
          final emoji = emojiData.getEmojiById(
            emojiId,
            skinTone: configuration.defaultSkinTone,
          );
          return ValueListenableBuilder(
            valueListenable: selectedIndexNotifier,
            builder: (context, value, child) {
              final isSelected = value == index;
              return SizedBox.square(
                dimension: emojiHeight,
                child: FlowyButton(
                  isSelected: isSelected,
                  margin: EdgeInsets.zero,
                  radius: Corners.s8Border,
                  text: ManualTooltip(
                    key: ValueKey('$emojiId-$isSelected'),
                    message: currentEmoji.name,
                    showAutomaticlly: isSelected,
                    preferBelow: false,
                    child: FlowyText.emoji(
                      emoji,
                      fontSize: configuration.emojiSize,
                    ),
                  ),
                  onTap: () => onSelect(index),
                ),
              );
            },
          );
        },
      ),
    );
  }

  void changeSelectedIndex(int index) => selectedIndexNotifier.value = index;

  void loadEmojis(EmojiData data) {
    emojiData = data;
    searchedEmojis.clear();
    searchedEmojis.addAll(emojiData.emojis.values);
    if (mounted) {
      setState(() {
        loaded = true;
      });
    }
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _doSearch();
    });
  }

  void _doSearch() {
    if (!loaded || !mounted) return;
    final enableEmptySearch = widget.initialSearchText.isEmpty;
    if ((_search.startsWith(' ') || _search.isEmpty) && !enableEmptySearch) {
      widget.onDismiss.call();
      return;
    }
    final searchEmojiData = emojiData.filterByKeyword(_search);
    setState(() {
      searchedEmojis.clear();
      searchedEmojis.addAll(searchEmojiData.emojis.values);
      changeSelectedIndex(0);
      _scrollToItem();
    });
    if (searchedEmojis.isEmpty) {
      if (lastSearchedQuery.isEmpty) {
        lastSearchedQuery = _search;
      }
      if (_search.length - lastSearchedQuery.length >= 5) {
        widget.onDismiss.call();
      }
    } else {
      lastSearchedQuery = '';
    }
  }

  KeyEventResult onKeyEvent(focus, KeyEvent event) {
    if (event is! KeyDownEvent && event is! KeyRepeatEvent) {
      return KeyEventResult.ignored;
    }

    const moveKeys = [
      LogicalKeyboardKey.arrowUp,
      LogicalKeyboardKey.arrowDown,
      LogicalKeyboardKey.arrowLeft,
      LogicalKeyboardKey.arrowRight,
    ];

    if (event.logicalKey == LogicalKeyboardKey.enter) {
      onSelect(selectedIndexNotifier.value);
      return KeyEventResult.handled;
    } else if (event.logicalKey == LogicalKeyboardKey.escape) {
      // Workaround to bring focus back to editor
      widget.editorState
          .updateSelectionWithReason(widget.editorState.selection);
      widget.onDismiss.call();
    } else if (event.logicalKey == LogicalKeyboardKey.backspace) {
      if (_search.isEmpty) {
        if (widget.initialSearchText.isEmpty) {
          widget.onDismiss.call();
          return KeyEventResult.handled;
        }
        if (_canDeleteLastCharacter()) {
          widget.editorState.deleteBackward();
        } else {
          // Workaround for editor regaining focus
          widget.editorState.apply(
            widget.editorState.transaction
              ..afterSelection = widget.editorState.selection,
          );
        }
        widget.onDismiss.call();
      } else {
        widget.onSelectionUpdate();
        widget.editorState.deleteBackward();
        _deleteCharacterAtSelection();
      }

      return KeyEventResult.handled;
    } else if (event.character != null &&
        !moveKeys.contains(event.logicalKey)) {
      /// Prevents dismissal of context menu by notifying the parent
      /// that the selection change occurred from the handler.
      widget.onSelectionUpdate();

      if (event.logicalKey == LogicalKeyboardKey.space) {
        final cancelBySpaceHandler = widget.cancelBySpaceHandler;
        if (cancelBySpaceHandler != null && cancelBySpaceHandler()) {
          return KeyEventResult.handled;
        }
      }

      // Interpolation to avoid having a getter for private variable
      _insertCharacter(event.character!);
      return KeyEventResult.handled;
    } else if (moveKeys.contains(event.logicalKey)) {
      _moveSelection(event.logicalKey);
      return KeyEventResult.handled;
    }

    return KeyEventResult.handled;
  }

  void onSelect(int index) {
    widget.onEmojiSelect.call(
      context,
      (startOffset - startCharAmount, startOffset + _search.length),
      emojiData.getEmojiById(searchedEmojis[index].id),
    );
    widget.onDismiss.call();
  }

  void _insertCharacter(String character) {
    widget.editorState.insertTextAtCurrentSelection(character);

    final selection = widget.editorState.selection;
    if (selection == null || !selection.isCollapsed) {
      return;
    }

    final delta = widget.editorState.getNodeAtPath(selection.end.path)?.delta;
    if (delta == null) {
      return;
    }

    search = widget.editorState
        .getTextInSelection(
          selection.copyWith(
            start: selection.start.copyWith(offset: startOffset),
            end: selection.start
                .copyWith(offset: startOffset + _search.length + 1),
          ),
        )
        .join();
  }

  void _moveSelection(LogicalKeyboardKey key) {
    final index = selectedIndexNotifier.value,
        perLine = configuration.perLine,
        remainder = index % perLine,
        length = searchedEmojis.length,
        currentLine = index ~/ perLine,
        maxLine = (length / perLine).ceil();

    final heightBefore = currentLine * emojiHeight;
    if (key == LogicalKeyboardKey.arrowUp) {
      if (currentLine == 0) {
        final exceptLine = max(0, maxLine - 1);
        changeSelectedIndex(min(exceptLine * perLine + remainder, length - 1));
      } else if (currentLine > 0) {
        changeSelectedIndex(index - perLine);
      }
    } else if (key == LogicalKeyboardKey.arrowDown) {
      if (currentLine == maxLine - 1) {
        changeSelectedIndex(remainder);
      } else if (currentLine < maxLine - 1) {
        changeSelectedIndex(min(index + perLine, length - 1));
      }
    } else if (key == LogicalKeyboardKey.arrowLeft) {
      if (index == 0) {
        changeSelectedIndex(length - 1);
      } else if (index > 0) {
        changeSelectedIndex(index - 1);
      }
    } else if (key == LogicalKeyboardKey.arrowRight) {
      if (index == length - 1) {
        changeSelectedIndex(0);
      } else if (index < length - 1) {
        changeSelectedIndex(index + 1);
      }
    }
    final heightAfter =
        (selectedIndexNotifier.value ~/ configuration.perLine) * emojiHeight;

    if (mounted && (heightAfter != heightBefore)) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        _scrollToItem();
      });
    }
  }

  void _scrollToItem() {
    final noEmojis = searchedEmojis.isEmpty;
    if (noEmojis || !mounted || !scrollController.hasClients) return;
    final currentItem = selectedIndexNotifier.value;
    final exceptHeight = (currentItem ~/ configuration.perLine) * emojiHeight;
    final maxExtent = scrollController.position.maxScrollExtent;
    final jumpTo = (exceptHeight - maxExtent > 10 * emojiHeight)
        ? exceptHeight
        : min(exceptHeight, maxExtent);
    scrollController.animateTo(
      jumpTo,
      duration: Duration(milliseconds: 300),
      curve: Curves.linear,
    );
  }

  void _deleteCharacterAtSelection() {
    final selection = widget.editorState.selection;
    if (selection == null || !selection.isCollapsed) {
      return;
    }

    final node = widget.editorState.getNodeAtPath(selection.end.path);
    final delta = node?.delta;
    if (node == null || delta == null) {
      return;
    }

    search = delta.toPlainText().substring(
          startOffset,
          startOffset + _search.length - 1,
        );
  }

  bool _canDeleteLastCharacter() {
    final selection = widget.editorState.selection;
    if (selection == null || !selection.isCollapsed) {
      return false;
    }

    final delta = widget.editorState.getNodeAtPath(selection.start.path)?.delta;
    if (delta == null) {
      return false;
    }

    return delta.isNotEmpty;
  }
}

typedef SelectEmojiItemHandler = void Function(
  BuildContext context,
  (int start, int end) replacement,
  String emoji,
);
