'use strict';
(function(global) {

  global.LabFX = global.LabFX || {};

  /* ── pool ── */
  var POOL_SIZE = 1500;
  var pool = [];

  (function buildPool() {
    for (var i = 0; i < POOL_SIZE; i++) {
      pool.push({
        alive: false,
        ctx:   null,
        x: 0, y: 0,
        vx: 0, vy: 0,
        ax: 0, ay: 0,
        life: 1000,
        age:  0,
        color: '#fff',
        size:  3,
        baseSize: 3,
        shape: 'dot',
        glow:     false,
        fade:     true,
        sizeFade: true,
        /* spark: previous position for motion-blur line */
        px: 0, py: 0
      });
    }
  })();

  /* grab a dead particle from pool; returns null if pool exhausted */
  function acquire() {
    for (var i = 0; i < POOL_SIZE; i++) {
      if (!pool[i].alive) return pool[i];
    }
    return null;
  }

  /* ── helpers ── */
  function pickColor(color) {
    if (Array.isArray(color)) {
      return color[Math.floor(Math.random() * color.length)];
    }
    return color;
  }

  /* ─────────────────────────────────────────────
     PUBLIC API
  ───────────────────────────────────────────── */
  global.LabFX.particles = {

    /**
     * Spawn N particles at canvas coords (x, y).
     */
    emit: function(opts) {
      opts = opts || {};
      var ctx      = opts.ctx;
      var x        = opts.x        || 0;
      var y        = opts.y        || 0;
      var count    = opts.count    != null ? opts.count    : 10;
      var color    = opts.color    != null ? opts.color    : '#fff';
      var speed    = opts.speed    != null ? opts.speed    : 60;
      var spread   = opts.spread   != null ? opts.spread   : Math.PI * 2;
      var angle    = opts.angle    != null ? opts.angle    : 0;
      var gravity  = opts.gravity  != null ? opts.gravity  : 0;
      var life     = opts.life     != null ? opts.life     : 1000;
      var fade     = opts.fade     != null ? opts.fade     : true;
      var glow     = opts.glow     != null ? opts.glow     : false;
      var shape    = opts.shape    != null ? opts.shape    : 'dot';
      var size     = opts.size     != null ? opts.size     : 3;
      var sizeFade = opts.sizeFade != null ? opts.sizeFade : true;

      for (var i = 0; i < count; i++) {
        var p = acquire();
        if (!p) break;

        var dir  = angle + (Math.random() - 0.5) * spread;
        var spd  = speed * (0.5 + Math.random() * 0.5);

        p.alive    = true;
        p.ctx      = ctx;
        p.x        = x;
        p.y        = y;
        p.px       = x;
        p.py       = y;
        p.vx       = Math.cos(dir) * spd;
        p.vy       = Math.sin(dir) * spd;
        p.ax       = 0;
        p.ay       = gravity;
        p.life     = life;
        p.age      = 0;
        p.color    = pickColor(color);
        p.size     = size * (0.7 + Math.random() * 0.6);
        p.baseSize = p.size;
        p.shape    = shape;
        p.glow     = glow;
        p.fade     = fade;
        p.sizeFade = sizeFade;
      }
    },

    /**
     * Advance all alive particles by dt seconds.
     * Call from your sim RAF loop.
     */
    update: function(dt) {
      for (var i = 0; i < POOL_SIZE; i++) {
        var p = pool[i];
        if (!p.alive) continue;

        p.age += dt * 1000; /* convert s → ms */

        if (p.age >= p.life) {
          p.alive = false;
          continue;
        }

        p.px = p.x;
        p.py = p.y;

        p.vx += p.ax * dt;
        p.vy += p.ay * dt;
        p.x  += p.vx * dt;
        p.y  += p.vy * dt;
      }
    },

    /**
     * Draw all live particles that belong to ctx.
     * Call after update, inside your sim's draw/render fn.
     */
    draw: function(ctx) {
      for (var i = 0; i < POOL_SIZE; i++) {
        var p = pool[i];
        if (!p.alive || p.ctx !== ctx) continue;

        var t    = p.age / p.life;         /* 0..1 */
        var alpha = p.fade ? (1 - t) : 1;
        var sz   = p.sizeFade ? p.baseSize * (1 - t * 0.8) : p.baseSize;

        ctx.save();

        if (p.glow) {
          ctx.globalCompositeOperation = 'lighter';
        }

        ctx.globalAlpha = alpha;
        ctx.fillStyle   = p.color;
        ctx.strokeStyle = p.color;

        switch (p.shape) {
          case 'spark': {
            /* line from old to current pos — motion blur */
            var len = Math.max(2, sz * 3);
            var dx  = p.x - p.px;
            var dy  = p.y - p.py;
            var d   = Math.sqrt(dx * dx + dy * dy) || 1;
            var nx  = dx / d * len;
            var ny  = dy / d * len;
            ctx.lineWidth = Math.max(0.5, sz * 0.4);
            ctx.beginPath();
            ctx.moveTo(p.x - nx, p.y - ny);
            ctx.lineTo(p.x, p.y);
            ctx.stroke();
            break;
          }

          case 'ring': {
            var r = p.baseSize * (1 + t * 3);
            ctx.lineWidth = Math.max(0.5, sz * 0.5);
            ctx.beginPath();
            ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
            ctx.stroke();
            break;
          }

          case 'smoke': {
            var smokeAlpha = alpha * 0.25;
            ctx.globalAlpha = smokeAlpha;
            var smokeR = sz * (2 + t * 3);
            var grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, smokeR);
            grad.addColorStop(0, p.color);
            grad.addColorStop(1, 'transparent');
            ctx.fillStyle = grad;
            ctx.beginPath();
            ctx.arc(p.x, p.y, smokeR, 0, Math.PI * 2);
            ctx.fill();
            break;
          }

          case 'dust': {
            ctx.globalAlpha = alpha * 0.6;
            ctx.beginPath();
            ctx.arc(p.x, p.y, Math.max(0.5, sz * 0.5), 0, Math.PI * 2);
            ctx.fill();
            break;
          }

          case 'splash':
            /* same as dot but gravity param makes it droop — handled in update */
            /* fall-through */
          case 'dot':
          default: {
            ctx.beginPath();
            ctx.arc(p.x, p.y, Math.max(0.5, sz), 0, Math.PI * 2);
            ctx.fill();
            break;
          }
        }

        ctx.restore();
      }
    },

    /** Remove all particles */
    clear: function() {
      for (var i = 0; i < POOL_SIZE; i++) {
        pool[i].alive = false;
      }
    }
  };

})(window);
