'use strict';

/* ═══════════════════════════════════════════════════════════════════
   AngryBirdsSim — Angry Birds Physics
   Real projectile physics: RK4, drag, wind, gravity by planet.
   Blocks: AABB with impulse collisions and destruction.
   Pigs: circle targets with HP system.
   6 levels with increasing difficulty.
   ═══════════════════════════════════════════════════════════════════ */

const AB_PLANETS = {
  earth:   { g: 9.81,  label: 'Земля <svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>',  sky1: '#0f1923', sky2: '#1a3a2a', ground: '#3d6b47', g_label: '9.81' },
  moon:    { g: 1.62,  label: 'Луна <svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="currentColor" stroke="none"/></svg>',   sky1: '#000008', sky2: '#0a0a18', ground: '#7a7a66', g_label: '1.62' },
  mars:    { g: 3.71,  label: 'Марс <svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="currentColor" stroke="none"/></svg>',   sky1: '#2a0800', sky2: '#5c1e00', ground: '#7a3010', g_label: '3.71' },
  jupiter: { g: 24.79, label: 'Юпитер <svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="currentColor" stroke="none"/></svg>', sky1: '#1a0a00', sky2: '#3d1e00', ground: '#5c3220', g_label: '24.79' },
};

const AB_MATS = {
  wood:  { color: '#b5651d', border: '#7a3f0a', hpMax: 120, mass: 2.0, debris: '#8B4513' },
  stone: { color: '#7a7a7a', border: '#444',    hpMax: 320, mass: 6.0, debris: '#555' },
  glass: { color: '#a8d8ea', border: '#5badd4', hpMax: 45,  mass: 0.8, debris: '#d4eef8' },
};

const AB_BIRDS = {
  normal: { color: '#e63946', r: 18, mass: 1.0, Cd: 0.28, label: 'Красная' },
  heavy:  { color: '#888',    r: 23, mass: 4.2, Cd: 0.45, label: 'Тяжёлая' },
  fast:   { color: '#ffd166', r: 13, mass: 0.55, Cd: 0.08, label: 'Жёлтая' },
};

const _PX_M = 42; // pixels per metre (base scale, adjusted in _layout)

/* ── Level definitions ── */
/* rx = metres right of slingshot, gy = metres above ground (bottom of block) */
function _buildLevels() {
  return [
    /* 1 — Tutorial: one wooden column, one pig */
    { planet: 'earth', wind: 0, birdType: 'normal', birds: 3,
      blocks: [
        { mat: 'wood', rx: 7.2, gy: 0,    w: 0.55, h: 1.9 },
        { mat: 'wood', rx: 6.8, gy: 1.9,  w: 1.2,  h: 0.38 },
      ],
      pigs: [{ rx: 7.2, gy: 2.35 }] },

    /* 2 — Glass tower, 2 pigs */
    { planet: 'earth', wind: 0, birdType: 'normal', birds: 4,
      blocks: [
        { mat: 'glass', rx: 6.6, gy: 0,    w: 0.45, h: 1.6 },
        { mat: 'glass', rx: 8.2, gy: 0,    w: 0.45, h: 1.6 },
        { mat: 'glass', rx: 6.3, gy: 1.6,  w: 2.5,  h: 0.4 },
        { mat: 'glass', rx: 7.3, gy: 2.0,  w: 0.55, h: 1.3 },
      ],
      pigs: [{ rx: 6.8, gy: 2.05 }, { rx: 7.3, gy: 3.4 }] },

    /* 3 — Wind, wooden house */
    { planet: 'earth', wind: 5, birdType: 'normal', birds: 4,
      blocks: [
        { mat: 'wood', rx: 6.5, gy: 0,    w: 0.5, h: 2.1 },
        { mat: 'wood', rx: 9.1, gy: 0,    w: 0.5, h: 2.1 },
        { mat: 'wood', rx: 6.2, gy: 2.1,  w: 3.4, h: 0.45 },
        { mat: 'wood', rx: 7.1, gy: 2.55, w: 0.5, h: 1.6 },
        { mat: 'wood', rx: 8.5, gy: 2.55, w: 0.5, h: 1.6 },
        { mat: 'wood', rx: 6.8, gy: 4.15, w: 2.5, h: 0.4 },
      ],
      pigs: [{ rx: 7.8, gy: 2.6 }, { rx: 7.8, gy: 4.6 }] },

    /* 4 — Moon, stone fortress, heavy bird */
    { planet: 'moon', wind: 0, birdType: 'heavy', birds: 3,
      blocks: [
        { mat: 'stone', rx: 7.0, gy: 0,    w: 0.6, h: 2.6 },
        { mat: 'stone', rx: 9.6, gy: 0,    w: 0.6, h: 2.6 },
        { mat: 'stone', rx: 6.7, gy: 2.6,  w: 3.5, h: 0.6 },
        { mat: 'stone', rx: 8.2, gy: 3.2,  w: 0.6, h: 1.6 },
      ],
      pigs: [{ rx: 7.5, gy: 3.2 }, { rx: 8.5, gy: 4.8 }] },

    /* 5 — Mars, headwind, mixed materials, fast bird */
    { planet: 'mars', wind: -4, birdType: 'fast', birds: 4,
      blocks: [
        { mat: 'stone', rx: 6.5, gy: 0,    w: 0.5, h: 1.3 },
        { mat: 'wood',  rx: 6.5, gy: 1.3,  w: 0.5, h: 1.6 },
        { mat: 'stone', rx: 8.2, gy: 0,    w: 0.5, h: 1.3 },
        { mat: 'wood',  rx: 8.2, gy: 1.3,  w: 0.5, h: 1.6 },
        { mat: 'stone', rx: 6.2, gy: 2.9,  w: 2.9, h: 0.5 },
        { mat: 'wood',  rx: 7.4, gy: 3.4,  w: 0.7, h: 1.1 },
      ],
      pigs: [{ rx: 6.8, gy: 3.4 }, { rx: 8.8, gy: 3.4 }] },

    /* 6 — Jupiter, strong wind, multi-level, 3 pigs */
    { planet: 'jupiter', wind: 7, birdType: 'normal', birds: 5,
      blocks: [
        { mat: 'stone', rx: 6.2, gy: 0,    w: 0.5, h: 1.6 },
        { mat: 'wood',  rx: 7.7, gy: 0,    w: 0.5, h: 1.6 },
        { mat: 'stone', rx: 9.2, gy: 0,    w: 0.5, h: 1.6 },
        { mat: 'stone', rx: 5.9, gy: 1.6,  w: 1.2, h: 0.5 },
        { mat: 'wood',  rx: 7.4, gy: 1.6,  w: 2.3, h: 0.5 },
        { mat: 'stone', rx: 8.9, gy: 1.6,  w: 1.2, h: 0.5 },
        { mat: 'stone', rx: 6.7, gy: 2.1,  w: 0.5, h: 1.6 },
        { mat: 'stone', rx: 8.7, gy: 2.1,  w: 0.5, h: 1.6 },
        { mat: 'wood',  rx: 6.4, gy: 3.7,  w: 3.1, h: 0.45 },
      ],
      pigs: [{ rx: 6.4, gy: 2.15 }, { rx: 7.7, gy: 2.15 }, { rx: 8.9, gy: 2.15 }] },
  ];
}

/* ══════════════════════════════════════════════════════════════════ */
class AngryBirdsSim {
  constructor(canvas) {
    this.canvas = canvas;
    this.ctx    = canvas.getContext('2d');
    this.W = 0; this.H = 0;

    this.levelIdx = 0;
    this.state    = 'aim'; // 'aim' | 'flying' | 'settling' | 'win' | 'lose'
    this.bird     = null;
    this.birdsLeft = [];
    this.blocks   = [];
    this.pigs     = [];
    this.score    = 0;

    this._particles  = [];
    this._raf        = null;
    this._lastTs     = null;
    this._settleTimer = 0;
    this._previewPath = [];
    this._drag       = { pulling: false, mx: 0, my: 0 }; // mx/my updated in _layout()

    /* layout (computed in _layout) */
    this._gY  = 0;  // ground Y (canvas px)
    this._sX  = 0;  // sling X
    this._sY  = 0;  // sling Y (bird rest position)
    this._sc  = _PX_M; // px per metre

    this._levels = _buildLevels();
    this._stars  = Array.from({ length: 70 }, () => ({
      x: Math.random(), y: Math.random() * 0.7, r: 0.5 + Math.random() * 1.5, a: 0.3 + Math.random() * 0.7,
    }));

    this.onUpdate = null;
    this._ready   = false; // true after first _initLevel

    new ResizeObserver(() => { this.fit(); if (!this._raf) this.draw(); })
      .observe(canvas.parentElement || canvas);
  }

  /* ── PUBLIC API ─────────────────────────────────────────────── */

  fit() {
    const dpr = window.devicePixelRatio || 1;
    const el  = this.canvas.parentElement || this.canvas;
    const W   = el.clientWidth  || 800;
    const H   = el.clientHeight || 480;
    this.canvas.width  = W * dpr;
    this.canvas.height = H * dpr;
    this.canvas.style.width  = W + 'px';
    this.canvas.style.height = H + 'px';
    this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    this.W = W; this.H = H;
    this._layout();
    /* Only init on first open — resize must NOT reset the game */
    if (!this._ready) { this._ready = true; this._initLevel(); }
  }

  loadLevel(n) {
    this.levelIdx = Math.max(0, Math.min(n, this._levels.length - 1));
    this._ready = true; // ensure _initLevel runs even before first fit
    this._initLevel();
  }

  restart() { this._ready = true; this._initLevel(); }

  start() {
    if (this._raf) return;
    this._lastTs = null;
    const tick = (ts) => {
      const dt = this._lastTs ? Math.min((ts - this._lastTs) / 1000, 0.05) : 0.016;
      this._lastTs = ts;
      if (window.LabFX) LabFX.particles.update(dt);
      this._update(dt);
      this.draw();
      this._raf = requestAnimationFrame(tick);
    };
    this._raf = requestAnimationFrame(tick);
  }

  stop() {
    if (this._raf) { cancelAnimationFrame(this._raf); this._raf = null; }
  }

  draw() {
    if (!this.W) return;
    this._drawBackground();
    this._drawSlingshot();
    this._drawBlocks();
    this._drawPigs();
    if (this.bird) this._drawBird(this.bird);
    this._drawParticles();
    if (this.state === 'aim' && this._drag.pulling) this._drawPreview();
    this._drawSlingshotRubber();
    this._drawQueue();
    this._drawHUD();
    if (this.state === 'win' || this.state === 'lose') this._drawOverlay();
    /* LabFX: particles overlay */
    if (window.LabFX) LabFX.particles.draw(this.ctx);
  }

  info() {
    const lvl = this._levels[this.levelIdx];
    const planet = lvl ? AB_PLANETS[lvl.planet] : AB_PLANETS.earth;
    return {
      level:  this.levelIdx + 1,
      birds:  this.birdsLeft.length + (this.bird ? 1 : 0),
      pigs:   this.pigs.filter(p => !p.destroyed).length,
      score:  this.score,
      planet: planet ? planet.label : '—',
      g:      planet ? planet.g_label : '—',
    };
  }

  /* ── MOUSE HANDLERS ─────────────────────────────────────────── */

  handleMouseDown(e) {
    if (this.state !== 'aim' || !this.bird) return;
    const { x, y } = this._evt(e);
    if (Math.hypot(x - this._sX, y - this._sY) < 54) {
      this._drag.pulling = true;
      this._clampDrag(x, y);
      this._updatePreview();
    }
  }

  handleMouseMove(e) {
    if (!this._drag.pulling) return;
    const { x, y } = this._evt(e);
    this._clampDrag(x, y);
    this._updatePreview();
    this.draw();
  }

  handleMouseUp(e) {
    if (!this._drag.pulling) return;
    this._drag.pulling = false;
    const dx = this._drag.mx - this._sX;
    const dy = this._drag.my - this._sY;
    if (Math.hypot(dx, dy) < 12) { this.draw(); return; }
    const K = 5.8; // px stretch <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> px/s velocity
    this._launch(-dx * K, -dy * K);
  }

  /* ── PRIVATE ────────────────────────────────────────────────── */

  _layout() {
    this._gY  = Math.round(this.H * 0.80);
    this._sX  = Math.round(this.W * 0.17);
    this._sY  = this._gY - 68;
    this._sc  = Math.max(34, Math.min(50, this.H / 11));
    /* keep drag position at sling so rubber band doesn't snap to (0,0) */
    if (!this._drag.pulling) { this._drag.mx = this._sX; this._drag.my = this._sY; }
  }

  _initLevel() {
    if (!this.W) return;
    const lvl = this._levels[this.levelIdx];
    if (!lvl) return;

    this.state        = 'aim';
    this.score        = 0;
    this._particles   = [];
    this._previewPath = [];
    this._settleTimer = 0;
    this._drag.pulling = false;

    this.birdsLeft = Array(lvl.birds - 1).fill(lvl.birdType);
    this.bird      = this._spawnBird(lvl.birdType);
    this._placeBirdAtSling(this.bird);

    const sc = this._sc;
    this.blocks = lvl.blocks.map((b, i) => {
      const mat = AB_MATS[b.mat] || AB_MATS.wood;
      return {
        id: i, mat: b.mat,
        x: this._sX + b.rx * sc,
        y: this._gY - b.gy * sc - b.h * sc,
        w: b.w * sc, h: b.h * sc,
        vx: 0, vy: 0, angle: 0, angVel: 0,
        hp: mat.hpMax, maxHp: mat.hpMax,
        mass: mat.mass * b.w * b.h,
        destroyed: false, onGround: true, // start stationary; set false on impulse
      };
    });

    this.pigs = lvl.pigs.map((p, i) => ({
      id: i,
      x: this._sX + p.rx * sc,
      y: this._gY - p.gy * sc - 20,
      vx: 0, vy: 0,
      r: 20, hp: 100, maxHp: 100,
      destroyed: false, flash: 0, onGround: true, // start stationary
    }));

    if (this.onUpdate) this.onUpdate(this.info());
  }

  _spawnBird(type) {
    const def = AB_BIRDS[type] || AB_BIRDS.normal;
    return {
      type, color: def.color,
      x: 0, y: 0, vx: 0, vy: 0,
      r: def.r, mass: def.mass, Cd: def.Cd,
      trail: [], launched: false, destroyed: false,
    };
  }

  _placeBirdAtSling(bird) {
    bird.x = this._sX; bird.y = this._sY;
    bird.vx = 0; bird.vy = 0;
    bird.launched = false; bird.trail = [];
  }

  _launch(vx, vy) {
    if (!this.bird) return;
    this.bird.vx = vx; this.bird.vy = vy;
    this.bird.launched = true;
    this.state = 'flying';
    this._emit('launch', this.bird.x, this.bird.y, this.bird.color, 8);
    /* LabFX: launch sound */
    if (window.LabFX) LabFX.sound.play('whoosh', { pitch: 0.8 });
    if (this.onUpdate) this.onUpdate(this.info());
  }

  _clampDrag(mx, my) {
    let dx = mx - this._sX;
    let dy = my - this._sY;
    if (dx > 5) dx = 5; // mostly left only
    const dist = Math.hypot(dx, dy);
    const maxPull = 80;
    if (dist > maxPull) { dx = dx / dist * maxPull; dy = dy / dist * maxPull; }
    this._drag.mx = this._sX + dx;
    this._drag.my = this._sY + dy;
  }

  /* ── UPDATE LOOP ─────────────────────────────────────────────── */

  _update(dt) {
    if (this.state === 'flying') {
      this._stepBird(dt);
      this._collisions();
      if (this.bird && (this.bird.destroyed || this._offScreen(this.bird))) {
        this.bird = null;
        this.state = 'settling';
        this._settleTimer = 1.5;
      }
    }

    if (this.state === 'flying' || this.state === 'settling') {
      this._stepBlocks(dt);
      this._stepPigs(dt);
    }

    if (this.state === 'settling') {
      this._settleTimer -= dt;
      if (this._settleTimer <= 0) {
        if (this.pigs.every(p => p.destroyed)) {
          this._win();
        } else if (!this.birdsLeft.length) {
          this.state = 'lose';
          if (this.onUpdate) this.onUpdate(this.info());
        } else {
          this.bird = this._spawnBird(this.birdsLeft.shift());
          this._placeBirdAtSling(this.bird);
          this.state = 'aim';
          if (this.onUpdate) this.onUpdate(this.info());
        }
      }
    }

    this._stepParticles(dt);
  }

  _planet() {
    const lvl = this._levels[this.levelIdx];
    return AB_PLANETS[lvl?.planet] || AB_PLANETS.earth;
  }

  _stepBird(dt) {
    const bird = this.bird;
    if (!bird?.launched) return;
    const g   = this._planet().g * this._sc;          // px/s²
    const lvl = this._levels[this.levelIdx];
    const wind = (lvl.wind || 0) * this._sc * 0.22;   // px/s² wind accel

    /* Quadratic drag in px-space (empirical, looks right) */
    const spd = Math.hypot(bird.vx, bird.vy);
    const kD  = 2.8e-5 * bird.Cd;
    const ax  = (wind - kD * spd * bird.vx) / bird.mass;
    const ay  = (g    - kD * spd * bird.vy) / bird.mass;

    /* Simple Euler (fast enough for game) */
    bird.vx += ax * dt;
    bird.vy += ay * dt;
    bird.x  += bird.vx * dt;
    bird.y  += bird.vy * dt;

    /* Trail */
    bird.trail.push({ x: bird.x, y: bird.y });
    if (bird.trail.length > 22) bird.trail.shift();

    /* Ground bounce / stop */
    if (bird.y + bird.r >= this._gY) {
      bird.y  = this._gY - bird.r;
      bird.vy *= -0.32;
      bird.vx *= 0.72;
      this._emit('impact', bird.x, this._gY, '#a0d080', 5);
      if (Math.abs(bird.vy) < 22) bird.destroyed = true;
    }
  }

  _stepBlocks(dt) {
    const g = this._planet().g * this._sc;
    for (const b of this.blocks) {
      if (b.destroyed) continue;
      if (!b.onGround) {
        b.vy += g * dt;
        b.x  += b.vx * dt; b.y  += b.vy * dt;
        b.angle += b.angVel * dt;
        b.vx *= 0.992;
        if (b.y + b.h >= this._gY) {
          b.y = this._gY - b.h;
          b.vy *= -0.22; b.vx *= 0.65; b.angVel *= 0.45;
          if (Math.abs(b.vy) < 18) { b.vy = 0; b.onGround = true; }
        }
      } else {
        // sleeping on ground: slide + check if kicked into air
        if (Math.abs(b.vx) > 0.5 || Math.abs(b.vy) > 0.5) b.onGround = false;
        b.vx *= 0.82; b.x += b.vx * dt;
      }
    }
  }

  _stepPigs(dt) {
    const g = this._planet().g * this._sc;
    for (const p of this.pigs) {
      if (p.destroyed) continue;
      if (p.flash > 0) p.flash -= dt;
      if (p.onGround) {
        // wake up if kicked
        if (Math.abs(p.vx) > 0.5 || Math.abs(p.vy) > 0.5) p.onGround = false;
        p.vx *= 0.82; p.x += p.vx * dt;
        continue;
      }
      p.vy += g * dt;
      p.x  += p.vx * dt; p.y  += p.vy * dt;
      if (p.y + p.r >= this._gY) {
        p.y = this._gY - p.r; p.vy *= -0.18; p.vx *= 0.65;
        if (Math.abs(p.vy) < 10) { p.vy = 0; p.onGround = true; }
      }
    }
  }

  _collisions() {
    const bird = this.bird;
    if (!bird?.launched) return;

    /* Bird vs Blocks */
    for (const b of this.blocks) {
      if (b.destroyed) continue;
      const cx = Math.max(b.x, Math.min(bird.x, b.x + b.w));
      const cy = Math.max(b.y, Math.min(bird.y, b.y + b.h));
      const dx = bird.x - cx, dy = bird.y - cy;
      const dist = Math.hypot(dx, dy);
      if (dist >= bird.r) continue;

      const nx = dist > 0.5 ? dx / dist : 0;
      const ny = dist > 0.5 ? dy / dist : -1;
      const vRel = (bird.vx - b.vx) * nx + (bird.vy - b.vy) * ny;
      if (vRel >= 0) continue;

      const e = 0.28;
      const j = -(1 + e) * vRel / (1 / bird.mass + 1 / b.mass);
      bird.vx += j / bird.mass * nx;
      bird.vy += j / bird.mass * ny;
      b.vx    -= j / b.mass * nx;
      b.vy    -= j / b.mass * ny;
      b.angVel += (dx * (-j / b.mass * ny) - dy * (-j / b.mass * nx)) * 0.015;
      b.onGround = false; // wake up block — now subject to gravity

      const dmg = Math.abs(j) * 0.18;
      b.hp -= dmg;
      this.score += Math.max(0, Math.floor(dmg * 3));
      this._emit('hit', cx, cy, AB_MATS[b.mat]?.debris || '#888', 5);
      /* LabFX: bird-block collision */
      if (window.LabFX) {
        LabFX.sound.play('bounce');
        LabFX.particles.emit({
          ctx: this.ctx, x: cx, y: cy,
          count: 15, color: '#FFD166', speed: 80,
          spread: Math.PI * 2, life: 400, shape: 'spark',
        });
      }
      if (b.hp <= 0) {
        b.destroyed = true;
        this.score += 500;
        this._emit('destroy', b.x + b.w / 2, b.y + b.h / 2, AB_MATS[b.mat]?.debris || '#888', 15);
        /* LabFX: block destroyed */
        if (window.LabFX) {
          LabFX.sound.play('fizz');
          LabFX.particles.emit({
            ctx: this.ctx, x: b.x + b.w / 2, y: b.y + b.h / 2,
            count: 25, color: AB_MATS[b.mat]?.debris || '#888',
            speed: 100, spread: Math.PI * 2, life: 700, shape: 'splash',
          });
        }
      }
      bird.x += nx * (bird.r - dist + 1);
      bird.y += ny * (bird.r - dist + 1);
    }

    /* Bird vs Pigs */
    for (const p of this.pigs) {
      if (p.destroyed) continue;
      const dx = bird.x - p.x, dy = bird.y - p.y;
      const dist = Math.hypot(dx, dy);
      const minD = bird.r + p.r;
      if (dist >= minD) continue;

      const nx = dist > 0.5 ? dx / dist : 0;
      const ny = dist > 0.5 ? dy / dist : -1;
      const pigMass = 2.2;
      const vRel = (bird.vx - p.vx) * nx + (bird.vy - p.vy) * ny;
      if (vRel >= 0) continue;

      const e = 0.15;
      const j = -(1 + e) * vRel / (1 / bird.mass + 1 / pigMass);
      bird.vx += j / bird.mass * nx;
      bird.vy += j / bird.mass * ny;
      p.vx -= j / pigMass * nx;
      p.vy -= j / pigMass * ny;
      p.onGround = false; // wake up pig

      const dmg = Math.abs(j) * 0.28;
      p.hp -= dmg; p.flash = 0.35;
      if (p.hp <= 0) {
        p.destroyed = true;
        this.score += 5000;
        this._emit('destroy', p.x, p.y, '#4ade80', 20);
        /* LabFX: pig defeated */
        if (window.LabFX) {
          LabFX.sound.play('chime');
          LabFX.particles.emit({
            ctx: this.ctx, x: p.x, y: p.y,
            count: 30, color: ['#06D6E0', '#FFD166', '#EF476F'],
            speed: 130, spread: Math.PI * 2, life: 800,
            glow: true, shape: 'spark',
          });
        }
      } else {
        this._emit('hit', p.x, p.y, '#86efac', 5);
      }
      bird.x += nx * (minD - dist + 1);
      bird.y += ny * (minD - dist + 1);
    }
  }

  _win() {
    this.state = 'win';
    this.score += this.birdsLeft.length * 3000;
    this._emit('confetti', this.W / 2, this.H * 0.35, '#ffd166', 50);
    if (this.onUpdate) this.onUpdate(this.info());
  }

  _offScreen(b) {
    return b.x > this.W + 60 || b.x < -60 || b.y > this.H + 60;
  }

  /* ── PARTICLES ───────────────────────────────────────────────── */

  _emit(type, x, y, color, n) {
    const confetti = ['#ffd166', '#ef476f', '#06d6e0', '#7bf5a4', '#9b5de5'];
    for (let i = 0; i < n; i++) {
      const a = Math.random() * Math.PI * 2;
      const spd = type === 'confetti' ? 80 + Math.random() * 200
                : type === 'destroy'  ? 90 + Math.random() * 220
                : 45 + Math.random() * 100;
      this._particles.push({
        x, y,
        vx: Math.cos(a) * spd,
        vy: Math.sin(a) * spd - (type === 'destroy' || type === 'confetti' ? 100 : 20),
        r:  type === 'confetti' ? 4 + Math.random() * 5 : 2 + Math.random() * 4,
        color: type === 'confetti' ? confetti[i % confetti.length] : color,
        gravity: type === 'confetti' ? 180 : 300,
        life: 1, maxLife: 0.5 + Math.random() * 0.9,
      });
    }
  }

  _stepParticles(dt) {
    for (const p of this._particles) {
      p.x += p.vx * dt; p.y += p.vy * dt;
      p.vy += p.gravity * dt; p.vx *= 0.97;
      p.life -= dt / p.maxLife;
    }
    this._particles = this._particles.filter(p => p.life > 0);
  }

  /* ── PREVIEW PATH ────────────────────────────────────────────── */

  _updatePreview() {
    const dx = this._drag.mx - this._sX;
    const dy = this._drag.my - this._sY;
    const K  = 5.8;
    const vx0 = -dx * K, vy0 = -dy * K;
    const g   = this._planet().g * this._sc;
    const lvl = this._levels[this.levelIdx];
    const wind = (lvl?.wind || 0) * this._sc * 0.22;

    this._previewPath = [];
    let x = this._sX, y = this._sY, vx = vx0, vy = vy0;
    const dt = 0.028;
    for (let i = 0; i < 38; i++) {
      vx += wind * dt; vy += g * dt;
      x += vx * dt; y += vy * dt;
      if (y > this._gY || x > this.W) break;
      this._previewPath.push({ x, y, a: 1 - i / 38 });
    }
  }

  /* ── DRAWING ─────────────────────────────────────────────────── */

  _drawBackground() {
    const ctx = this.ctx;
    const pl  = this._planet();
    ctx.clearRect(0, 0, this.W, this.H);

    /* Sky */
    const sky = ctx.createLinearGradient(0, 0, 0, this._gY);
    sky.addColorStop(0, pl.sky1); sky.addColorStop(1, pl.sky2);
    ctx.fillStyle = sky; ctx.fillRect(0, 0, this.W, this._gY);

    /* Stars */
    for (const s of this._stars) {
      ctx.beginPath(); ctx.arc(s.x * this.W, s.y * this._gY, s.r, 0, Math.PI * 2);
      ctx.fillStyle = `rgba(255,255,255,${s.a})`; ctx.fill();
    }

    /* Ground */
    const gnd = ctx.createLinearGradient(0, this._gY, 0, this.H);
    gnd.addColorStop(0, pl.ground);
    gnd.addColorStop(1, this._shade(pl.ground, 0.45));
    ctx.fillStyle = gnd; ctx.fillRect(0, this._gY, this.W, this.H - this._gY);
    ctx.strokeStyle = 'rgba(255,255,255,0.10)'; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.moveTo(0, this._gY); ctx.lineTo(this.W, this._gY); ctx.stroke();

    /* Wind arrow (visual, centred top) */
    const lvl = this._levels[this.levelIdx];
    if (lvl?.wind) {
      const dir = lvl.wind > 0 ? 1 : -1;
      const len = Math.min(Math.abs(lvl.wind) * 7, 70);
      const wx  = this.W * 0.5, wy = 22;
      ctx.strokeStyle = 'rgba(255,255,255,0.5)'; ctx.lineWidth = 2.5;
      ctx.setLineDash([5, 3]);
      ctx.beginPath(); ctx.moveTo(wx - dir * len / 2, wy); ctx.lineTo(wx + dir * len / 2, wy); ctx.stroke();
      ctx.setLineDash([]);
      const ax = wx + dir * len / 2;
      ctx.fillStyle = 'rgba(255,255,255,0.5)';
      ctx.beginPath(); ctx.moveTo(ax, wy); ctx.lineTo(ax - dir*9, wy-5); ctx.lineTo(ax - dir*9, wy+5); ctx.closePath(); ctx.fill();
    }
  }

  _drawSlingshot() {
    const ctx = this.ctx;
    const sx = this._sX, sy = this._sY, gY = this._gY;
    ctx.strokeStyle = '#6b3a1f'; ctx.lineWidth = 9; ctx.lineCap = 'round';
    ctx.beginPath(); ctx.moveTo(sx, gY); ctx.lineTo(sx - 2, sy + 14); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(sx - 12, gY + 4); ctx.lineTo(sx - 18, sy - 12); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(sx + 12, gY + 4); ctx.lineTo(sx + 18, sy - 12); ctx.stroke();
    ctx.fillStyle = '#4a2510';
    ctx.beginPath(); ctx.arc(sx - 18, sy - 12, 5.5, 0, Math.PI * 2); ctx.fill();
    ctx.beginPath(); ctx.arc(sx + 18, sy - 12, 5.5, 0, Math.PI * 2); ctx.fill();
  }

  _drawSlingshotRubber() {
    const ctx = this.ctx;
    const sx = this._sX, sy = this._sY;
    const bx = (this._drag.pulling && this.bird) ? this._drag.mx : (this.bird ? this.bird.x : sx);
    const by = (this._drag.pulling && this.bird) ? this._drag.my : (this.bird ? this.bird.y : sy);
    ctx.strokeStyle = '#7a4020'; ctx.lineWidth = 3; ctx.lineCap = 'round';
    ctx.beginPath(); ctx.moveTo(sx - 18, sy - 12); ctx.lineTo(bx, by); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(sx + 18, sy - 12); ctx.lineTo(bx, by); ctx.stroke();
  }

  _drawQueue() {
    const birds = this.birdsLeft;
    if (!birds.length) return;
    const qy  = this._gY + 26;
    const gap = 28;
    const startX = this._sX + 30; // right of sling handle
    for (let i = 0; i < Math.min(birds.length, 8); i++) {
      const def = AB_BIRDS[birds[i]] || AB_BIRDS.normal;
      this._drawBirdShape(startX + i * gap, qy, def.r * 0.6, def.color, 0.65);
    }
  }

  _drawBird(bird) {
    const ctx = this.ctx;
    /* trail */
    for (let i = 0; i < bird.trail.length; i++) {
      const t = bird.trail[i];
      ctx.beginPath(); ctx.arc(t.x, t.y, bird.r * 0.38, 0, Math.PI * 2);
      ctx.fillStyle = `rgba(255,255,255,${(i / bird.trail.length) * 0.35})`; ctx.fill();
    }
    this._drawBirdShape(bird.x, bird.y, bird.r, bird.color, 1);
  }

  _drawBirdShape(x, y, r, color, alpha) {
    const ctx = this.ctx;
    ctx.save(); ctx.globalAlpha = alpha;
    const g = ctx.createRadialGradient(x - r * 0.3, y - r * 0.35, r * 0.1, x, y, r);
    g.addColorStop(0, this._lighten(color, 60)); g.addColorStop(1, color);
    ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2);
    ctx.fillStyle = g; ctx.fill();
    ctx.strokeStyle = this._shade(color, 0.6); ctx.lineWidth = 1.5; ctx.stroke();
    /* eyes */
    ctx.fillStyle = '#fff';
    ctx.beginPath(); ctx.arc(x + r * 0.26, y - r * 0.18, r * 0.24, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = '#1a1a1a';
    ctx.beginPath(); ctx.arc(x + r * 0.33, y - r * 0.15, r * 0.11, 0, Math.PI * 2); ctx.fill();
    /* eyebrow (angry) */
    ctx.strokeStyle = '#333'; ctx.lineWidth = Math.max(1.5, r * 0.13); ctx.lineCap = 'round';
    ctx.beginPath(); ctx.moveTo(x + r * 0.08, y - r * 0.4); ctx.lineTo(x + r * 0.52, y - r * 0.28); ctx.stroke();
    ctx.restore();
  }

  _drawBlocks() {
    const ctx = this.ctx;
    for (const b of this.blocks) {
      if (b.destroyed) continue;
      const mat = AB_MATS[b.mat] || AB_MATS.wood;
      const hp  = b.hp / b.maxHp;
      ctx.save();
      ctx.translate(b.x + b.w / 2, b.y + b.h / 2); ctx.rotate(b.angle);
      ctx.fillStyle = mat.color; ctx.globalAlpha = 0.45 + hp * 0.55;
      ctx.fillRect(-b.w / 2, -b.h / 2, b.w, b.h);
      ctx.globalAlpha = 1;
      ctx.strokeStyle = mat.border; ctx.lineWidth = 1.5;
      ctx.strokeRect(-b.w / 2, -b.h / 2, b.w, b.h);
      if (hp < 0.55) {
        ctx.strokeStyle = `rgba(0,0,0,${(1 - hp) * 0.55})`; ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(-b.w * 0.12, -b.h * 0.35); ctx.lineTo(b.w * 0.18, b.h * 0.12);
        ctx.moveTo(b.w * 0.08, -b.h * 0.22);  ctx.lineTo(-b.w * 0.2, b.h * 0.3);
        ctx.stroke();
      }
      ctx.restore();
    }
  }

  _drawPigs() {
    const ctx = this.ctx;
    for (const p of this.pigs) {
      if (p.destroyed) continue;
      const flash = p.flash > 0;
      ctx.save();
      const grd = ctx.createRadialGradient(p.x - p.r * 0.3, p.y - p.r * 0.3, p.r * 0.1, p.x, p.y, p.r);
      grd.addColorStop(0, flash ? '#ffcc44' : '#7bf5a4');
      grd.addColorStop(1, flash ? '#ff6600' : '#22c55e');
      ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
      ctx.fillStyle = grd; ctx.fill();
      ctx.strokeStyle = '#166534'; ctx.lineWidth = 2; ctx.stroke();
      /* snout */
      ctx.beginPath(); ctx.ellipse(p.x, p.y + p.r * 0.28, p.r * 0.38, p.r * 0.26, 0, 0, Math.PI * 2);
      ctx.fillStyle = flash ? '#ffdd88' : '#4ade80'; ctx.fill();
      ctx.fillStyle = '#166534';
      ctx.beginPath(); ctx.arc(p.x - p.r * 0.13, p.y + p.r * 0.25, p.r * 0.07, 0, Math.PI * 2); ctx.fill();
      ctx.beginPath(); ctx.arc(p.x + p.r * 0.13, p.y + p.r * 0.25, p.r * 0.07, 0, Math.PI * 2); ctx.fill();
      /* eyes */
      ctx.fillStyle = '#fff';
      ctx.beginPath(); ctx.arc(p.x - p.r * 0.26, p.y - p.r * 0.1, p.r * 0.23, 0, Math.PI * 2); ctx.fill();
      ctx.beginPath(); ctx.arc(p.x + p.r * 0.26, p.y - p.r * 0.1, p.r * 0.23, 0, Math.PI * 2); ctx.fill();
      ctx.fillStyle = '#111';
      ctx.beginPath(); ctx.arc(p.x - p.r * 0.22, p.y - p.r * 0.08, p.r * 0.1, 0, Math.PI * 2); ctx.fill();
      ctx.beginPath(); ctx.arc(p.x + p.r * 0.3, p.y - p.r * 0.08, p.r * 0.1, 0, Math.PI * 2); ctx.fill();
      /* HP bar */
      if (p.hp < p.maxHp) {
        const bw = p.r * 2.2, bh = 5, bx = p.x - bw / 2, by = p.y - p.r - 11;
        ctx.fillStyle = 'rgba(0,0,0,0.45)'; ctx.fillRect(bx, by, bw, bh);
        ctx.fillStyle = `hsl(${120 * p.hp / p.maxHp},88%,42%)`;
        ctx.fillRect(bx, by, bw * p.hp / p.maxHp, bh);
      }
      ctx.restore();
    }
  }

  _drawParticles() {
    const ctx = this.ctx;
    for (const p of this._particles) {
      ctx.save(); ctx.globalAlpha = Math.max(0, p.life) * 0.88;
      ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
      ctx.fillStyle = p.color; ctx.fill(); ctx.restore();
    }
  }

  _drawPreview() {
    const ctx = this.ctx;
    ctx.save();
    for (const pt of this._previewPath) {
      ctx.beginPath(); ctx.arc(pt.x, pt.y, 2.5, 0, Math.PI * 2);
      ctx.fillStyle = `rgba(255,255,255,${pt.a * 0.55})`; ctx.fill();
    }
    ctx.restore();
  }

  _drawHUD() {
    const ctx = this.ctx;
    const pl  = this._planet();
    const lvl = this._levels[this.levelIdx];

    /* Score — top right */
    ctx.font = 'bold 20px Manrope,sans-serif'; ctx.textAlign = 'right';
    ctx.fillStyle = 'rgba(255,255,255,0.95)';
    ctx.fillText(this.score.toLocaleString('ru') + ' очков', this.W - 14, 30);

    /* Level — top left */
    ctx.textAlign = 'left'; ctx.font = 'bold 15px Manrope,sans-serif';
    ctx.fillStyle = 'rgba(255,255,255,0.9)';
    ctx.fillText(`Уровень ${this.levelIdx + 1} / ${this._levels.length}`, 14, 30);

    /* Planet + g — second line, readable */
    ctx.font = '13px Manrope,sans-serif'; ctx.fillStyle = 'rgba(255,255,255,0.72)';
    ctx.fillText(`${pl.label.replace(/<svg[\s\S]*?<\/svg>/g, '').trim()}   g = ${pl.g} м/с²`, 14, 50);

    /* Wind reminder */
    if (lvl?.wind) {
      ctx.font = '12px Manrope,sans-serif'; ctx.fillStyle = 'rgba(255,255,255,0.65)';
      ctx.fillText(`Ветер ${lvl.wind > 0 ? '→' : '←'} ${Math.abs(lvl.wind)} м/с`, 14, 66);
    }

    /* Active bird type label near sling */
    if (this.bird && this.state === 'aim') {
      const def = AB_BIRDS[this.bird.type] || AB_BIRDS.normal;
      ctx.font = '12px Manrope,sans-serif'; ctx.textAlign = 'center';
      ctx.fillStyle = 'rgba(255,255,255,0.7)';
      ctx.fillText(def.label, this._sX, this._gY + 50);
    }
  }

  _drawOverlay() {
    const ctx = this.ctx;
    const win = this.state === 'win';
    ctx.fillStyle = win ? 'rgba(0,30,8,0.78)' : 'rgba(30,0,0,0.78)';
    ctx.fillRect(0, 0, this.W, this.H);
    const cx = this.W / 2, cy = this.H / 2;
    ctx.textAlign = 'center';
    ctx.font = 'bold 34px Manrope,sans-serif';
    ctx.fillStyle = win ? '#7bf5a4' : '#ef476f';
    ctx.fillText(win ? '✦ Уровень пройден!' : '✦ Попробуй ещё раз', cx, cy - 18);
    ctx.font = '17px Manrope,sans-serif'; ctx.fillStyle = 'rgba(255,255,255,0.7)';
    ctx.fillText(win ? `Очки: ${this.score.toLocaleString('ru')}` : 'Свиньи выжили!', cx, cy + 18);
    ctx.font = '12px Manrope,sans-serif'; ctx.fillStyle = 'rgba(255,255,255,0.38)';
    ctx.fillText('Выбери уровень или нажми «Сначала»', cx, cy + 48);
  }

  /* ── UTILS ───────────────────────────────────────────────────── */

  _evt(e) {
    const r = this.canvas.getBoundingClientRect();
    return { x: e.clientX - r.left, y: e.clientY - r.top };
  }

  _expandHex(hex) {
    // Expand 3-digit (#abc <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> #aabbcc)
    if (/^#[0-9a-fA-F]{3}$/.test(hex))
      hex = '#' + hex[1]+hex[1] + hex[2]+hex[2] + hex[3]+hex[3];
    return hex;
  }

  _shade(hex, f) {
    hex = this._expandHex(hex);
    const r = parseInt(hex.slice(1, 3), 16), g = parseInt(hex.slice(3, 5), 16), b = parseInt(hex.slice(5, 7), 16);
    return `rgb(${Math.floor(r*f)},${Math.floor(g*f)},${Math.floor(b*f)})`;
  }

  _lighten(hex, add) {
    hex = this._expandHex(hex);
    const r = Math.min(255, parseInt(hex.slice(1,3),16)+add);
    const g = Math.min(255, parseInt(hex.slice(3,5),16)+add);
    const b = Math.min(255, parseInt(hex.slice(5,7),16)+add);
    return `rgb(${r},${g},${b})`;
  }
}

/* ─── lab UI init ─────────────────────────────────── */
  var angryBirdsSim = null;

  function _openAngryBirds() {
    document.getElementById('sim-topbar-title').textContent = 'Angry Birds Physics';
    _simShow('sim-angrybirds');
    _simShow('ctrl-angrybirds');
    requestAnimationFrame(() => requestAnimationFrame(() => {
      const c = document.getElementById('angrybirds-canvas');
      if (!angryBirdsSim) {
        angryBirdsSim = new AngryBirdsSim(c);
        angryBirdsSim.onUpdate = _abUpdateUI;
        c.addEventListener('mousedown', e => angryBirdsSim.handleMouseDown(e));
        c.addEventListener('mousemove', e => angryBirdsSim.handleMouseMove(e));
        c.addEventListener('mouseup',   e => angryBirdsSim.handleMouseUp(e));
        c.addEventListener('mouseleave', e => angryBirdsSim.handleMouseUp(e));
        _addTouchSupport(c, angryBirdsSim);
      }
      angryBirdsSim.fit();
      angryBirdsSim.start();
    }));
  }

  function abLevel(n, btn) {
    document.querySelectorAll('.ab-lvl-btn').forEach(b => b.classList.remove('active'));
    if (btn) btn.classList.add('active');
    if (angryBirdsSim) angryBirdsSim.loadLevel(n);
  }

  function angryBirdsRestart() {
    if (angryBirdsSim) angryBirdsSim.restart();
  }

  function _abUpdateUI(info) {
    const v = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
    v('abbar-v1', info.level);
    v('abbar-v2', info.birds);
    v('abbar-v3', info.pigs);
    v('abbar-v4', info.score.toLocaleString('ru'));
    v('abbar-v5', info.planet);
    /* sync level button highlight */
    document.querySelectorAll('.ab-lvl-btn').forEach((b, i) => {
      b.classList.toggle('active', i === (info.level - 1));
    });
  }

  /* ── quadratic ── */

