import 'dart:math';
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/events.dart';
import 'package:flame/collisions.dart';
import 'package:flutter/material.dart';
import '../../utils/colors.dart';

class Bubble extends SpriteComponent with HasGameReference, TapCallbacks, CollisionCallbacks {
  static const double startSize = 40.0; // Small starting size for all bubbles
  static const double maxSize = 50.0; // Same final size for all bubbles
  static const double lifecycleDuration = 12.0; // Same lifecycle duration for all bubbles
  
  // Triple collision rule constants
  static const int maxCollisions = 3;
  static const double collisionGracePeriod = 2.0; // seconds
  
  late String bubbleImagePath;
  late Color bubbleColor;
  bool isPopping = false;
  bool poppedByUser = false;
  bool isAutoSpawned = true; // Whether bubble was auto-spawned or user-triggered
  Function(Bubble, bool)? onPop;
  
  // Collision tracking for triple collision rule
  int _collisionCount = 0;
  double _lastCollisionTime = 0.0;
  
  double _age = 0.0;
  double _lifecycleProgress = 0.0;
  late double _targetSize;
  
  // Physics properties for collision and movement
  Vector2 velocity = Vector2.zero();
  static const double maxSpeed = 50.0;
  static const double friction = 0.98;
  static const double collisionDamping = 0.7;
  double _floatTimer = 0.0;
  
  Bubble({
    required Vector2 position,
    this.onPop,
    this.isAutoSpawned = true,
  }) : super(
    size: Vector2.all(startSize),
    position: position,
    anchor: Anchor.center,
  ) {
    // All bubbles have the same target size regardless of type
    _targetSize = maxSize;
  }

  @override
  Future<void> onLoad() async {
    // Choose random bubble image and set corresponding color
    final bubbleData = _getRandomBubbleData();
    bubbleImagePath = bubbleData['path'];
    bubbleColor = bubbleData['color'];
    
    // Load the sprite
    sprite = await Sprite.load(bubbleImagePath);
    
    await super.onLoad();
    
    // Add circular collision hitbox - will be updated dynamically
    add(CircleHitbox(
      radius: startSize / 2, // Start with initial size
      anchor: Anchor.center,
    ));
    
    // Start with zero scale for smooth entrance animation
    scale = Vector2.zero();
    
    // Add smooth entrance animation
    _addEntranceAnimation();
    
    // Add varied, organic floating animations
    _addRandomFloatingAnimation();
    _addRandomRotationAnimation();
    
    // Initialize random velocity for natural movement
    final random = Random();
    velocity = Vector2(
      (random.nextDouble() - 0.5) * 20, // -10 to +10
      (random.nextDouble() - 0.5) * 20, // -10 to +10
    );
  }

  @override
  void update(double dt) {
    super.update(dt);
    
    if (isPopping) return;
    
    // Update age and lifecycle
    _age += dt;
    _lifecycleProgress = (_age / lifecycleDuration).clamp(0.0, 1.0);
    
    // Update scale based on lifecycle progress - grow from startSize to maxSize
    final currentScale = _lifecycleProgress * (_targetSize / startSize);
    scale = Vector2.all(1.0 + currentScale * 3.0); // Scale from 1.0 to 4.0 over lifetime
    
    // Update collision hitbox radius to match current visual size
    final currentRadius = (startSize / 2) * scale.x; // Use scale.x since it's uniform scaling
    final hitbox = children.whereType<CircleHitbox>().firstOrNull;
    if (hitbox != null) {
      hitbox.radius = currentRadius;
    }
    
    // Update opacity based on lifecycle (fade out towards the end)
    final opacityFactor = 1.0 - (_lifecycleProgress * 0.6); // Fade to 40% opacity
    opacity = opacityFactor.clamp(0.4, 1.0);
    
    // Physics-based movement
    _updatePhysics(dt);
    
    // Keep bubble within screen bounds
    _keepInBounds();
    
    // Update collision tracking
    _updateCollisionTracking(dt);
    
    // Auto-pop when lifecycle is complete
    if (_lifecycleProgress >= 1.0) {
      pop(userTriggered: false);
    }
  }
  
  void _updatePhysics(double dt) {
    // Apply velocity to position
    position.add(velocity * dt);
    
    // Apply friction
    velocity.scale(friction);
    
    // Add periodic floating effects
    _floatTimer += dt;
    if (_floatTimer > 2.0) {
      _floatTimer = 0.0;
      final random = Random();
      final floatForce = Vector2(
        (random.nextDouble() - 0.5) * 8, // -4 to +4 horizontal
        (random.nextDouble() - 0.5) * 8, // -4 to +4 vertical
      );
      velocity.add(floatForce);
    }
    
    // Clamp velocity to max speed
    if (velocity.length > maxSpeed) {
      velocity.normalize();
      velocity.scale(maxSpeed);
    }
  }
  
  void _keepInBounds() {
    const margin = 60.0;
    final gameSize = game.size;
    
    // Bounce off screen edges
    if (position.x < margin) {
      position.x = margin;
      velocity.x = velocity.x.abs(); // Bounce right
    } else if (position.x > gameSize.x - margin) {
      position.x = gameSize.x - margin;
      velocity.x = -velocity.x.abs(); // Bounce left
    }
    
    if (position.y < margin + 100) { // Account for UI at top
      position.y = margin + 100;
      velocity.y = velocity.y.abs(); // Bounce down
    } else if (position.y > gameSize.y - margin) {
      position.y = gameSize.y - margin;
      velocity.y = -velocity.y.abs(); // Bounce up
    }
  }

  Map<String, dynamic> _getRandomBubbleData() {
    final random = Random();
    final bubbleTypes = [
      {
        'path': 'bubble_blue.png',
        'color': ZenColors.defaultLink,
      },
      {
        'path': 'bubble_cyan.png',
        'color': ZenColors.buttonBackground,
      },
      {
        'path': 'bubble_green.png',
        'color': const Color(0xFF00FF80),
      },
      {
        'path': 'bubble_purple.png',
        'color': ZenColors.lightModeHover,
      },
    ];
    return bubbleTypes[random.nextInt(bubbleTypes.length)];
  }

  void _addRandomFloatingAnimation() {
    final random = Random();
    
    // Add a small upward bias to the initial velocity
    velocity.y += -5 - random.nextDouble() * 10; // -5 to -15 upward bias
    
    // The floating effect will now be handled by the physics system
    // and periodic velocity changes in the update method
  }

  void _addEntranceAnimation() {
    // Start at scale 1.0 (startSize) and grow gradually throughout lifecycle
    // The growth will be handled in the update method based on lifecycle progress
    
    // Simple fade-in animation only
    opacity = 0.0;
    add(
      OpacityEffect.to(
        1.0,
        EffectController(
          duration: 0.4,
          curve: Curves.easeOut,
        ),
      ),
    );
  }

  void _addRandomRotationAnimation() {
    final random = Random();
    
    // 30% chance for rotation animation
    if (random.nextDouble() < 0.3) {
      // Small random rotation between -0.2 and 0.2 radians
      final rotationAngle = (random.nextDouble() - 0.5) * 0.4;
      
      // Random rotation duration between 3 and 8 seconds
      final rotationDuration = 3.0 + random.nextDouble() * 5.0;
      
      // Random delay
      final delay = random.nextDouble() * 3.0;
      
      Future.delayed(Duration(milliseconds: (delay * 1000).round()), () {
        if (isMounted) {
          add(
            RotateEffect.by(
              rotationAngle,
              EffectController(
                duration: rotationDuration,
                alternate: true,
                infinite: true,
                curve: Curves.easeInOut,
              ),
            ),
          );
        }
      });
    }
  }


  @override
  bool onTapDown(TapDownEvent event) {
    if (!isPopping) {
      pop(userTriggered: true);
    }
    return true;
  }

  void pop({bool userTriggered = false}) {
    if (isPopping) return;
    
    isPopping = true;
    poppedByUser = userTriggered;
    
    // Create exciting multi-stage pop animation
    
    // Stage 1: Quick expand (0.1s)
    final expandEffect = ScaleEffect.to(
      Vector2.all(1.8),
      EffectController(duration: 0.1, curve: Curves.easeOut),
    );
    
    // Stage 2: Slight compression (0.05s)
    final compressEffect = ScaleEffect.to(
      Vector2.all(0.8),
      EffectController(duration: 0.05, curve: Curves.easeIn),
    );
    
    // Stage 3: Final explosion (0.15s)
    final explodeEffect = ScaleEffect.to(
      Vector2.all(2.5),
      EffectController(duration: 0.15, curve: Curves.easeOut),
    );
    
    // Fade out effect
    final fadeEffect = OpacityEffect.to(
      0.0,
      EffectController(duration: 0.3, curve: Curves.easeIn),
    );
    
    // Rotation effect for more dynamic feel
    final rotateEffect = RotateEffect.by(
      0.5, // Half rotation
      EffectController(duration: 0.3),
    );
    
    // Chain the scale effects
    add(expandEffect);
    expandEffect.onComplete = () {
      if (isMounted) {
        add(compressEffect);
        compressEffect.onComplete = () {
          if (isMounted) {
            add(explodeEffect);
          }
        };
      }
    };
    
    add(fadeEffect);
    add(rotateEffect);
    
    // Create enhanced particle effects
    _createExcitingPopParticles();
    
    // Notify parent and remove bubble
    onPop?.call(this, userTriggered);
    
    Future.delayed(const Duration(milliseconds: 300), () {
      if (isMounted) {
        removeFromParent();
      }
    });
  }

  void _createExcitingPopParticles() {
    final random = Random();
    const particleCount = 4; // Further reduced for better performance
    
    // Use object pooling approach - create fewer, simpler particles
    for (int i = 0; i < particleCount; i++) {
      final angle = (i / particleCount) * 2 * pi;
      
      // Simplified particle creation
      final baseSpeed = 50 + random.nextDouble() * 30; // 50-80 speed
      final particleVelocity = Vector2(
        cos(angle) * baseSpeed,
        sin(angle) * baseSpeed,
      );
      
      // Fixed particle size for performance
      const particleSize = 3.0;
      
      // Simplified particle color
      final particleColor = bubbleColor.withValues(alpha: 0.8);
      
      final particle = CircleComponent(
        radius: particleSize,
        position: position.clone(),
        paint: Paint()
          ..color = particleColor
          ..style = PaintingStyle.fill,
      );
      
      parent?.add(particle);
      
      // Combine effects for better performance
      final combinedEffect = MoveEffect.by(
        particleVelocity + Vector2(0, 40), // Combined movement + gravity
        EffectController(duration: 0.3, curve: Curves.easeOut), // Faster animation
      );
      
      particle.add(combinedEffect);
      
      // Single fade effect instead of multiple effects
      particle.add(
        OpacityEffect.to(
          0.0,
          EffectController(duration: 0.3, curve: Curves.easeIn),
        ),
      );
      
      // Faster cleanup
      Future.delayed(const Duration(milliseconds: 300), () {
        if (particle.isMounted) {
          particle.removeFromParent();
        }
      });
    }
  }

  // Track if shake effect is currently active to prevent overlapping effects
  bool _isShakeEffectActive = false;
  
  /// Add shake effect to this bubble (called when phone is shaken)
  /// Fixed version that only adds visual effects without movement or size flickering
  void addShakeEffect() {
    if (isPopping || _isShakeEffectActive) return;
    
    _isShakeEffectActive = true;
    final random = Random();
    
    // Store current scale to return to it after effect
    final currentScale = scale.clone();
    final targetScale = currentScale * 1.1; // Only 10% bigger than current size
    
    // Use absolute scaling to prevent flickering with lifecycle scaling
    final scaleUpEffect = ScaleEffect.to(
      targetScale,
      EffectController(
        duration: 0.1, // Very short duration
        curve: Curves.easeOut,
      ),
    );
    
    final scaleDownEffect = ScaleEffect.to(
      currentScale,
      EffectController(
        duration: 0.1, // Very short duration
        curve: Curves.easeIn,
      ),
    );
    
    // Add slight rotation effect - reduced intensity
    final rotateEffect = RotateEffect.by(
      (random.nextDouble() - 0.5) * 0.15, // Further reduced rotation
      EffectController(
        duration: 0.15,
        alternate: true,
        curve: Curves.easeInOut,
      ),
    );
    
    // Chain the scale effects
    add(scaleUpEffect);
    scaleUpEffect.onComplete = () {
      if (isMounted && !isPopping) {
        add(scaleDownEffect);
      }
    };
    
    add(rotateEffect);
    
    // Set completion callback to reset shake effect flag
    Future.delayed(const Duration(milliseconds: 200), () {
      _isShakeEffectActive = false;
    });
  }

  // Collision detection methods
  @override
  bool onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
    super.onCollision(intersectionPoints, other);
    
    if (other is Bubble && !isPopping && !other.isPopping) {
      _handleBubbleCollision(other, intersectionPoints);
    }
    return true;
  }
  
  void _handleBubbleCollision(Bubble other, Set<Vector2> intersectionPoints) {
    if (intersectionPoints.isEmpty) return;
    
    // Track collision for triple collision rule
    _incrementCollisionCount();
    other._incrementCollisionCount();
    
    // Calculate collision direction
    final direction = (position - other.position);
    final distance = direction.length;
    
    // Prevent division by zero
    if (distance < 0.1) {
      // If bubbles are too close, separate them with a random direction
      final random = Random();
      direction.setFrom(Vector2(
        (random.nextDouble() - 0.5) * 2,
        (random.nextDouble() - 0.5) * 2,
      ));
    }
    direction.normalize();
    
    // Calculate required separation based on current bubble sizes
    final thisRadius = (startSize / 2) * scale.x;
    final otherRadius = (startSize / 2) * other.scale.x;
    final requiredDistance = thisRadius + otherRadius + 5.0; // 5px padding
    
    // If bubbles are overlapping, separate them immediately
    if (distance < requiredDistance) {
      final separationNeeded = requiredDistance - distance;
      final separationVector = direction * (separationNeeded / 2);
      
      // Move both bubbles away from each other
      position.add(separationVector);
      other.position.sub(separationVector);
      
      // Ensure bubbles stay within bounds after separation
      _keepInBounds();
      other._keepInBounds();
    }
    
    // Apply collision response - bubbles bounce off each other
    final collisionForce = 25.0; // Reduced from 30.0 for gentler bouncing
    velocity.add(direction * collisionForce);
    other.velocity.add(direction * -collisionForce);
    
    // Apply stronger damping to prevent infinite acceleration
    velocity.scale(0.6); // Increased damping from 0.7 to 0.6
    other.velocity.scale(0.6);
  }
  
  /// Update collision tracking and handle grace period reset
  void _updateCollisionTracking(double dt) {
    final currentTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
    
    // Reset collision count if grace period has passed
    if (currentTime - _lastCollisionTime > collisionGracePeriod) {
      _collisionCount = 0;
    }
  }
  
  /// Increment collision count and check for auto-pop
  void _incrementCollisionCount() {
    if (isPopping) return;
    
    _collisionCount++;
    _lastCollisionTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
    
    // Auto-pop after max collisions
    if (_collisionCount >= maxCollisions) {
      pop(userTriggered: false); // Auto-pop due to collision rule
    }
  }
  
  /// Get current collision count (for debugging/testing)
  int get collisionCount => _collisionCount;
  
  /// Apply tilt force to bubble based on device orientation
  void applyTiltForce(double tiltStrength) {
    if (isPopping) return;
    
    // Reduced tilt force to prevent excessive movement during shaking
    // Apply force opposite to tilt direction
    // If device tilts right, bubbles move left and vice versa
    final tiltForce = -tiltStrength * 8.0; // Reduced from 15.0 to 8.0
    velocity.x += tiltForce;
    
    // Add slight vertical component for more natural movement
    velocity.y += tiltForce * 0.2; // Reduced from 0.3 to 0.2
  }
}