import 'package:flutter/widgets.dart';

import '../photo_view.dart';
import 'core/photo_view_core.dart';
import 'photo_view_default_widgets.dart';
import 'utils/photo_view_utils.dart';

class ImageWrapper extends StatefulWidget {
  const ImageWrapper({
    super.key,
    required this.imageProvider,
    required this.loadingBuilder,
    required this.backgroundDecoration,
    required this.semanticLabel,
    required this.gaplessPlayback,
    required this.heroAttributes,
    required this.scaleStateChangedCallback,
    required this.enableRotation,
    required this.controller,
    required this.scaleStateController,
    required this.maxScale,
    required this.minScale,
    required this.initialScale,
    required this.basePosition,
    required this.scaleStateCycle,
    required this.onTapUp,
    required this.onTapDown,
    required this.onDragStart,
    required this.onDragEnd,
    required this.onDragUpdate,
    required this.onDragCancel,
    required this.onScaleEnd,
    required this.onLongPressStart,
    required this.outerSize,
    required this.gestureDetectorBehavior,
    required this.tightMode,
    required this.filterQuality,
    required this.disableGestures,
    this.disableScaleGestures,
    required this.errorBuilder,
    required this.enablePanAlways,
    required this.index,
  });

  final ImageProvider imageProvider;
  final LoadingBuilder? loadingBuilder;
  final ImageErrorWidgetBuilder? errorBuilder;
  final BoxDecoration backgroundDecoration;
  final String? semanticLabel;
  final bool gaplessPlayback;
  final PhotoViewHeroAttributes? heroAttributes;
  final ValueChanged<PhotoViewScaleState>? scaleStateChangedCallback;
  final bool enableRotation;
  final dynamic maxScale;
  final dynamic minScale;
  final dynamic initialScale;
  final PhotoViewControllerBase controller;
  final PhotoViewScaleStateController scaleStateController;
  final Alignment? basePosition;
  final ScaleStateCycle? scaleStateCycle;
  final PhotoViewImageTapUpCallback? onTapUp;
  final PhotoViewImageTapDownCallback? onTapDown;
  final PhotoViewImageDragStartCallback? onDragStart;
  final PhotoViewImageDragEndCallback? onDragEnd;
  final PhotoViewImageDragUpdateCallback? onDragUpdate;
  final VoidCallback? onDragCancel;
  final PhotoViewImageScaleEndCallback? onScaleEnd;
  final PhotoViewImageLongPressStartCallback? onLongPressStart;
  final Size outerSize;
  final HitTestBehavior? gestureDetectorBehavior;
  final bool? tightMode;
  final FilterQuality? filterQuality;
  final bool? disableGestures;
  final bool? disableScaleGestures;
  final bool? enablePanAlways;
  final int index;

  @override
  createState() => _ImageWrapperState();
}

class _ImageWrapperState extends State<ImageWrapper> {
  ImageStreamListener? _imageStreamListener;
  ImageStream? _imageStream;
  ImageChunkEvent? _loadingProgress;
  ImageInfo? _imageInfo;
  bool _loading = true;
  Size? _imageSize;
  Object? _lastException;
  StackTrace? _lastStack;
  bool _didLoadSynchronously = false;

  @override
  void dispose() {
    super.dispose();
    _stopImageStream();
  }

  @override
  void didChangeDependencies() {
    _resolveImage();
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(ImageWrapper oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.imageProvider != oldWidget.imageProvider) {
      _resolveImage();
    }
  }

  // Should be called only when _imageSize is not null
  ScaleBoundaries get scaleBoundaries {
    return ScaleBoundaries(
      widget.minScale ?? 0.0,
      widget.maxScale ?? double.infinity,
      widget.initialScale ?? PhotoViewComputedScale.contained,
      widget.outerSize,
      _imageSize!,
    );
  }

  // retrieve image from the provider
  void _resolveImage() {
    final ImageStream newStream = widget.imageProvider.resolve(const ImageConfiguration());
    _updateSourceStream(newStream);
  }

  ImageStreamListener _getOrCreateListener() {
    void handleImageChunk(ImageChunkEvent event) {
      setState(() {
        _loadingProgress = event;
        _lastException = null;
      });
    }

    void handleImageFrame(ImageInfo info, bool synchronousCall) {
      setupCB() {
        _imageSize = Size(info.image.width.toDouble(), info.image.height.toDouble());
        _loading = false;
        _imageInfo = _imageInfo;

        _loadingProgress = null;
        _lastException = null;
        _lastStack = null;

        _didLoadSynchronously = synchronousCall;
        widget.controller.scaleBoundaries = scaleBoundaries;
      }

      synchronousCall && !_didLoadSynchronously ? setupCB() : setState(setupCB);
    }

    void handleError(dynamic error, StackTrace? stackTrace) {
      setState(() {
        _loading = false;
        _lastException = error;
        _lastStack = stackTrace;
      });
      assert(() {
        if (widget.errorBuilder == null) {
          throw error;
        }
        return true;
      }());
    }

    _imageStreamListener = ImageStreamListener(handleImageFrame, onChunk: handleImageChunk, onError: handleError);

    return _imageStreamListener!;
  }

  void _updateSourceStream(ImageStream newStream) {
    if (_imageStream?.key == newStream.key) {
      return;
    }
    _imageStream?.removeListener(_imageStreamListener!);
    _imageStream = newStream;
    _imageStream!.addListener(_getOrCreateListener());
  }

  void _stopImageStream() {
    _imageStream?.removeListener(_imageStreamListener!);
  }

  @override
  Widget build(BuildContext context) {
    if (_loading || _lastException != null) {
      return CustomChildWrapper(
        childSize: null,
        backgroundDecoration: widget.backgroundDecoration,
        heroAttributes: widget.heroAttributes,
        scaleStateChangedCallback: widget.scaleStateChangedCallback,
        enableRotation: widget.enableRotation,
        controller: widget.controller,
        scaleStateController: widget.scaleStateController,
        maxScale: widget.maxScale,
        minScale: widget.minScale,
        initialScale: widget.initialScale,
        basePosition: widget.basePosition,
        scaleStateCycle: widget.scaleStateCycle,
        onTapUp: widget.onTapUp,
        onTapDown: widget.onTapDown,
        onDragStart: widget.onDragStart,
        onDragEnd: widget.onDragEnd,
        onDragUpdate: widget.onDragUpdate,
        onDragCancel: widget.onDragCancel,
        onScaleEnd: widget.onScaleEnd,
        onLongPressStart: widget.onLongPressStart,
        outerSize: widget.outerSize,
        gestureDetectorBehavior: widget.gestureDetectorBehavior,
        tightMode: widget.tightMode,
        filterQuality: widget.filterQuality,
        disableGestures: widget.disableGestures,
        disableScaleGestures: true,
        enablePanAlways: widget.enablePanAlways,
        child: _loading ? _buildLoading(context) : _buildError(context),
      );
    }

    return PhotoViewCore(
      imageProvider: widget.imageProvider,
      backgroundDecoration: widget.backgroundDecoration,
      semanticLabel: widget.semanticLabel,
      gaplessPlayback: widget.gaplessPlayback,
      enableRotation: widget.enableRotation,
      heroAttributes: widget.heroAttributes,
      basePosition: widget.basePosition ?? Alignment.center,
      controller: widget.controller,
      scaleStateController: widget.scaleStateController,
      scaleStateCycle: widget.scaleStateCycle ?? defaultScaleStateCycle,
      scaleBoundaries: scaleBoundaries,
      onTapUp: widget.onTapUp,
      onTapDown: widget.onTapDown,
      onDragStart: widget.onDragStart,
      onDragEnd: widget.onDragEnd,
      onDragUpdate: widget.onDragUpdate,
      onDragCancel: widget.onDragCancel,
      onScaleEnd: widget.onScaleEnd,
      onLongPressStart: widget.onLongPressStart,
      gestureDetectorBehavior: widget.gestureDetectorBehavior,
      tightMode: widget.tightMode ?? false,
      filterQuality: widget.filterQuality ?? FilterQuality.none,
      disableGestures: widget.disableGestures ?? false,
      disableScaleGestures: widget.disableScaleGestures ?? false,
      enablePanAlways: widget.enablePanAlways ?? false,
    );
  }

  Widget _buildLoading(BuildContext context) {
    if (widget.loadingBuilder != null) {
      return widget.loadingBuilder!(context, _loadingProgress, widget.index);
    }

    return PhotoViewDefaultLoading(event: _loadingProgress);
  }

  Widget _buildError(BuildContext context) {
    if (widget.errorBuilder != null) {
      return widget.errorBuilder!(context, _lastException!, _lastStack);
    }
    return PhotoViewDefaultError(decoration: widget.backgroundDecoration);
  }
}

class CustomChildWrapper extends StatelessWidget {
  const CustomChildWrapper({
    super.key,
    this.child,
    required this.childSize,
    required this.backgroundDecoration,
    this.heroAttributes,
    this.scaleStateChangedCallback,
    required this.enableRotation,
    required this.controller,
    required this.scaleStateController,
    required this.maxScale,
    required this.minScale,
    required this.initialScale,
    required this.basePosition,
    required this.scaleStateCycle,
    this.onTapUp,
    this.onTapDown,
    this.onDragStart,
    this.onDragEnd,
    this.onDragUpdate,
    this.onDragCancel,
    this.onScaleEnd,
    this.onLongPressStart,
    required this.outerSize,
    this.gestureDetectorBehavior,
    required this.tightMode,
    required this.filterQuality,
    required this.disableGestures,
    this.disableScaleGestures,
    required this.enablePanAlways,
  });

  final Widget? child;
  final Size? childSize;
  final Decoration backgroundDecoration;
  final PhotoViewHeroAttributes? heroAttributes;
  final ValueChanged<PhotoViewScaleState>? scaleStateChangedCallback;
  final bool enableRotation;

  final PhotoViewControllerBase controller;
  final PhotoViewScaleStateController scaleStateController;

  final dynamic maxScale;
  final dynamic minScale;
  final dynamic initialScale;

  final Alignment? basePosition;
  final ScaleStateCycle? scaleStateCycle;
  final PhotoViewImageTapUpCallback? onTapUp;
  final PhotoViewImageTapDownCallback? onTapDown;
  final PhotoViewImageDragStartCallback? onDragStart;
  final PhotoViewImageDragEndCallback? onDragEnd;
  final PhotoViewImageDragUpdateCallback? onDragUpdate;
  final VoidCallback? onDragCancel;
  final PhotoViewImageScaleEndCallback? onScaleEnd;
  final PhotoViewImageLongPressStartCallback? onLongPressStart;
  final Size outerSize;
  final HitTestBehavior? gestureDetectorBehavior;
  final bool? tightMode;
  final FilterQuality? filterQuality;
  final bool? disableScaleGestures;
  final bool? disableGestures;
  final bool? enablePanAlways;

  @override
  Widget build(BuildContext context) {
    final scaleBoundaries = ScaleBoundaries(
      minScale ?? 0.0,
      maxScale ?? double.infinity,
      initialScale ?? PhotoViewComputedScale.contained,
      outerSize,
      childSize ?? outerSize,
    );

    return PhotoViewCore.customChild(
      customChild: child,
      backgroundDecoration: backgroundDecoration,
      enableRotation: enableRotation,
      heroAttributes: heroAttributes,
      controller: controller,
      scaleStateController: scaleStateController,
      scaleStateCycle: scaleStateCycle ?? defaultScaleStateCycle,
      basePosition: basePosition ?? Alignment.center,
      scaleBoundaries: scaleBoundaries,
      onTapUp: onTapUp,
      onTapDown: onTapDown,
      onDragStart: onDragStart,
      onDragEnd: onDragEnd,
      onDragUpdate: onDragUpdate,
      onDragCancel: onDragCancel,
      onScaleEnd: onScaleEnd,
      onLongPressStart: onLongPressStart,
      gestureDetectorBehavior: gestureDetectorBehavior,
      tightMode: tightMode ?? false,
      filterQuality: filterQuality ?? FilterQuality.none,
      disableGestures: disableGestures ?? false,
      disableScaleGestures: disableScaleGestures ?? false,
      enablePanAlways: enablePanAlways ?? false,
    );
  }
}
