import 'dart:math' as math;

import 'package:flutter/material.dart';

class FlipCard extends StatefulWidget {
  final bool isFlipped;
  final bool isMatched;
  final int shakeToken;
  final bool shouldShake;
  final bool disabled;
  final VoidCallback? onPress;
  final Widget front;
  final Widget back;
  final double borderRadius;
  final Duration flipDuration;

  const FlipCard({
    super.key,
    required this.isFlipped,
    required this.isMatched,
    required this.shakeToken,
    required this.shouldShake,
    required this.disabled,
    required this.onPress,
    required this.front,
    required this.back,
    this.borderRadius = 16,
    this.flipDuration = const Duration(milliseconds: 220),
  });

  @override
  State<FlipCard> createState() => _FlipCardState();
}

class _FlipCardState extends State<FlipCard> with TickerProviderStateMixin {
  late final AnimationController _flipController;
  late final AnimationController _popController;
  late final AnimationController _glowController;
  late final AnimationController _shakeController;

  late Animation<double> _flip;
  late Animation<double> _pop;
  late Animation<double> _glow;
  late Animation<double> _shake;

  int _prevShakeToken = 0;
  bool _prevMatched = false;

  @override
  void initState() {
    super.initState();
    _prevMatched = widget.isMatched;
    _prevShakeToken = widget.shakeToken;

    _flipController = AnimationController(
      vsync: this,
      duration: widget.flipDuration,
      value: widget.isFlipped ? 1 : 0,
    );
    _popController = AnimationController(vsync: this, duration: const Duration(milliseconds: 210));
    _glowController = AnimationController(vsync: this, duration: const Duration(milliseconds: 340));
    _shakeController = AnimationController(vsync: this, duration: const Duration(milliseconds: 180));

    _flip = CurvedAnimation(parent: _flipController, curve: Curves.easeInOut);
    _pop = TweenSequence<double>([
      TweenSequenceItem(tween: Tween(begin: 1.0, end: 1.08), weight: 90),
      TweenSequenceItem(tween: Tween(begin: 1.08, end: 1.0), weight: 120),
    ]).animate(_popController);
    _glow = TweenSequence<double>([
      TweenSequenceItem(tween: Tween(begin: 0, end: 1), weight: 120),
      TweenSequenceItem(tween: Tween(begin: 1, end: 0), weight: 220),
    ]).animate(_glowController);
    _shake = TweenSequence<double>([
      TweenSequenceItem(tween: Tween(begin: 0, end: 8), weight: 45),
      TweenSequenceItem(tween: Tween(begin: 8, end: -8), weight: 45),
      TweenSequenceItem(tween: Tween(begin: -8, end: 8), weight: 45),
      TweenSequenceItem(tween: Tween(begin: 8, end: 0), weight: 45),
    ]).animate(_shakeController);
  }

  @override
  void didUpdateWidget(covariant FlipCard oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.isFlipped != oldWidget.isFlipped) {
      if (widget.isFlipped) {
        _flipController.forward();
      } else {
        _flipController.reverse();
      }
    }
    if (!_prevMatched && widget.isMatched) {
      _popController
        ..reset()
        ..forward();
      _glowController
        ..reset()
        ..forward();
    }
    _prevMatched = widget.isMatched;

    if (widget.shakeToken != _prevShakeToken) {
      _prevShakeToken = widget.shakeToken;
      if (widget.shouldShake) {
        _shakeController
          ..reset()
          ..forward();
      }
    }
  }

  @override
  void dispose() {
    _flipController.dispose();
    _popController.dispose();
    _glowController.dispose();
    _shakeController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: widget.disabled ? null : widget.onPress,
      child: AnimatedBuilder(
        animation: Listenable.merge([
          _flipController,
          _popController,
          _glowController,
          _shakeController,
        ]),
        builder: (context, child) {
          final angle = _flip.value * math.pi;
          final showBack = angle > math.pi / 2;
          final scale = _popController.isAnimating ? _pop.value : 1.0;
          final dx = _shakeController.isAnimating ? _shake.value : 0.0;

          return Transform.translate(
            offset: Offset(dx, 0),
            child: Transform.scale(
              scale: scale,
              child: Stack(
                fit: StackFit.expand,
                children: [
                  Transform(
                    alignment: Alignment.center,
                    transform: Matrix4.identity()
                      ..setEntry(3, 2, 0.0012)
                      ..rotateY(angle),
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(widget.borderRadius),
                      child: showBack
                          ? Transform(
                              alignment: Alignment.center,
                              transform: Matrix4.identity()..rotateY(math.pi),
                              child: widget.back,
                            )
                          : widget.front,
                    ),
                  ),
                  IgnorePointer(
                    child: Opacity(
                      opacity: _glow.value,
                      child: Container(
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(widget.borderRadius),
                          border: Border.all(
                            color: const Color.fromRGBO(120, 255, 255, 0.9),
                            width: 3,
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}
