// Particle Behaviors - Flocking, Breathing, Following
import { lerp, distance, clamp } from '../utils/math.js';

// Base behavior class
class Behavior {
  constructor() {
    this.enabled = true;
    this.strength = 1.0;
  }

  apply(particleSystem, params) {
    // Override in subclasses
  }

  onModeChange(newMode, oldMode) {
    // Override in subclasses
  }
}

// Breathing behavior - organic pulsing
export class BreathingBehavior extends Behavior {
  constructor() {
    super();
    this.frequency = 0.25; // Cycles per second (4s period)
    this.amplitude = 0.15; // 15% scale variation
    this.baseScale = 1.0;
  }

  apply(particleSystem, params) {
    if (!this.enabled) return;

    const { time } = params;
    const phase = Math.sin(time * this.frequency * Math.PI * 2 * 0.001);
    const scale = this.baseScale + phase * this.amplitude * this.strength;

    // Apply breathing to all particles - expand/contract from center
    for (let i = 0; i < particleSystem.count; i++) {
      const pos = particleSystem.getPosition(i);
      const dist = Math.sqrt(pos.x * pos.x + pos.y * pos.y);

      if (dist > 0.01) {
        const breathForce = (scale - 1) * 0.001;
        const forceX = (pos.x / dist) * breathForce;
        const forceY = (pos.y / dist) * breathForce;
        particleSystem.applyForce(i, forceX, forceY);
      }
    }
  }

  onModeChange(newMode) {
    if (newMode === 'background') {
      this.strength = 0.3;
    } else {
      this.strength = 1.0;
    }
  }
}

// Flocking behavior - boids algorithm
export class FlockingBehavior extends Behavior {
  constructor() {
    super();
    this.separationRadius = 0.05;
    this.alignmentRadius = 0.1;
    this.cohesionRadius = 0.15;

    this.separationWeight = 0.003;
    this.alignmentWeight = 0.001;
    this.cohesionWeight = 0.001;
  }

  apply(particleSystem, params) {
    if (!this.enabled) return;

    const count = particleSystem.count;

    // Sample subset for performance
    const sampleSize = Math.min(100, count);
    const step = Math.floor(count / sampleSize);

    for (let i = 0; i < count; i += step) {
      const pos = particleSystem.getPosition(i);
      let sepX = 0, sepY = 0, sepCount = 0;
      let alignX = 0, alignY = 0, alignCount = 0;
      let cohX = 0, cohY = 0, cohCount = 0;

      // Check neighbors (sample for performance)
      for (let j = 0; j < count; j += step) {
        if (i === j) continue;

        const other = particleSystem.getPosition(j);
        const dist = distance(pos.x, pos.y, other.x, other.y);

        // Separation
        if (dist < this.separationRadius && dist > 0) {
          const factor = 1 - dist / this.separationRadius;
          sepX += (pos.x - other.x) / dist * factor;
          sepY += (pos.y - other.y) / dist * factor;
          sepCount++;
        }

        // Alignment
        if (dist < this.alignmentRadius) {
          const vel = {
            x: particleSystem.velocities[j * 3],
            y: particleSystem.velocities[j * 3 + 1]
          };
          alignX += vel.x;
          alignY += vel.y;
          alignCount++;
        }

        // Cohesion
        if (dist < this.cohesionRadius) {
          cohX += other.x;
          cohY += other.y;
          cohCount++;
        }
      }

      // Apply forces
      let forceX = 0, forceY = 0;

      if (sepCount > 0) {
        forceX += (sepX / sepCount) * this.separationWeight * this.strength;
        forceY += (sepY / sepCount) * this.separationWeight * this.strength;
      }

      if (alignCount > 0) {
        const avgVelX = alignX / alignCount;
        const avgVelY = alignY / alignCount;
        forceX += avgVelX * this.alignmentWeight * this.strength;
        forceY += avgVelY * this.alignmentWeight * this.strength;
      }

      if (cohCount > 0) {
        const centerX = cohX / cohCount;
        const centerY = cohY / cohCount;
        forceX += (centerX - pos.x) * this.cohesionWeight * this.strength;
        forceY += (centerY - pos.y) * this.cohesionWeight * this.strength;
      }

      particleSystem.applyForce(i, forceX, forceY);
    }
  }

  onModeChange(newMode) {
    if (newMode === 'background') {
      this.strength = 0.2;
    } else if (newMode === 'engaged') {
      this.strength = 0.5;
    } else {
      this.strength = 1.0;
    }
  }
}

// Follow behavior - follow mouse or target
export class FollowBehavior extends Behavior {
  constructor() {
    super();
    this.mouseWeight = 0.0005;
    this.targetWeight = 0.002;
    this.returnWeight = 0.0001;
  }

  apply(particleSystem, params) {
    if (!this.enabled) return;

    const { mousePosition, target, mode } = params;

    for (let i = 0; i < particleSystem.count; i++) {
      const pos = particleSystem.getPosition(i);
      let forceX = 0, forceY = 0;

      // Convert mouse to scene coordinates (-1 to 1)
      const mouseX = (mousePosition.x - 0.5) * 2;
      const mouseY = (mousePosition.y - 0.5) * 2;

      if (target && mode === 'engaged') {
        // Follow target
        const targetX = (target.x - 0.5) * 2;
        const targetY = (target.y - 0.5) * 2;
        forceX += (targetX - pos.x) * this.targetWeight * this.strength;
        forceY += (targetY - pos.y) * this.targetWeight * this.strength;
      } else if (mode !== 'background') {
        // Follow mouse
        forceX += (mouseX - pos.x) * this.mouseWeight * this.strength;
        forceY += (mouseY - pos.y) * this.mouseWeight * this.strength;
      }

      // Return to center force (keep particles from drifting too far)
      const distFromCenter = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
      if (distFromCenter > 0.5) {
        const returnStrength = (distFromCenter - 0.5) * this.returnWeight;
        forceX -= pos.x * returnStrength;
        forceY -= pos.y * returnStrength;
      }

      particleSystem.applyForce(i, forceX, forceY);
    }
  }

  onModeChange(newMode) {
    if (newMode === 'background') {
      this.strength = 0;
    } else if (newMode === 'engaged') {
      this.strength = 2.0;
    } else {
      this.strength = 1.0;
    }
  }
}

export default { BreathingBehavior, FlockingBehavior, FollowBehavior };
