import 'dart:io';

import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactive_viewer_boundary.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/utils/extension/string_ext.dart';
import 'package:PiliPlus/utils/image_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show HapticFeedback;
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';

/// https://github.com/qq326646683/interactiveviewer_gallery

/// Builds a carousel controlled by a [PageView] for the tweet media sources.
///
/// Used for showing a full screen view of the [TweetMedia] sources.
///
/// The sources can be panned and zoomed interactively using an
/// [InteractiveViewer].
/// An [InteractiveViewerBoundary] is used to detect when the boundary of the
/// source is hit after zooming in to disable or enable the swiping gesture of
/// the [PageView].
///
typedef IndexedFocusedWidgetBuilder =
    Widget Function(
      BuildContext context,
      int index,
      bool isFocus,
      bool enablePageView,
    );

typedef IndexedTagStringBuilder = String Function(int index);

class InteractiveviewerGallery extends StatefulWidget {
  const InteractiveviewerGallery({
    super.key,
    required this.sources,
    required this.initIndex,
    this.itemBuilder,
    this.maxScale = 8,
    this.minScale = 1.0,
    required this.quality,
    this.onClose,
  });

  final int quality;

  /// The sources to show.
  final List<SourceModel> sources;

  /// The index of the first source in [sources] to show.
  final int initIndex;

  /// The item content
  final IndexedFocusedWidgetBuilder? itemBuilder;

  final double maxScale;

  final double minScale;

  final VoidCallback? onClose;

  @override
  State<InteractiveviewerGallery> createState() =>
      _InteractiveviewerGalleryState();
}

class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
    with SingleTickerProviderStateMixin {
  late final PageController _pageController;
  late final TransformationController _transformationController;

  /// The controller to animate the transformation value of the
  /// [InteractiveViewer] when it should reset.
  late AnimationController _animationController;
  Animation<Matrix4>? _animation;

  /// `true` when an source is zoomed in and not at the at a horizontal boundary
  /// to disable the [PageView].
  bool _enablePageView = true;

  late Offset _doubleTapLocalPosition;

  late final RxInt currentIndex = widget.initIndex.obs;

  late final int _quality = Pref.previewQ;

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

    _pageController = PageController(initialPage: widget.initIndex);

    _transformationController = TransformationController();

    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    )..addListener(listener);

    final item = widget.sources[currentIndex.value];
    if (item.sourceType == SourceType.livePhoto) {
      _onPlay(item.liveUrl!);
    }
  }

  void listener() {
    _transformationController.value = _animation?.value ?? Matrix4.identity();
  }

  @override
  void dispose() {
    widget.onClose?.call();
    _player?.dispose();
    _pageController.dispose();
    _animationController
      ..removeListener(listener)
      ..dispose();
    _transformationController.dispose();
    for (final item in widget.sources) {
      if (item.sourceType == SourceType.networkImage) {
        CachedNetworkImageProvider(_getActualUrl(item.url)).evict();
      }
    }
    super.dispose();
  }

  /// When the source gets scaled up, the swipe up / down to dismiss gets
  /// disabled.
  ///
  /// When the scale resets, the dismiss and the page view swiping gets enabled.
  void _onScaleChanged(double scale) {
    final bool initialScale = scale <= widget.minScale;

    if (initialScale) {
      if (!_enablePageView) {
        setState(() {
          _enablePageView = true;
        });
      }
    } else {
      if (_enablePageView) {
        setState(() {
          _enablePageView = false;
        });
      }
    }
  }

  /// When the left boundary has been hit after scaling up the source, the page
  /// view swiping gets enabled if it has a page to swipe to.
  void _onLeftBoundaryHit() {
    if (!_enablePageView && _pageController.page!.floor() > 0) {
      setState(() {
        _enablePageView = true;
      });
    }
  }

  /// When the right boundary has been hit after scaling up the source, the page
  /// view swiping gets enabled if it has a page to swipe to.
  void _onRightBoundaryHit() {
    if (!_enablePageView &&
        _pageController.page!.floor() < widget.sources.length - 1) {
      setState(() {
        _enablePageView = true;
      });
    }
  }

  /// When the source has been scaled up and no horizontal boundary has been hit,
  /// the page view swiping gets disabled.
  void _onNoBoundaryHit() {
    if (_enablePageView) {
      setState(() {
        _enablePageView = false;
      });
    }
  }

  void _onPlay(String liveUrl) {
    _player ??= Player();
    _videoController ??= VideoController(_player!);
    _player!.open(Media(liveUrl));
  }

  /// When the page view changed its page, the source will animate back into the
  /// original scale if it was scaled up.
  ///
  /// Additionally the swipe up / down to dismiss gets enabled.
  void _onPageChanged(int page) {
    _player?.pause();
    currentIndex.value = page;
    final item = widget.sources[page];
    if (item.sourceType == SourceType.livePhoto) {
      _onPlay(item.liveUrl!);
    }
    if (_transformationController.value != Matrix4.identity()) {
      // animate the reset for the transformation of the interactive viewer

      _animation =
          Matrix4Tween(
            begin: _transformationController.value,
            end: Matrix4.identity(),
          ).animate(
            CurveTween(curve: Curves.easeOut).animate(_animationController),
          );

      _animationController.forward(from: 0);
    }
  }

  String _getActualUrl(String url) {
    return _quality != 100
        ? ImageUtils.thumbnailUrl(url, _quality)
        : url.http2https;
  }

  Player? _player;
  VideoController? _videoController;

  @override
  Widget build(BuildContext context) {
    return Stack(
      clipBehavior: Clip.none,
      children: [
        InteractiveViewerBoundary(
          controller: _transformationController,
          boundaryWidth: MediaQuery.widthOf(context),
          onScaleChanged: _onScaleChanged,
          onLeftBoundaryHit: _onLeftBoundaryHit,
          onRightBoundaryHit: _onRightBoundaryHit,
          onNoBoundaryHit: _onNoBoundaryHit,
          maxScale: widget.maxScale,
          minScale: widget.minScale,
          onDismissed: Get.back,
          onReset: () {
            if (!_enablePageView) {
              setState(() {
                _enablePageView = true;
              });
            }
          },
          child: PageView.builder(
            onPageChanged: _onPageChanged,
            controller: _pageController,
            physics: _enablePageView
                ? null
                : const NeverScrollableScrollPhysics(),
            itemCount: widget.sources.length,
            itemBuilder: (BuildContext context, int index) {
              final item = widget.sources[index];
              final isFileImg = item.sourceType == SourceType.fileImage;
              return GestureDetector(
                behavior: HitTestBehavior.opaque,
                onTap: () => EasyThrottle.throttle(
                  'preview',
                  const Duration(milliseconds: 555),
                  Get.back,
                ),
                onDoubleTapDown: (TapDownDetails details) {
                  _doubleTapLocalPosition = details.localPosition;
                },
                onDoubleTap: () => EasyThrottle.throttle(
                  'preview',
                  const Duration(milliseconds: 555),
                  onDoubleTap,
                ),
                onLongPress: !isFileImg ? () => onLongPress(item) : null,
                onSecondaryTapUp: PlatformUtils.isDesktop && !isFileImg
                    ? (e) => _showDesktopMenu(e.globalPosition, item)
                    : null,
                child: widget.itemBuilder != null
                    ? widget.itemBuilder!(
                        context,
                        index,
                        index == currentIndex.value,
                        _enablePageView,
                      )
                    : _itemBuilder(index, item),
              );
            },
          ),
        ),
        Positioned(
          bottom: 0,
          left: 0,
          right: 0,
          child: Container(
            padding:
                MediaQuery.viewPaddingOf(context) +
                const EdgeInsets.fromLTRB(12, 8, 20, 8),
            decoration: _enablePageView
                ? BoxDecoration(
                    gradient: LinearGradient(
                      begin: Alignment.topCenter,
                      end: Alignment.bottomCenter,
                      colors: [
                        Colors.transparent,
                        Colors.black.withValues(alpha: 0.3),
                      ],
                    ),
                  )
                : null,
            alignment: Alignment.center,
            child: Obx(
              () => Text(
                "${currentIndex.value + 1}/${widget.sources.length}",
                style: const TextStyle(color: Colors.white),
              ),
            ),
          ),
        ),
      ],
    );
  }

  Widget _itemBuilder(int index, SourceModel item) {
    return Center(
      child: Hero(
        tag: item.url,
        child: switch (item.sourceType) {
          SourceType.fileImage => Image(
            filterQuality: FilterQuality.low,
            image: FileImage(File(item.url)),
          ),
          SourceType.networkImage => CachedNetworkImage(
            fadeInDuration: Duration.zero,
            fadeOutDuration: Duration.zero,
            imageUrl: _getActualUrl(item.url),
            placeholderFadeInDuration: Duration.zero,
            placeholder: (context, url) {
              if (widget.quality == _quality) {
                return const SizedBox.expand();
              }
              return CachedNetworkImage(
                fadeInDuration: Duration.zero,
                fadeOutDuration: Duration.zero,
                imageUrl: ImageUtils.thumbnailUrl(item.url, widget.quality),
                placeholder: (_, _) => const SizedBox.expand(),
              );
            },
          ),
          SourceType.livePhoto => Obx(
            () => currentIndex.value == index
                ? IgnorePointer(
                    child: Video(
                      controller: _videoController!,
                      fill: Colors.transparent,
                    ),
                  )
                : const SizedBox.shrink(),
          ),
        },
      ),
    );
  }

  void onDoubleTap() {
    Matrix4 matrix = _transformationController.value.clone();
    double currentScale = matrix.storage[0];

    double targetScale = widget.minScale;

    if (currentScale <= widget.minScale) {
      targetScale = widget.maxScale * 0.4;
    }

    double offSetX = targetScale == 1.0
        ? 0.0
        : -_doubleTapLocalPosition.dx * (targetScale - 1);
    double offSetY = targetScale == 1.0
        ? 0.0
        : -_doubleTapLocalPosition.dy * (targetScale - 1);

    matrix = Matrix4.fromList([
      targetScale,
      matrix.row1.x,
      matrix.row2.x,
      matrix.row3.x,
      matrix.row0.y,
      targetScale,
      matrix.row2.y,
      matrix.row3.y,
      matrix.row0.z,
      matrix.row1.z,
      targetScale,
      matrix.row3.z,
      offSetX,
      offSetY,
      matrix.row2.w,
      matrix.row3.w,
    ]);

    _animation =
        Matrix4Tween(
          begin: _transformationController.value,
          end: matrix,
        ).animate(
          CurveTween(curve: Curves.easeOut).animate(_animationController),
        );
    _animationController
        .forward(from: 0)
        .whenComplete(() => _onScaleChanged(targetScale));
  }

  void onLongPress(SourceModel item) {
    HapticFeedback.mediumImpact();
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          clipBehavior: Clip.hardEdge,
          contentPadding: const EdgeInsets.symmetric(vertical: 12),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              if (PlatformUtils.isMobile)
                ListTile(
                  onTap: () {
                    Get.back();
                    ImageUtils.onShareImg(item.url);
                  },
                  dense: true,
                  title: const Text('分享', style: TextStyle(fontSize: 14)),
                ),
              ListTile(
                onTap: () {
                  Get.back();
                  Utils.copyText(item.url);
                },
                dense: true,
                title: const Text('复制链接', style: TextStyle(fontSize: 14)),
              ),
              ListTile(
                onTap: () {
                  Get.back();
                  ImageUtils.downloadImg([item.url]);
                },
                dense: true,
                title: const Text('保存图片', style: TextStyle(fontSize: 14)),
              ),
              if (PlatformUtils.isDesktop)
                ListTile(
                  onTap: () {
                    Get.back();
                    PageUtils.launchURL(item.url);
                  },
                  dense: true,
                  title: const Text('网页打开', style: TextStyle(fontSize: 14)),
                )
              else if (widget.sources.length > 1)
                ListTile(
                  onTap: () {
                    Get.back();
                    ImageUtils.downloadImg(
                      widget.sources.map((item) => item.url).toList(),
                    );
                  },
                  dense: true,
                  title: const Text('保存全部图片', style: TextStyle(fontSize: 14)),
                ),
              if (item.sourceType == SourceType.livePhoto)
                ListTile(
                  onTap: () {
                    Get.back();
                    ImageUtils.downloadLivePhoto(
                      url: item.url,
                      liveUrl: item.liveUrl!,
                      width: item.width!,
                      height: item.height!,
                    );
                  },
                  dense: true,
                  title: Text(
                    '保存${Platform.isIOS ? ' Live Photo' : '视频'}',
                    style: const TextStyle(fontSize: 14),
                  ),
                ),
            ],
          ),
        );
      },
    );
  }

  void _showDesktopMenu(Offset offset, SourceModel item) {
    showMenu(
      context: context,
      position: PageUtils.menuPosition(offset),
      items: [
        PopupMenuItem(
          height: 42,
          onTap: () => Utils.copyText(item.url),
          child: const Text('复制链接', style: TextStyle(fontSize: 14)),
        ),
        PopupMenuItem(
          height: 42,
          onTap: () => ImageUtils.downloadImg([item.url]),
          child: const Text('保存图片', style: TextStyle(fontSize: 14)),
        ),
        PopupMenuItem(
          height: 42,
          onTap: () => PageUtils.launchURL(item.url),
          child: const Text('网页打开', style: TextStyle(fontSize: 14)),
        ),
        if (item.sourceType == SourceType.livePhoto)
          PopupMenuItem(
            height: 42,
            onTap: () => ImageUtils.downloadLivePhoto(
              url: item.url,
              liveUrl: item.liveUrl!,
              width: item.width!,
              height: item.height!,
            ),
            child: const Text('保存视频', style: TextStyle(fontSize: 14)),
          ),
      ],
    );
  }
}
