'use strict';

/**
 * RadioactiveSim — Radioactive decay simulation.
 * Left panel: particle canvas (circles colored by species).
 * Right panel: N(t) graph with theoretical curve overlay.
 * Supports single-step decays and short decay chains.
 *
 * Decay chains are simplified to 4-5 prominent steps;
 * the full U-238 chain (14 nuclides) is condensed to 5.
 */
class RadioactiveSim {
  constructor(canvas, graphCanvas) {
    this.canvas      = canvas;
    this.ctx         = canvas.getContext('2d');
    this.graphCanvas = graphCanvas;
    this.gCtx        = graphCanvas.getContext('2d');

    /* layout */
    this.W = 0; this.H = 0;
    this.GW = 0; this.GH = 0;
    this._dpr = 1;

    /* simulation state */
    this.particles  = [];   // [{x, y, vx, vy, step, flash, flashT}]
    this.history    = [];   // [{t, counts:[...per step]}]
    this._raf       = null;
    this._last      = 0;
    this.simTime    = 0;    // sim time in seconds (scaled)
    this.playing    = false;

    /* parameters */
    this.isotope  = 'C-14';
    this.N0       = 500;
    this.speed    = 10;     // time multiplier

    /* callbacks */
    this.onUpdate = null;

    /* load preset */
    this._loadIsotope(this.isotope);
    this._spawn();

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

  /* ══════════════ isotope presets ══════════════ */

  static ISOTOPES = {
    'C-14': {
      label: '¹⁴C',
      steps: [
        { name: '¹⁴C',  T_half: 5730 * 3.156e7,  type: 'β⁻', color: '#9B5DE5' },
        { name: '¹⁴N',  T_half: Infinity,          type: null,  color: '#4CAF50' },
      ]
    },
    'I-131': {
      label: '¹³¹I',
      steps: [
        { name: '¹³¹I',  T_half: 8.02 * 86400,     type: 'β⁻', color: '#F15BB5' },
        { name: '¹³¹Xe', T_half: Infinity,           type: null,  color: '#06D6E0' },
      ]
    },
    'Cs-137': {
      label: '¹³⁷Cs',
      steps: [
        { name: '¹³⁷Cs', T_half: 30.2 * 3.156e7,   type: 'β⁻', color: '#FFD166' },
        { name: '¹³⁷Ba', T_half: Infinity,           type: null,  color: '#7BF5A4' },
      ]
    },
    'Ra-226': {
      label: '²²⁶Ra',
      steps: [
        { name: '²²⁶Ra', T_half: 1600 * 3.156e7,   type: 'α',   color: '#EF476F' },
        { name: '²²²Rn', T_half: 3.82 * 86400,      type: 'α',   color: '#FF9F1C' },
        { name: '²¹⁸Po', T_half: 3.05 * 60,         type: 'α',   color: '#F15BB5' },
        { name: '²¹⁴Pb', T_half: 26.8 * 60,         type: 'β⁻',  color: '#9B5DE5' },
        { name: '²⁰⁶Pb', T_half: Infinity,           type: null,  color: '#4CAF50' },
      ]
    },
    'K-40': {
      label: '⁴⁰K',
      steps: [
        { name: '⁴⁰K',   T_half: 1.248e9 * 3.156e7, type: 'β⁻/EC', color: '#06D6E0' },
        { name: '⁴⁰Ca/⁴⁰Ar', T_half: Infinity,       type: null,  color: '#7BF5A4' },
      ]
    },
    'U-238': {
      label: '²³⁸U',
      // Condensed chain: U-238 → Th-234 → Ra-226 → Rn-222 → Pb-206 (stable)
      // Full chain has 14 steps; we keep 5 most prominent
      steps: [
        { name: '²³⁸U',  T_half: 4.468e9 * 3.156e7, type: 'α',   color: '#FFD166' },
        { name: '²³⁴Th', T_half: 24.1 * 86400,       type: 'β⁻',  color: '#F15BB5' },
        { name: '²²⁶Ra', T_half: 1600 * 3.156e7,     type: 'α',   color: '#EF476F' },
        { name: '²²²Rn', T_half: 3.82 * 86400,       type: 'α',   color: '#9B5DE5' },
        { name: '²⁰⁶Pb', T_half: Infinity,            type: null,  color: '#4CAF50' },
      ]
    },
    'U-235': {
      label: '²³⁵U',
      // Condensed: U-235 → Pa-231 → Ac-227 → Bi-211 → Pb-207 (stable)
      steps: [
        { name: '²³⁵U',  T_half: 7.04e8 * 3.156e7,  type: 'α',   color: '#FF9F1C' },
        { name: '²³¹Pa', T_half: 32760 * 3.156e7,    type: 'α',   color: '#F15BB5' },
        { name: '²²⁷Ac', T_half: 21.77 * 3.156e7,   type: 'β⁻',  color: '#9B5DE5' },
        { name: '²¹¹Bi', T_half: 2.14 * 60,          type: 'α',   color: '#06D6E0' },
        { name: '²⁰⁷Pb', T_half: Infinity,            type: null,  color: '#4CAF50' },
      ]
    },
  };

  _loadIsotope(id) {
    this.isotope = id;
    const preset = RadioactiveSim.ISOTOPES[id];
    this.steps   = preset.steps;
    // λ for each step
    this.lambdas = this.steps.map(s =>
      s.T_half === Infinity ? 0 : Math.LN2 / s.T_half
    );
    this.simTime = 0;
    this.history = [];
    this._lastHalfLifeMark = 0;
    this._fxDecayCount = 0;
  }

  /* ══════════════ public API ══════════════ */

  fit() {
    const dpr = window.devicePixelRatio || 1;
    this._dpr = dpr;

    const pw = this.canvas.offsetWidth  || 480;
    const ph = this.canvas.offsetHeight || 400;
    this.canvas.width  = pw * dpr;
    this.canvas.height = ph * dpr;
    this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    this.W = pw; this.H = ph;

    const gw = this.graphCanvas.offsetWidth  || 340;
    const gh = this.graphCanvas.offsetHeight || 400;
    this.graphCanvas.width  = gw * dpr;
    this.graphCanvas.height = gh * dpr;
    this.gCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
    this.GW = gw; this.GH = gh;

    this._layoutParticles();
    this.draw();
  }

  reset() {
    this.pause();
    this._loadIsotope(this.isotope);
    this._spawn();
    this.draw();
    this._emit();
  }

  play() {
    if (this.playing) return;
    this.playing = true;
    this._last = performance.now();
    this._raf  = requestAnimationFrame(ts => this._tick(ts));
  }

  pause() {
    this.playing = false;
    if (this._raf) { cancelAnimationFrame(this._raf); this._raf = null; }
  }

  stop() { this.pause(); }

  setIsotope(id) {
    if (!RadioactiveSim.ISOTOPES[id]) return;
    this.isotope = id;
    this.reset();
  }

  setSpeed(v)  { this.speed = Math.max(1, Math.min(1000, +v)); }
  setN0(v)     { this.N0 = Math.max(50, Math.min(2000, +v)); this.reset(); }

  getParams() {
    return { isotope: this.isotope, N0: this.N0, speed: this.speed };
  }

  info() {
    const counts = this._counts();
    const T = this.steps[0].T_half;
    const periods = T === Infinity ? 0 : this.simTime / T;
    const decayed = this.N0 > 0 ? 1 - counts[0] / this.N0 : 0;
    const lambda0 = this.lambdas[0];
    const activity = Math.round(counts[0] * lambda0);
    return {
      periods:  periods.toFixed(2),
      decayPct: (decayed * 100).toFixed(1),
      activity,
      counts,
      names: this.steps.map(s => s.name),
    };
  }

  /* ══════════════ internal ══════════════ */

  _spawn() {
    this.particles = [];
    this._flashes  = [];
    const simW = this.W || 480;
    const simH = this.H || 400;
    for (let i = 0; i < this.N0; i++) {
      this.particles.push({
        x:  Math.random() * simW,
        y:  Math.random() * simH,
        vx: (Math.random() - 0.5) * 30,
        vy: (Math.random() - 0.5) * 30,
        step:   0,   // index into this.steps
        flash:  false,
        flashT: 0,
        flashSymbol: '',
      });
    }
  }

  _layoutParticles() {
    // re-distribute within new canvas size after fit
    const W = this.W, H = this.H;
    if (!W || !H) return;
    for (const p of this.particles) {
      if (p.x > W) p.x = Math.random() * W;
      if (p.y > H) p.y = Math.random() * H;
    }
  }

  _counts() {
    const c = new Array(this.steps.length).fill(0);
    for (const p of this.particles) {
      if (p.step < this.steps.length) c[p.step]++;
    }
    return c;
  }

  _tick(ts) {
    if (!this.playing) return;
    const wallDt = Math.min((ts - this._last) / 1000, 0.05); // s, capped
    this._last = ts;
    const dt = wallDt * this.speed;  // scaled sim time step

    // physics + decay
    const W = this.W, H = this.H;
    for (const p of this.particles) {
      // move
      p.x += p.vx * wallDt;
      p.y += p.vy * wallDt;
      // bounce off walls
      if (p.x < 0)  { p.x = 0;  p.vx =  Math.abs(p.vx); }
      if (p.x > W)  { p.x = W;  p.vx = -Math.abs(p.vx); }
      if (p.y < 0)  { p.y = 0;  p.vy =  Math.abs(p.vy); }
      if (p.y > H)  { p.y = H;  p.vy = -Math.abs(p.vy); }

      // decay (only if not at final stable step)
      const step = p.step;
      const lambda = this.lambdas[step];
      if (lambda > 0 && Math.random() < lambda * dt) {
        p.step = Math.min(step + 1, this.steps.length - 1);
        // emit flash
        const decayType = this.steps[step].type || '';
        const sym = decayType.startsWith('α') ? 'α'
                  : decayType.startsWith('β') ? 'β'
                  : 'γ';
        this._flashes.push({ x: p.x, y: p.y, t: 0, maxT: 0.35, sym });
        // LabFX decay effects (throttled)
        if (window.LabFX) {
          this._fxFrameDecays = (this._fxFrameDecays || 0) + 1;
          LabFX.particles.emit({
            ctx: this.ctx, x: p.x, y: p.y,
            count: 6, color: '#FFD700', speed: 60,
            spread: Math.PI * 2, angle: 0, gravity: 0,
            life: 300, shape: 'spark', glow: true, size: 2,
          });
        }
      }

      // age flash on particle itself
      if (p.flash) {
        p.flashT -= wallDt;
        if (p.flashT <= 0) p.flash = false;
      }
    }

    // age global flashes
    for (let i = this._flashes.length - 1; i >= 0; i--) {
      this._flashes[i].t += wallDt;
      if (this._flashes[i].t >= this._flashes[i].maxT) {
        this._flashes.splice(i, 1);
      }
    }

    this.simTime += dt;

    // LabFX half-life crossing + throttled tick sound
    if (window.LabFX) {
      /* throttle tick: play at most once per frame, only if ≤10 decays/s effective */
      const frameDecays = this._fxFrameDecays || 0;
      this._fxFrameDecays = 0;
      if (frameDecays > 0) {
        const decaysPerSec = frameDecays / Math.max(wallDt, 0.001);
        const N = Math.max(1, Math.round(decaysPerSec / 10));
        if (Math.random() < 1 / N) {
          LabFX.sound.play('tick', { pitch: 0.8 + Math.random() * 0.4, volume: 0.08 });
        }
      }
      LabFX.particles.update(wallDt);
      const T0 = this.steps[0].T_half;
      if (T0 !== Infinity) {
        const halfLifesElapsed = Math.floor(this.simTime / T0);
        if (halfLifesElapsed > (this._lastHalfLifeMark || 0)) {
          this._lastHalfLifeMark = halfLifesElapsed;
          LabFX.sound.play('chime', {
            pitch: 0.6 + halfLifesElapsed * 0.1,
            volume: 0.3,
          });
        }
      }
    }

    // record history every ~2 ticks (≈30ms)
    const last = this.history[this.history.length - 1];
    if (!last || this.simTime - last.t > this.steps[0].T_half * 0.005 || this.history.length < 5) {
      this._recordHistory();
    }

    this.draw();
    this._emit();

    this._raf = requestAnimationFrame(ts2 => this._tick(ts2));
  }

  _recordHistory() {
    this.history.push({ t: this.simTime, counts: this._counts() });
    // keep last 500 points
    if (this.history.length > 500) this.history.shift();
  }

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

  /* ══════════════ drawing ══════════════ */

  draw() {
    this._drawParticles();
    this._drawGraph();
  }

  _drawParticles() {
    const ctx = this.ctx;
    const W = this.W, H = this.H;
    if (!W || !H) return;

    ctx.clearRect(0, 0, W, H);

    // background
    ctx.fillStyle = '#0D0D1A';
    ctx.fillRect(0, 0, W, H);

    // subtle grid
    ctx.strokeStyle = 'rgba(255,255,255,0.04)';
    ctx.lineWidth = 1;
    const step = 40;
    ctx.beginPath();
    for (let x = 0; x < W; x += step) { ctx.moveTo(x, 0); ctx.lineTo(x, H); }
    for (let y = 0; y < H; y += step) { ctx.moveTo(0, y); ctx.lineTo(W, y); }
    ctx.stroke();

    // draw flashes first (under particles)
    for (const fl of this._flashes) {
      const alpha = 1 - fl.t / fl.maxT;
      const r = 6 + fl.t / fl.maxT * 12;
      ctx.beginPath();
      ctx.arc(fl.x, fl.y, r, 0, Math.PI * 2);
      ctx.fillStyle = `rgba(255,255,200,${alpha * 0.45})`;
      ctx.fill();

      const symSize = Math.round(8 + alpha * 4);
      ctx.font = `bold ${symSize}px Manrope,sans-serif`;
      const symColor = `rgba(255,255,180,${alpha})`;
      ctx.fillStyle = symColor;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      if (window.LabFX) {
        LabFX.glow.drawGlow(ctx, () => {
          ctx.font = `bold ${symSize}px Manrope,sans-serif`;
          ctx.fillStyle = symColor;
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle';
          ctx.fillText(fl.sym, fl.x, fl.y - r - 4);
        }, { color: '#FFFFFF', intensity: 6 });
      } else {
        ctx.fillText(fl.sym, fl.x, fl.y - r - 4);
      }
    }

    // draw particles
    const R = 4;
    for (const p of this.particles) {
      const s = this.steps[p.step];
      ctx.beginPath();
      ctx.arc(p.x, p.y, R, 0, Math.PI * 2);
      ctx.fillStyle = s.color;
      ctx.fill();
    }

    // legend
    const lx = 10, ly = 10;
    ctx.font = '11px Manrope,sans-serif';
    ctx.textAlign = 'left';
    ctx.textBaseline = 'top';
    for (let i = 0; i < this.steps.length; i++) {
      const s = this.steps[i];
      const y = ly + i * 18;
      ctx.fillStyle = s.color;
      ctx.beginPath();
      ctx.arc(lx + 5, y + 6, 5, 0, Math.PI * 2);
      ctx.fill();
      ctx.fillStyle = 'rgba(255,255,255,0.75)';
      ctx.fillText(s.name, lx + 15, y);
    }

    if (window.LabFX) LabFX.particles.draw(ctx);
  }

  _drawGraph() {
    const ctx = this.gCtx;
    const W = this.GW, H = this.GH;
    if (!W || !H) return;

    ctx.clearRect(0, 0, W, H);
    ctx.fillStyle = '#0D0D1A';
    ctx.fillRect(0, 0, W, H);

    const pad = { l: 40, r: 14, t: 20, b: 36 };
    const gW = W - pad.l - pad.r;
    const gH = H - pad.t - pad.b;

    // grid
    ctx.strokeStyle = 'rgba(255,255,255,0.06)';
    ctx.lineWidth = 1;
    ctx.beginPath();
    for (let i = 0; i <= 4; i++) {
      const y = pad.t + gH - i * gH / 4;
      ctx.moveTo(pad.l, y); ctx.lineTo(pad.l + gW, y);
    }
    for (let i = 0; i <= 5; i++) {
      const x = pad.l + i * gW / 5;
      ctx.moveTo(x, pad.t); ctx.lineTo(x, pad.t + gH);
    }
    ctx.stroke();

    // axes
    ctx.strokeStyle = 'rgba(255,255,255,0.3)';
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, pad.t + gH);
    ctx.moveTo(pad.l, pad.t + gH); ctx.lineTo(pad.l + gW, pad.t + gH);
    ctx.stroke();

    // axis labels
    ctx.font = '10px Manrope,sans-serif';
    ctx.fillStyle = 'rgba(255,255,255,0.45)';
    ctx.textAlign = 'right';
    ctx.textBaseline = 'middle';
    for (let i = 0; i <= 4; i++) {
      const y = pad.t + gH - i * gH / 4;
      const val = Math.round(this.N0 * i / 4);
      ctx.fillText(val, pad.l - 4, y);
    }

    const T0 = this.steps[0].T_half;
    const tMax = T0 === Infinity ? Math.max(this.simTime * 1.1, 1e-6) : T0 * 5;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'top';
    for (let i = 0; i <= 5; i++) {
      const x = pad.l + i * gW / 5;
      const tVal = tMax * i / 5;
      const label = T0 === Infinity ? tVal.toFixed(0) + 's' : (tVal / T0).toFixed(1) + 'T';
      ctx.fillText(label, x, pad.t + gH + 4);
    }

    // axis title
    ctx.font = '9px Manrope,sans-serif';
    ctx.fillStyle = 'rgba(255,255,255,0.3)';
    ctx.textAlign = 'left';
    ctx.fillText('N', pad.l + 2, pad.t + 2);
    ctx.textAlign = 'right';
    ctx.fillText(T0 === Infinity ? 't, с' : 't / T½', pad.l + gW, pad.t + gH + 28);

    if (this.history.length < 2) return;

    const tx = t => pad.l + (t / tMax) * gW;
    const ty = n => pad.t + gH - (n / this.N0) * gH;

    // theoretical decay curve for step 0 (semi-transparent)
    if (T0 !== Infinity) {
      const lam = this.lambdas[0];
      ctx.beginPath();
      ctx.strokeStyle = 'rgba(255,255,255,0.18)';
      ctx.lineWidth = 1.5;
      ctx.setLineDash([4, 3]);
      const nPts = 80;
      for (let i = 0; i <= nPts; i++) {
        const t = tMax * i / nPts;
        const n = this.N0 * Math.exp(-lam * t);
        const x = tx(t), y = ty(n);
        i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
      }
      ctx.stroke();
      ctx.setLineDash([]);
    }

    // actual curves per species
    for (let si = 0; si < this.steps.length; si++) {
      const color = this.steps[si].color;
      ctx.beginPath();
      ctx.strokeStyle = color;
      ctx.lineWidth = 2;
      let first = true;
      for (const pt of this.history) {
        const x = tx(pt.t);
        const y = ty(pt.counts[si]);
        if (x < pad.l || x > pad.l + gW) continue;
        first ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
        first = false;
      }
      ctx.stroke();
    }

    // current time marker
    const curX = tx(this.simTime);
    if (curX >= pad.l && curX <= pad.l + gW) {
      ctx.beginPath();
      ctx.strokeStyle = 'rgba(255,255,255,0.4)';
      ctx.lineWidth = 1;
      ctx.setLineDash([2, 3]);
      ctx.moveTo(curX, pad.t);
      ctx.lineTo(curX, pad.t + gH);
      ctx.stroke();
      ctx.setLineDash([]);
    }
  }
}

/* ══════════════════════════════════════════════
   _openRadioactive — wiring
══════════════════════════════════════════════ */
var radioactiveSim = null;

function _openRadioactive() {
  document.getElementById('sim-topbar-title').textContent = 'Радиоактивный распад';
  document.getElementById('ctrl-radioactive').style.display = '';
  _simShow('sim-radioactive');
  _registerSimState('radioactive', () => radioactiveSim?.getParams(),
    st => { if (radioactiveSim && st) {
      if (st.isotope) radioactiveSim.setIsotope(st.isotope);
      if (st.N0)      radioactiveSim.setN0(st.N0);
      if (st.speed)   radioactiveSim.setSpeed(st.speed);
    }});
  if (typeof _embedMode !== 'undefined' && _embedMode) _startStateEmit('radioactive');

  requestAnimationFrame(() => requestAnimationFrame(() => {
    if (!radioactiveSim) {
      radioactiveSim = new RadioactiveSim(
        document.getElementById('radioactive-canvas'),
        document.getElementById('radioactive-graph')
      );
      radioactiveSim.onUpdate = _radioactiveUpdateHUD;
    }
    radioactiveSim.fit();
    radioactiveSim.reset();
    radioactiveSim.play();
    _radioactiveUpdateHUD(radioactiveSim.info());
  }));
}

function radioactiveIsotope(id) {
  if (radioactiveSim) {
    radioactiveSim.setIsotope(id);
    radioactiveSim.play();
  }
}

function radioactiveSpeed(val) {
  if (radioactiveSim) radioactiveSim.setSpeed(+val);
  const el = document.getElementById('rd-speed-val');
  if (el) el.textContent = '×' + (+val).toFixed(0);
}

function radioactiveN0(val) {
  if (radioactiveSim) radioactiveSim.setN0(+val);
  const el = document.getElementById('rd-n0-val');
  if (el) el.textContent = val;
}

function radioactivePlay() {
  if (!radioactiveSim) return;
  if (window.LabFX) LabFX.sound.play('click');
  if (radioactiveSim.playing) {
    radioactiveSim.pause();
    document.getElementById('rd-play-btn').textContent = 'Старт';
  } else {
    radioactiveSim.play();
    document.getElementById('rd-play-btn').textContent = 'Пауза';
  }
}

function radioactiveReset() {
  if (!radioactiveSim) return;
  if (window.LabFX) LabFX.sound.play('whoosh', { pitch: 0.5, volume: 0.3 });
  radioactiveSim._lastHalfLifeMark = 0;
  radioactiveSim._fxDecayCount = 0;
  radioactiveSim.reset();
  document.getElementById('rd-play-btn').textContent = 'Старт';
}

function _radioactiveUpdateHUD(info) {
  const v = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
  v('rd-hud-periods', info.periods + ' T½');
  v('rd-hud-decayed', info.decayPct + '%');
  v('rd-hud-activity', info.activity + ' Бк');
}

/* ── dating mode ── */
function radioactiveDating(pctLeft) {
  // pct of parent remaining (0-100)
  const ratio = Math.max(0.001, Math.min(0.999, (+pctLeft) / 100));
  const T = radioactiveSim ? radioactiveSim.steps[0].T_half : null;
  if (!T || T === Infinity) return;
  const lambda = Math.LN2 / T;
  const age = -Math.log(ratio) / lambda;
  const el = document.getElementById('rd-dating-result');
  if (el) {
    const years = (age / 3.156e7).toExponential(3);
    el.textContent = 'Возраст: ' + years + ' лет';
  }
  const pctEl = document.getElementById('rd-dating-pct-val');
  if (pctEl) pctEl.textContent = (+pctLeft).toFixed(0) + '% осталось';
}
