import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class SpriteAnimationWidget extends StatefulWidget {
  final String assetPath;
  final double tileWidth;
  final double tileHeight;
  final int frameCount;
  final double scale;
  final bool flip;
  final bool loop;
  final Duration? customDuration;
  final String? fallbackAssetPath;

  const SpriteAnimationWidget({
    super.key,
    required this.assetPath,
    this.tileWidth = 100.0,
    this.tileHeight = 100.0,
    this.frameCount = 6,
    this.scale = 1.0,
    this.flip = false,
    this.loop = true,
    this.customDuration,
    this.fallbackAssetPath,
  });

  @override
  State<SpriteAnimationWidget> createState() => _SpriteAnimationWidgetState();
}

class _SpriteAnimationWidgetState extends State<SpriteAnimationWidget>
    with SingleTickerProviderStateMixin {
  ui.Image? _image;
  late AnimationController _controller;
  bool _isLoading = true;
  int _calculatedFrameCount = 6;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: widget.customDuration ?? const Duration(milliseconds: 600),
    );
    _loadImage();
  }

  @override
  void didUpdateWidget(covariant SpriteAnimationWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.assetPath != widget.assetPath) {
      // Don't set _isLoading = true to avoid flickering.
      // Keep showing the old image until the new one is loaded.
      _controller.reset();
      _loadImage();
    }
  }

  Future<void> _loadImage() async {
    try {
      await _loadAsset(widget.assetPath);
    } catch (e) {
      debugPrint('Failed to load sprite image: $e');
      if (widget.fallbackAssetPath != null) {
        debugPrint(
          'Attempting to load fallback image: ${widget.fallbackAssetPath}',
        );
        try {
          await _loadAsset(widget.fallbackAssetPath!);
        } catch (e2) {
          debugPrint('Failed to load fallback image: $e2');
        }
      }
    }
  }

  Future<void> _loadAsset(String path) async {
    final ByteData data = await rootBundle.load(path);
    final List<int> bytes = data.buffer.asUint8List();
    final Completer<ui.Image> completer = Completer();
    ui.decodeImageFromList(Uint8List.fromList(bytes), (ui.Image img) {
      completer.complete(img);
    });
    final image = await completer.future;

    if (mounted) {
      setState(() {
        _image = image;
        _isLoading = false;
        // Use provided frameCount, but clamp to available frames in image
        int maxFrames = (image.width / widget.tileWidth).floor();
        _calculatedFrameCount = widget.frameCount > maxFrames
            ? maxFrames
            : widget.frameCount;

        // Adjust duration based on frame count if not custom
        if (widget.customDuration == null) {
          _controller.duration = Duration(
            milliseconds: _calculatedFrameCount * 100,
          );
        }

        if (widget.loop) {
          _controller.repeat();
        } else {
          _controller.forward();
        }
      });
    }
  }

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

  @override
  Widget build(BuildContext context) {
    if (_image == null) {
      return SizedBox(
        width: widget.tileWidth * widget.scale,
        height: widget.tileHeight * widget.scale,
      );
    }

    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        int frame;
        if (!widget.loop) {
          frame = (_controller.value * _calculatedFrameCount)
              .floor()
              .clamp(0, _calculatedFrameCount - 1);
        } else {
          frame = (_controller.value * _calculatedFrameCount).floor() %
              _calculatedFrameCount;
        }

        return Transform.scale(
          scaleX: widget.flip ? -1.0 : 1.0,
          alignment: Alignment.center,
          child: CustomPaint(
            size: Size(
              widget.tileWidth * widget.scale,
              widget.tileHeight * widget.scale,
            ),
            painter: SpriteSheetPainter(
              image: _image!,
              currentFrame: frame,
              tileWidth: widget.tileWidth,
              tileHeight: widget.tileHeight,
              scale: widget.scale,
            ),
          ),
        );
      },
    );
  }
}

class SpriteSheetPainter extends CustomPainter {
  final ui.Image image;
  final int currentFrame;
  final double tileWidth;
  final double tileHeight;
  final double scale;

  SpriteSheetPainter({
    required this.image,
    required this.currentFrame,
    required this.tileWidth,
    required this.tileHeight,
    required this.scale,
  });

  @override
  void paint(Canvas canvas, Size size) {
    // Correct src rect calculation
    // Assuming horizontal strip for the animation row.
    // Ideally we would want to select which 'row' (Y) to animate, but for now assuming row 0.
    // If the image is a single row, srcY is 0.
    final double srcX = currentFrame * tileWidth;
    final double srcY = 0.0; // Default to first row

    final Rect src = Rect.fromLTWH(srcX, srcY, tileWidth, tileHeight);
    final Rect dst = Rect.fromLTWH(0, 0, tileWidth * scale, tileHeight * scale);

    canvas.drawImageRect(
      image,
      src,
      dst,
      Paint()..filterQuality = FilterQuality.none,
    );
  }

  @override
  bool shouldRepaint(covariant SpriteSheetPainter oldDelegate) {
    return oldDelegate.currentFrame != currentFrame ||
        oldDelegate.image != image;
  }
}
