// ignore_for_file: inference_failure_on_function_invocation

import 'dart:async';
import 'dart:collection';
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers_viewer.dart';
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
import 'package:twonly/src/views/camera/share_image_view.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart';
import 'package:twonly/src/views/components/notification_badge.dart';
import 'package:video_player/video_player.dart';

List<Layer> layers = [];
List<Layer> undoLayers = [];
List<Layer> removedLayers = [];

class ShareImageEditorView extends StatefulWidget {
  const ShareImageEditorView({
    required this.sharedFromGallery,
    required this.mediaFileService,
    super.key,
    this.imageBytesFuture,
    this.sendToGroup,
  });
  final Future<Uint8List?>? imageBytesFuture;
  final Group? sendToGroup;
  final bool sharedFromGallery;
  final MediaFileService mediaFileService;
  @override
  State<ShareImageEditorView> createState() => _ShareImageEditorView();
}

class _ShareImageEditorView extends State<ShareImageEditorView> {
  double tabDownPosition = 0;
  bool sendingOrLoadingImage = true;
  bool loadingImage = true;
  bool isDisposed = false;
  HashSet<String> selectedGroupIds = HashSet();
  double widthRatio = 1;
  double heightRatio = 1;
  double pixelRatio = 1;
  Uint8List? imageBytes;
  VideoPlayerController? videoController;
  ImageItem currentImage = ImageItem();
  ScreenshotController screenshotController = ScreenshotController();

  MediaFileService get mediaService => widget.mediaFileService;
  MediaFile get media => widget.mediaFileService.mediaFile;

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

    if (media.type != MediaType.gif) {
      layers.add(FilterLayerData());
    }

    if (widget.sendToGroup != null) {
      selectedGroupIds.add(widget.sendToGroup!.groupId);
    }

    if (widget.mediaFileService.mediaFile.type == MediaType.image ||
        widget.mediaFileService.mediaFile.type == MediaType.gif) {
      if (widget.imageBytesFuture != null) {
        loadImage(widget.imageBytesFuture!);
      } else {
        if (widget.mediaFileService.tempPath.existsSync()) {
          loadImage(widget.mediaFileService.tempPath.readAsBytes());
        } else if (widget.mediaFileService.originalPath.existsSync()) {
          loadImage(widget.mediaFileService.originalPath.readAsBytes());
        }
      }
    }

    if (media.type == MediaType.video) {
      setState(() {
        sendingOrLoadingImage = false;
        loadingImage = false;
      });
      videoController = VideoPlayerController.file(mediaService.originalPath);
      videoController?.setLooping(true);
      videoController?.initialize().then((_) async {
        await videoController!.play();
        setState(() {});
        // ignore: invalid_return_type_for_catch_error, argument_type_not_assignable_to_error_handler
      }).catchError(Log.error);
    }
  }

  @override
  void dispose() {
    isDisposed = true;
    layers.clear();
    videoController?.dispose();
    twonlyDB.mediaFilesDao.updateAllMediaFiles(
      const MediaFilesCompanion(
        isDraftMedia: Value(false),
      ),
    );
    super.dispose();
  }

  void updateSelectedGroupIds(String groupId, bool checked) {
    if (checked) {
      if (media.requiresAuthentication) {
        selectedGroupIds.clear();
      }
      selectedGroupIds.add(groupId);
    } else {
      selectedGroupIds.remove(groupId);
    }
    setState(() {});
  }

  List<Widget> get actionsAtTheRight {
    if (layers.isNotEmpty &&
        layers.last.isEditing &&
        layers.last.hasCustomActionButtons) {
      return [];
    }
    return <Widget>[
      if (media.type != MediaType.gif)
        ActionButton(
          Icons.text_fields_rounded,
          tooltipText: context.lang.addTextItem,
          onPressed: () async {
            layers = layers.where((x) => !x.isDeleted).toList();
            if (layers.any((x) => x.isEditing)) return;
            undoLayers.clear();
            removedLayers.clear();
            layers.add(
              TextLayerData(
                textLayersBefore: layers.whereType<TextLayerData>().length,
              ),
            );
            setState(() {});
          },
        ),
      const SizedBox(height: 8),
      if (media.type != MediaType.gif)
        ActionButton(
          Icons.draw_rounded,
          tooltipText: context.lang.addDrawing,
          onPressed: () async {
            undoLayers.clear();
            removedLayers.clear();
            layers.add(DrawLayerData());
            setState(() {});
          },
        ),
      const SizedBox(height: 8),
      if (media.type != MediaType.gif)
        ActionButton(
          Icons.add_reaction_outlined,
          tooltipText: context.lang.addEmoji,
          onPressed: () async {
            final layer = await showModalBottomSheet(
              context: context,
              backgroundColor: Colors.black,
              builder: (BuildContext context) {
                return const EmojiPickerBottom();
              },
            ) as Layer?;
            if (layer == null) return;
            undoLayers.clear();
            removedLayers.clear();
            layers.add(layer);
            setState(() {});
          },
        ),
      const SizedBox(height: 8),
      NotificationBadge(
        count: (media.type == MediaType.video)
            ? '0'
            : media.displayLimitInMilliseconds == null
                ? '∞'
                : (media.displayLimitInMilliseconds! ~/ 1000).toString(),
        child: ActionButton(
          (media.type == MediaType.video)
              ? media.displayLimitInMilliseconds == null
                  ? Icons.repeat_rounded
                  : Icons.repeat_one_rounded
              : Icons.timer_outlined,
          tooltipText: context.lang.protectAsARealTwonly,
          onPressed: () async {
            if (media.type == MediaType.video) {
              await mediaService.setDisplayLimit(
                (media.displayLimitInMilliseconds == null) ? 0 : null,
              );
              if (!mounted) return;
              setState(() {});
              return;
            }
            int? maxShowTime;
            if (media.displayLimitInMilliseconds == null) {
              maxShowTime = 1000;
            } else if (media.displayLimitInMilliseconds == 1000) {
              maxShowTime = 5000;
            } else if (media.displayLimitInMilliseconds == 5000) {
              maxShowTime = 12000;
            } else if (media.displayLimitInMilliseconds == 12000) {
              maxShowTime = 20000;
            }
            await mediaService.setDisplayLimit(maxShowTime);
            if (!mounted) return;
            setState(() {});
            await updateUserdata((user) {
              user.defaultShowTime = maxShowTime;
              return user;
            });
          },
        ),
      ),
      if (media.type == MediaType.video)
        ActionButton(
          (mediaService.removeAudio)
              ? Icons.volume_off_rounded
              : Icons.volume_up_rounded,
          tooltipText: 'Enable Audio in Video',
          color: (mediaService.removeAudio)
              ? Colors.white.withAlpha(160)
              : Colors.white,
          onPressed: () async {
            await mediaService.toggleRemoveAudio();
            if (mediaService.removeAudio) {
              await videoController?.setVolume(0);
            } else {
              await videoController?.setVolume(100);
            }
            if (mounted) setState(() {});
          },
        ),
      const SizedBox(height: 8),
      ActionButton(
        FontAwesomeIcons.shieldHeart,
        tooltipText: context.lang.protectAsARealTwonly,
        color: media.requiresAuthentication
            ? Theme.of(context).colorScheme.primary
            : Colors.white,
        onPressed: () async {
          await mediaService.setRequiresAuth(!media.requiresAuthentication);
          selectedGroupIds = HashSet();
          setState(() {});
        },
      ),
    ];
  }

  Future<bool?> _showBackDialog() {
    return showDialog<bool>(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text(
            context.lang.dialogAskDeleteMediaFilePopTitle,
          ),
          actions: [
            FilledButton(
              child: Text(context.lang.dialogAskDeleteMediaFilePopDelete),
              onPressed: () {
                Navigator.pop(context, true);
              },
            ),
            TextButton(
              child: Text(context.lang.cancel),
              onPressed: () {
                Navigator.pop(context, false);
              },
            ),
          ],
        );
      },
    );
  }

  Future<void> askToCloseThenClose() async {
    final shouldPop = await _showBackDialog() ?? false;
    if (mounted && shouldPop) {
      Navigator.pop(context);
    }
  }

  List<Widget> get actionsAtTheTop {
    if (layers.isNotEmpty &&
        layers.last.isEditing &&
        layers.last.hasCustomActionButtons) {
      return [];
    }
    return [
      ActionButton(
        FontAwesomeIcons.xmark,
        tooltipText: context.lang.close,
        onPressed: () async {
          final nonImageFilterLayer = layers.where(
            (x) => x is! BackgroundLayerData && x is! FilterLayerData,
          );
          if (nonImageFilterLayer.isEmpty) {
            Navigator.pop(context, false);
          } else {
            await askToCloseThenClose();
          }
        },
      ),
      Expanded(child: Container()),
      const SizedBox(width: 8),
      ActionButton(
        FontAwesomeIcons.rotateLeft,
        tooltipText: context.lang.undo,
        disable: layers.where((x) => !x.isDeleted).length <= 2,
        onPressed: () {
          if (removedLayers.isNotEmpty) {
            final lastLayer = removedLayers.removeLast()
              ..isDeleted = false
              ..isEditing = false;
            layers.add(lastLayer);
            setState(() {});
            return;
          }
          layers = layers.where((x) => !x.isDeleted).toList();
          if (layers.length <= 2) {
            // do not remove image layer and filter layer
            return;
          }
          undoLayers.add(layers.removeLast());
          setState(() {});
        },
      ),
      const SizedBox(width: 8),
      ActionButton(
        FontAwesomeIcons.rotateRight,
        tooltipText: context.lang.redo,
        disable: undoLayers.isEmpty,
        onPressed: () {
          if (undoLayers.isEmpty) return;
          layers.add(undoLayers.removeLast());
          setState(() {});
        },
      ),
      const SizedBox(width: 70),
    ];
  }

  Future<void> pushShareImageView() async {
    final mediaStoreFuture = storeImageAsOriginal();

    await videoController?.pause();
    if (isDisposed || !mounted) return;
    final wasSend = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => ShareImageView(
          selectedGroupIds: selectedGroupIds,
          updateSelectedGroupIds: updateSelectedGroupIds,
          mediaStoreFuture: mediaStoreFuture,
          mediaFileService: mediaService,
        ),
      ),
    ) as bool?;
    if (wasSend != null && wasSend && mounted) {
      Navigator.pop(context, true);
    } else {
      await videoController?.play();
    }
  }

  Future<Uint8List?> getEditedImageBytes() async {
    if (layers.length == 1) {
      if (layers.first is BackgroundLayerData) {
        final image = (layers.first as BackgroundLayerData).image.bytes;
        return image;
      }
    }

    for (final x in layers) {
      x.showCustomButtons = false;
    }
    setState(() {});
    final image = await screenshotController.capture(
      pixelRatio: pixelRatio,
    );
    if (image == null) {
      Log.error('screenshotController did not return image bytes');
      return null;
    }

    for (final x in layers) {
      x.showCustomButtons = true;
    }
    if (mounted) {
      setState(() {});
    }
    return image;
  }

  Future<bool> storeImageAsOriginal() async {
    if (mediaService.overlayImagePath.existsSync()) {
      mediaService.overlayImagePath.deleteSync();
    }
    if (mediaService.tempPath.existsSync()) {
      mediaService.tempPath.deleteSync();
    }
    if (media.type == MediaType.gif) {
      mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
    } else {
      final imageBytes = await getEditedImageBytes();
      if (imageBytes == null) return false;
      if (media.type == MediaType.image || media.type == MediaType.gif) {
        mediaService.originalPath.writeAsBytesSync(imageBytes);
      } else if (media.type == MediaType.video) {
        mediaService.overlayImagePath.writeAsBytesSync(imageBytes);
      } else {
        Log.error('MediaType not supported: ${media.type}');
      }
    }

    // In case the image was already stored, then rename the stored image.
    if (mediaService.storedPath.existsSync()) {
      final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
        MediaFilesCompanion(
          type: Value(mediaService.mediaFile.type),
          createdAt: Value(DateTime.now()),
          stored: const Value(true),
        ),
      );
      if (mediaFile != null) {
        mediaService.storedPath
            .renameSync(MediaFileService(mediaFile).storedPath.path);
      }
    }
    return true;
  }

  Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async {
    imageBytes = await imageBytesFuture;

    // store this image so it can be used as a draft in case the app is restarted
    mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());

    await currentImage.load(imageBytes);
    if (isDisposed) return;

    if (!context.mounted) return;

    layers.insert(
      0,
      BackgroundLayerData(
        image: currentImage,
      ),
    );
    setState(() {
      sendingOrLoadingImage = false;
      loadingImage = false;
    });
  }

  Future<void> sendImageToSinglePerson() async {
    if (sendingOrLoadingImage) return;
    setState(() {
      sendingOrLoadingImage = true;
    });

    await storeImageAsOriginal();

    if (!context.mounted) return;

    // Insert media file into the messages database and start uploading process in the background
    await insertMediaFileInMessagesTable(
      mediaService,
      [widget.sendToGroup!.groupId],
    );

    if (context.mounted) {
      // ignore: use_build_context_synchronously
      Navigator.pop(context, true);
    }
  }

  @override
  Widget build(BuildContext context) {
    pixelRatio = MediaQuery.of(context).devicePixelRatio;

    return PopScope<bool?>(
      canPop: false,
      onPopInvokedWithResult: (bool didPop, bool? result) async {
        if (didPop) return;
        await askToCloseThenClose();
      },
      child: Scaffold(
        backgroundColor:
            widget.sharedFromGallery ? null : Colors.white.withAlpha(0),
        resizeToAvoidBottomInset: false,
        body: Stack(
          fit: StackFit.expand,
          children: [
            GestureDetector(
              onTapDown: (details) {
                if (details.globalPosition.dy > 60) {
                  tabDownPosition = details.globalPosition.dy - 60;
                } else {
                  tabDownPosition = details.globalPosition.dy;
                }
              },
              onTap: () {
                if (layers.any((x) => x.isEditing)) {
                  return;
                }
                layers = layers.where((x) => !x.isDeleted).toList();
                undoLayers.clear();
                removedLayers.clear();
                layers.add(
                  TextLayerData(
                    offset: Offset(0, tabDownPosition),
                    textLayersBefore: layers.whereType<TextLayerData>().length,
                  ),
                );
                setState(() {});
              },
              child: MediaViewSizing(
                requiredHeight: 59,
                bottomNavigation: ColoredBox(
                  color: Theme.of(context).colorScheme.surface,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      SaveToGalleryButton(
                        storeImageAsOriginal: storeImageAsOriginal,
                        mediaService: mediaService,
                        displayButtonLabel: widget.sendToGroup == null,
                        isLoading: loadingImage,
                      ),
                      if (widget.sendToGroup != null) const SizedBox(width: 10),
                      if (widget.sendToGroup != null)
                        OutlinedButton(
                          style: OutlinedButton.styleFrom(
                            iconColor: Theme.of(context).colorScheme.primary,
                            foregroundColor:
                                Theme.of(context).colorScheme.primary,
                          ),
                          onPressed: pushShareImageView,
                          child: const FaIcon(FontAwesomeIcons.userPlus),
                        ),
                      SizedBox(width: widget.sendToGroup == null ? 20 : 10),
                      FilledButton.icon(
                        icon: sendingOrLoadingImage
                            ? SizedBox(
                                height: 12,
                                width: 12,
                                child: CircularProgressIndicator(
                                  strokeWidth: 2,
                                  color: Theme.of(context)
                                      .colorScheme
                                      .inversePrimary,
                                ),
                              )
                            : const FaIcon(FontAwesomeIcons.solidPaperPlane),
                        onPressed: () async {
                          if (sendingOrLoadingImage) return;
                          if (widget.sendToGroup == null) {
                            return pushShareImageView();
                          }
                          await sendImageToSinglePerson();
                        },
                        style: ButtonStyle(
                          padding: WidgetStateProperty.all<EdgeInsets>(
                            const EdgeInsets.symmetric(
                              vertical: 10,
                              horizontal: 30,
                            ),
                          ),
                        ),
                        label: Text(
                          (widget.sendToGroup == null)
                              ? context.lang.shareImagedEditorShareWith
                              : substringBy(widget.sendToGroup!.groupName, 15),
                          style: const TextStyle(fontSize: 17),
                        ),
                      ),
                    ],
                  ),
                ),
                child: SizedBox(
                  height: currentImage.height / pixelRatio,
                  width: currentImage.width / pixelRatio,
                  child: Stack(
                    children: [
                      if (videoController != null)
                        Positioned.fill(
                          child: VideoPlayer(videoController!),
                        ),
                      Screenshot(
                        controller: screenshotController,
                        child: LayersViewer(
                          layers: layers.where((x) => !x.isDeleted).toList(),
                          onUpdate: () {
                            for (final layer in layers) {
                              layer.isEditing = false;
                              if (layer.isDeleted) {
                                removedLayers.add(layer);
                              }
                            }
                            layers = layers.where((x) => !x.isDeleted).toList();
                            setState(() {});
                          },
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
            Positioned(
              top: 10,
              left: 5,
              right: 0,
              child: SafeArea(
                child: Row(
                  children: actionsAtTheTop,
                ),
              ),
            ),
            Positioned(
              right: 6,
              top: 100,
              child: Container(
                alignment: Alignment.bottomCenter,
                padding: const EdgeInsets.symmetric(vertical: 16),
                child: SafeArea(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: actionsAtTheRight,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
