import { Application, Container, Graphics, Ticker } from 'pixi.js';

// ============================================================================
// Constants
// ============================================================================

const COLORS = {
  gold: 0xf0c239,
  goldLight: 0xffd700,
  goldDark: 0xd4a84b,
  red: 0xc21f30,
  redLight: 0xe63946,
};

// Easing functions for smooth animation
const Easing = {
  // Smooth acceleration then deceleration
  easeInOutCubic: (t: number): number => {
    return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
  },
  // Fast start, slow end
  easeOutQuart: (t: number): number => {
    return 1 - Math.pow(1 - t, 4);
  },
  // Slow start, fast end
  easeInQuad: (t: number): number => {
    return t * t;
  },
  // Bounce at end
  easeOutBack: (t: number): number => {
    const c1 = 1.70158;
    const c3 = c1 + 1;
    return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
  },
};

// ============================================================================
// Types
// ============================================================================

interface VoteParticle {
  x: number;
  y: number;
  targetX: number;
  targetY: number;
  startX: number;
  startY: number;
  controlX: number;
  controlY: number;
  progress: number;
  speed: number;
  size: number;
  alpha: number;
  glowSize: number;
  trailPoints: Array<{ x: number; y: number; alpha: number }>;
  phase: 'flying' | 'impact' | 'done';
  impactTime: number;
}

interface ImpactEffect {
  x: number;
  y: number;
  particles: Array<{
    angle: number;
    speed: number;
    distance: number;
    size: number;
    alpha: number;
  }>;
  progress: number;
  phase: 'burst' | 'fade';
}

interface TargetGlow {
  x: number;
  y: number;
  intensity: number;
  pulsePhase: number;
  baseIntensity: number;
}

export interface ProgramTarget {
  id: string;
  x: number;
  y: number;
  name: string;
}

// ============================================================================
// VoteParticleSystem Class
// ============================================================================

export class VoteParticleSystem {
  private app: Application;
  private container: Container;
  private particleLayer: Graphics;
  private glowLayer: Graphics;
  private impactLayer: Graphics;

  private particles: VoteParticle[] = [];
  private impacts: ImpactEffect[] = [];
  private targetGlows: Map<string, TargetGlow> = new Map();
  private programTargets: Map<string, ProgramTarget> = new Map();

  private isDestroyed = false;
  private time = 0;

  // Configuration
  private readonly MAX_PARTICLES = 200;
  private readonly TRAIL_LENGTH = 12;
  private readonly PARTICLE_DURATION = 1500; // ms
  private readonly IMPACT_DURATION = 600; // ms

  constructor() {
    this.app = new Application();
    this.container = new Container();
    this.particleLayer = new Graphics();
    this.glowLayer = new Graphics();
    this.impactLayer = new Graphics();
  }

  async init(canvas: HTMLCanvasElement): Promise<void> {
    await this.app.init({
      canvas,
      width: window.innerWidth,
      height: window.innerHeight,
      backgroundAlpha: 0,
      antialias: true,
      resolution: window.devicePixelRatio || 1,
      autoDensity: true,
    });

    // Layer order: glow (back) -> particles -> impacts (front)
    this.container.addChild(this.glowLayer);
    this.container.addChild(this.particleLayer);
    this.container.addChild(this.impactLayer);
    this.app.stage.addChild(this.container);

    // Start animation loop
    this.app.ticker.add(this.update.bind(this));

    // Handle resize
    window.addEventListener('resize', this.handleResize.bind(this));
  }

  // ============================================================================
  // Public API
  // ============================================================================

  /**
   * Register a program target position for vote particles to fly towards
   */
  registerTarget(target: ProgramTarget): void {
    this.programTargets.set(target.id, target);

    // Initialize glow state
    if (!this.targetGlows.has(target.id)) {
      this.targetGlows.set(target.id, {
        x: target.x,
        y: target.y,
        intensity: 0,
        pulsePhase: Math.random() * Math.PI * 2,
        baseIntensity: 0,
      });
    }
  }

  /**
   * Update target position (for responsive layouts)
   */
  updateTargetPosition(id: string, x: number, y: number): void {
    const target = this.programTargets.get(id);
    if (target) {
      target.x = x;
      target.y = y;
    }

    const glow = this.targetGlows.get(id);
    if (glow) {
      glow.x = x;
      glow.y = y;
    }
  }

  /**
   * Spawn a vote particle flying towards a program target
   */
  spawnVoteParticle(targetId: string): void {
    if (this.isDestroyed) return;
    if (this.particles.length >= this.MAX_PARTICLES) return;

    const target = this.programTargets.get(targetId);
    if (!target) {
      console.warn(`[VoteParticle] Unknown target: ${targetId}`);
      return;
    }

    // Spawn from bottom edge with random horizontal position
    const screenWidth = this.app.screen.width;
    const screenHeight = this.app.screen.height;

    const startX = Math.random() * screenWidth;
    const startY = screenHeight + 20;

    // Calculate bezier control point for curved trajectory
    // Control point is offset to create an arc
    const midX = (startX + target.x) / 2;
    const midY = (startY + target.y) / 2;
    const perpX = -(target.y - startY);
    const perpY = target.x - startX;
    const perpLen = Math.sqrt(perpX * perpX + perpY * perpY);
    const curveAmount = (Math.random() - 0.5) * 300; // Random curve direction

    const controlX = midX + (perpX / perpLen) * curveAmount;
    const controlY = midY + (perpY / perpLen) * curveAmount;

    const particle: VoteParticle = {
      x: startX,
      y: startY,
      targetX: target.x,
      targetY: target.y,
      startX,
      startY,
      controlX,
      controlY,
      progress: 0,
      speed: 0.8 + Math.random() * 0.4, // Vary speed slightly
      size: 4 + Math.random() * 3,
      alpha: 1,
      glowSize: 15 + Math.random() * 10,
      trailPoints: [],
      phase: 'flying',
      impactTime: 0,
    };

    this.particles.push(particle);
  }

  /**
   * Spawn multiple particles at once (for burst effect)
   */
  spawnVoteParticleBurst(targetId: string, count: number = 3): void {
    for (let i = 0; i < count; i++) {
      setTimeout(() => {
        this.spawnVoteParticle(targetId);
      }, i * 50); // Stagger spawns
    }
  }

  /**
   * Increase heat/glow intensity for a target
   */
  addHeat(targetId: string, amount: number = 0.3): void {
    const glow = this.targetGlows.get(targetId);
    if (glow) {
      glow.baseIntensity = Math.min(1, glow.baseIntensity + amount);
      glow.intensity = Math.min(1.5, glow.intensity + amount * 2); // Pulse effect
    }
  }

  // ============================================================================
  // Animation Loop
  // ============================================================================

  private update(ticker: Ticker): void {
    if (this.isDestroyed) return;

    this.time += ticker.deltaMS;

    // Clear all layers
    this.particleLayer.clear();
    this.glowLayer.clear();
    this.impactLayer.clear();

    // Update and draw target glows
    this.updateTargetGlows(ticker.deltaMS);

    // Update and draw particles
    this.updateParticles(ticker.deltaMS);

    // Update and draw impact effects
    this.updateImpacts(ticker.deltaMS);
  }

  private updateTargetGlows(deltaMs: number): void {
    this.targetGlows.forEach((glow, id) => {
      // Decay intensity over time
      glow.intensity = Math.max(glow.baseIntensity, glow.intensity - deltaMs * 0.002);
      glow.baseIntensity = Math.max(0, glow.baseIntensity - deltaMs * 0.0002);

      // Pulse animation
      glow.pulsePhase += deltaMs * 0.003;
      const pulse = Math.sin(glow.pulsePhase) * 0.1 + 1;

      if (glow.intensity > 0.01) {
        this.drawTargetGlow(glow, pulse);
      }
    });
  }

  private drawTargetGlow(glow: TargetGlow, pulse: number): void {
    const intensity = glow.intensity * pulse;
    const maxRadius = 60 + intensity * 40;

    // Draw multiple concentric circles for glow effect
    for (let i = 5; i >= 0; i--) {
      const ratio = i / 5;
      const radius = maxRadius * (0.3 + ratio * 0.7);
      const alpha = intensity * (1 - ratio) * 0.4;

      this.glowLayer.circle(glow.x, glow.y, radius);
      this.glowLayer.fill({ color: COLORS.gold, alpha });
    }

    // Inner bright core
    this.glowLayer.circle(glow.x, glow.y, 8 + intensity * 5);
    this.glowLayer.fill({ color: COLORS.goldLight, alpha: intensity * 0.8 });
  }

  private updateParticles(deltaMs: number): void {
    this.particles = this.particles.filter((p) => {
      if (p.phase === 'flying') {
        // Update progress along bezier curve
        p.progress += (deltaMs / this.PARTICLE_DURATION) * p.speed;

        if (p.progress >= 1) {
          // Reached target - trigger impact
          p.phase = 'impact';
          p.impactTime = 0;
          this.triggerImpact(p.targetX, p.targetY);

          // Find target and add heat
          for (const [id, target] of this.programTargets) {
            if (target.x === p.targetX && target.y === p.targetY) {
              this.addHeat(id);
              break;
            }
          }
          return false;
        }

        // Calculate position on quadratic bezier curve with easing
        const t = Easing.easeInOutCubic(p.progress);
        const invT = 1 - t;

        p.x = invT * invT * p.startX + 2 * invT * t * p.controlX + t * t * p.targetX;
        p.y = invT * invT * p.startY + 2 * invT * t * p.controlY + t * t * p.targetY;

        // Update trail
        p.trailPoints.unshift({ x: p.x, y: p.y, alpha: 1 });
        if (p.trailPoints.length > this.TRAIL_LENGTH) {
          p.trailPoints.pop();
        }

        // Fade trail points
        p.trailPoints.forEach((point, i) => {
          point.alpha = 1 - i / this.TRAIL_LENGTH;
        });

        // Draw particle
        this.drawParticle(p);
        return true;
      }

      return false;
    });
  }

  private drawParticle(p: VoteParticle): void {
    // Draw trail
    p.trailPoints.forEach((point, i) => {
      const size = p.size * (1 - i / this.TRAIL_LENGTH * 0.7);
      const alpha = point.alpha * 0.6;

      this.particleLayer.circle(point.x, point.y, size);
      this.particleLayer.fill({ color: COLORS.gold, alpha });
    });

    // Draw glow around particle
    for (let i = 3; i >= 0; i--) {
      const ratio = i / 3;
      const radius = p.glowSize * (0.3 + ratio * 0.7);
      const alpha = (1 - ratio) * 0.3 * p.alpha;

      this.particleLayer.circle(p.x, p.y, radius);
      this.particleLayer.fill({ color: COLORS.gold, alpha });
    }

    // Draw bright core
    this.particleLayer.circle(p.x, p.y, p.size);
    this.particleLayer.fill({ color: COLORS.goldLight, alpha: p.alpha });

    // Inner white hot center
    this.particleLayer.circle(p.x, p.y, p.size * 0.5);
    this.particleLayer.fill({ color: 0xffffff, alpha: p.alpha * 0.8 });
  }

  private triggerImpact(x: number, y: number): void {
    const particleCount = 12 + Math.floor(Math.random() * 8);
    const impactParticles: ImpactEffect['particles'] = [];

    for (let i = 0; i < particleCount; i++) {
      const angle = (Math.PI * 2 * i) / particleCount + Math.random() * 0.3;
      impactParticles.push({
        angle,
        speed: 2 + Math.random() * 3,
        distance: 0,
        size: 2 + Math.random() * 3,
        alpha: 1,
      });
    }

    this.impacts.push({
      x,
      y,
      particles: impactParticles,
      progress: 0,
      phase: 'burst',
    });
  }

  private updateImpacts(deltaMs: number): void {
    this.impacts = this.impacts.filter((impact) => {
      impact.progress += deltaMs / this.IMPACT_DURATION;

      if (impact.progress >= 1) {
        return false;
      }

      // Update impact particles
      const t = Easing.easeOutQuart(impact.progress);

      impact.particles.forEach((p) => {
        p.distance = t * 50 * p.speed;
        p.alpha = 1 - Easing.easeInQuad(impact.progress);
        p.size = p.size * (1 + t * 0.5);
      });

      // Draw impact
      this.drawImpact(impact);
      return true;
    });
  }

  private drawImpact(impact: ImpactEffect): void {
    // Draw central flash
    const flashAlpha = (1 - impact.progress) * 0.8;
    const flashSize = 20 + impact.progress * 30;

    for (let i = 3; i >= 0; i--) {
      const ratio = i / 3;
      const radius = flashSize * (0.3 + ratio * 0.7);
      const alpha = flashAlpha * (1 - ratio);

      this.impactLayer.circle(impact.x, impact.y, radius);
      this.impactLayer.fill({ color: COLORS.goldLight, alpha });
    }

    // Draw burst particles
    impact.particles.forEach((p) => {
      const px = impact.x + Math.cos(p.angle) * p.distance;
      const py = impact.y + Math.sin(p.angle) * p.distance;

      // Particle glow
      this.impactLayer.circle(px, py, p.size * 2);
      this.impactLayer.fill({ color: COLORS.gold, alpha: p.alpha * 0.3 });

      // Particle core
      this.impactLayer.circle(px, py, p.size);
      this.impactLayer.fill({ color: COLORS.goldLight, alpha: p.alpha });
    });
  }

  // ============================================================================
  // Lifecycle
  // ============================================================================

  private handleResize(): void {
    if (this.isDestroyed) return;
    this.app.renderer.resize(window.innerWidth, window.innerHeight);
  }

  destroy(): void {
    this.isDestroyed = true;
    window.removeEventListener('resize', this.handleResize.bind(this));
    this.particles = [];
    this.impacts = [];
    this.targetGlows.clear();
    this.programTargets.clear();
    this.app.destroy(true, { children: true });
  }
}
