// Canvas2D weather engine — the fallback when WebGL2 isn't available. This is
// the original hand-drawn renderer wrapped as an engine factory; weather.js
// owns the toggle, storage and the PeteWeather API and just calls
// set/start/stop here. New GPU-only variants (hail, haze, wind, aurora) alias
// to their nearest 2D look so the fallback never renders a blank page.
(function () {
  window.PeteWeatherEngines = window.PeteWeatherEngines || {};

  window.PeteWeatherEngines.canvas2d = function (canvas) {
    var ctx = canvas.getContext("2d");
    if (!ctx) return null;
    var root = document.documentElement;

    var W = 0, H = 0, DPR = 1;
    function resize() {
      DPR = Math.min(window.devicePixelRatio || 1, 2);
      W = canvas.clientWidth || window.innerWidth;
      H = canvas.clientHeight || window.innerHeight;
      canvas.width = Math.floor(W * DPR);
      canvas.height = Math.floor(H * DPR);
      ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
    }
    resize();
    window.addEventListener("resize", resize);

    var aliases = { hail: "snow", haze: "motes", wind: "leaves", aurora: "clear" };

    var counts = {
      rain: 160, petals: 50, jacaranda: 55, motes: 70, leaves: 38,
      snow: 150, clouds: 7, fog: 7, clear: 0, storm: 200
    };
    var mults = { light: 0.6, medium: 1.0, heavy: 1.4 };

    function rand(a, b) { return a + Math.random() * (b - a); }

    var variant = null;       // current rendered variant
    var intensity = "heavy";  // light | medium | heavy
    var particles = [];
    var flash = 0;            // lightning flash decay (storm only)
    var nextBolt = 2;         // seconds until next lightning bolt (storm only)

    function spawn(initial) {
      var p = {};
      p.x = rand(0, W);
      p.y = initial ? rand(0, H) : rand(-40, -10);
      p.z = rand(0.7, 1.3); // depth — affects size + speed + alpha
      if (variant === "rain" || variant === "storm") {
        p.vy = rand(700, 1100) * p.z;
        p.vx = -rand(60, 120);
        p.len = rand(10, 18) * p.z;
        p.alpha = rand(0.25, 0.55);
      } else if (variant === "snow") {
        p.vy = rand(35, 75) * p.z;
        p.swayAmp = rand(12, 34);
        p.swayFreq = rand(0.3, 0.8);
        p.swayPhase = rand(0, Math.PI * 2);
        p.size = rand(2, 4.6) * p.z;
        p.alpha = rand(0.65, 0.95);
      } else if (variant === "clouds") {
        // Soft puffs drifting across the upper sky.
        p.y = initial ? rand(0, H * 0.5) : rand(-H * 0.1, H * 0.45);
        p.x = initial ? rand(0, W) : -rand(120, 320);
        p.vx = rand(8, 22);
        p.size = rand(80, 170) * p.z;
        p.alpha = rand(0.32, 0.55);
        p.sprite = Math.floor(rand(0, 3));
      } else if (variant === "fog") {
        // Wide translucent bands creeping sideways.
        p.y = rand(H * 0.1, H);
        p.x = initial ? rand(0, W) : -rand(200, 500);
        p.vx = rand(6, 16);
        p.w = rand(260, 520);
        p.h = rand(70, 150);
        p.alpha = rand(0.05, 0.14);
      } else if (variant === "petals" || variant === "jacaranda") {
        p.vy = rand(60, 120);
        p.swayAmp = rand(20, 50);
        p.swayFreq = rand(0.4, 0.9);
        p.swayPhase = rand(0, Math.PI * 2);
        p.rot = rand(0, Math.PI * 2);
        p.vrot = rand(-1.2, 1.2);
        p.size = rand(8, 16) * p.z;
        p.alpha = rand(0.75, 1.0);
        if (variant === "jacaranda") {
          p.hue = rand(265, 285);    // blue-violet — jacaranda uses the petal silhouette in purple
          p.sat = rand(55, 75);
          p.light = rand(70, 82);
        } else {
          p.hue = rand(335, 355);    // pink (almond/cherry)
          p.sat = rand(60, 80);
          p.light = rand(78, 88);
        }
      } else if (variant === "motes") {
        p.vx = rand(-15, 25);
        p.vy = rand(8, 25);
        p.size = rand(1.2, 2.8) * p.z;
        p.alpha = rand(0.35, 0.7);
        p.twinklePhase = rand(0, Math.PI * 2);
        p.twinkleFreq = rand(0.5, 1.5);
      } else if (variant === "leaves") {
        p.vy = rand(70, 130);
        p.swayAmp = rand(30, 70);
        p.swayFreq = rand(0.3, 0.6);
        p.swayPhase = rand(0, Math.PI * 2);
        p.rot = rand(0, Math.PI * 2);
        p.vrot = rand(-2.0, 2.0);
        p.size = rand(10, 18) * p.z;
        p.alpha = rand(0.8, 1.0);
        p.hue = rand(18, 42); // orange→amber→brown
        p.light = rand(38, 55);
      }
      return p;
    }

    var night = root.dataset.phase === "night";
    function refreshNight() { night = root.dataset.phase === "night"; }

    function drawRain(p) {
      // Day/dawn/dusk backgrounds are warm and pale, so the rain needs a darker,
      // more saturated blue plus a slightly thicker line to stay visible. Night
      // keeps a paler tone so streaks read against the dark palette.
      if (night) {
        ctx.strokeStyle = "rgba(200,215,240," + p.alpha + ")";
        ctx.lineWidth = 1;
      } else {
        ctx.strokeStyle = "rgba(60,95,150," + Math.min(1, p.alpha + 0.25) + ")";
        ctx.lineWidth = 1.3;
      }
      ctx.beginPath();
      ctx.moveTo(p.x, p.y);
      ctx.lineTo(p.x - p.len * 0.12, p.y + p.len);
      ctx.stroke();
    }

    function drawSnow(p) {
      // Soft round flake with a faint glow so it reads on light and dark palettes.
      var col = night ? "235,242,255" : "255,255,255";
      ctx.fillStyle = "rgba(" + col + "," + p.alpha + ")";
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
      ctx.fill();
      if (!night) {
        // A thin cool outline keeps white flakes visible against cream backgrounds.
        ctx.strokeStyle = "rgba(150,180,220," + (p.alpha * 0.5) + ")";
        ctx.lineWidth = 0.6;
        ctx.stroke();
      }
    }

    // Pre-baked cloud sprites: clustered blobs softened with a heavy blur so each
    // cloud reads as one fluffy mass. Built once per palette (day/night) onto
    // offscreen canvases, then just drifted — cheap to animate.
    var cloudSprites = null;
    var cloudSpritesNight = null;

    // Three silhouettes for variety. Each is a list of blobs [cx, cy, r] in a
    // 0..1 box (x across, y down) with a flattish base around y≈0.66.
    var cloudShapes = [
      [[0.50, 0.50, 0.26], [0.34, 0.56, 0.20], [0.66, 0.56, 0.20], [0.42, 0.40, 0.18],
       [0.58, 0.38, 0.17], [0.22, 0.60, 0.15], [0.78, 0.60, 0.15], [0.50, 0.62, 0.22]],
      [[0.46, 0.52, 0.24], [0.30, 0.58, 0.18], [0.62, 0.50, 0.22], [0.74, 0.58, 0.16],
       [0.40, 0.40, 0.16], [0.56, 0.36, 0.15], [0.18, 0.62, 0.13], [0.84, 0.62, 0.12]],
      [[0.52, 0.50, 0.28], [0.36, 0.56, 0.19], [0.68, 0.56, 0.18], [0.48, 0.36, 0.16],
       [0.26, 0.60, 0.14], [0.74, 0.60, 0.15], [0.60, 0.62, 0.18], [0.40, 0.62, 0.18]]
    ];

    function makeCloudSprite(col, shape) {
      var c = document.createElement("canvas");
      var w = 320, h = 224;
      c.width = w; c.height = h;
      var cx = c.getContext("2d");
      cx.filter = "blur(18px)"; // the softness that turns blobs into a cloud
      cx.fillStyle = "rgba(" + col + ",1)";
      for (var i = 0; i < shape.length; i++) {
        cx.beginPath();
        cx.arc(shape[i][0] * w, shape[i][1] * h, shape[i][2] * w, 0, Math.PI * 2);
        cx.fill();
      }
      return c;
    }

    function ensureCloudSprites() {
      if (cloudSprites && cloudSpritesNight === night) return;
      // Day sky is warm cream so clouds need a cool slate-grey; night uses a pale
      // tone against the dark palette.
      var col = night ? "206,216,236" : "150,164,190";
      cloudSprites = cloudShapes.map(function (s) { return makeCloudSprite(col, s); });
      cloudSpritesNight = night;
    }

    function drawCloud(p) {
      ensureCloudSprites();
      var spr = cloudSprites[p.sprite % cloudSprites.length];
      var w = p.size * 2.6;
      var h = w * (spr.height / spr.width);
      ctx.globalAlpha = p.alpha;
      ctx.drawImage(spr, p.x - w / 2, p.y - h / 2, w, h);
      ctx.globalAlpha = 1;
    }

    function drawFog(p) {
      var col = night ? "150,160,180" : "230,228,224";
      var g = ctx.createLinearGradient(p.x - p.w / 2, 0, p.x + p.w / 2, 0);
      g.addColorStop(0, "rgba(" + col + ",0)");
      g.addColorStop(0.5, "rgba(" + col + "," + p.alpha + ")");
      g.addColorStop(1, "rgba(" + col + ",0)");
      ctx.fillStyle = g;
      ctx.beginPath();
      ctx.ellipse(p.x, p.y, p.w / 2, p.h / 2, 0, 0, Math.PI * 2);
      ctx.fill();
    }

    function drawMoon(cx, cy, mr) {
      var TAU = Math.PI * 2;
      ctx.save();
      // Clip everything to the lunar disc so shading stays inside the sphere.
      ctx.beginPath();
      ctx.arc(cx, cy, mr, 0, TAU);
      ctx.clip();

      // Spherical body shading — light source upper-right, terminator lower-left.
      var lx = cx + mr * 0.45, ly = cy - mr * 0.45;
      var body = ctx.createRadialGradient(lx, ly, mr * 0.1, cx, cy, mr * 1.35);
      body.addColorStop(0, "rgba(249,251,255,0.97)");
      body.addColorStop(0.55, "rgba(214,222,240,0.92)");
      body.addColorStop(1, "rgba(140,151,180,0.85)");
      ctx.fillStyle = body;
      ctx.fillRect(cx - mr, cy - mr, mr * 2, mr * 2);

      // Limb darkening — fade the edge so the disc reads as a ball, not a coin.
      var limb = ctx.createRadialGradient(cx, cy, mr * 0.62, cx, cy, mr);
      limb.addColorStop(0, "rgba(18,24,46,0)");
      limb.addColorStop(1, "rgba(18,24,46,0.4)");
      ctx.fillStyle = limb;
      ctx.fillRect(cx - mr, cy - mr, mr * 2, mr * 2);
      ctx.restore();

      // Soft outer halo, drawn unclipped around the disc.
      var halo = ctx.createRadialGradient(cx, cy, mr, cx, cy, mr * 2.4);
      halo.addColorStop(0, "rgba(220,230,255,0.28)");
      halo.addColorStop(1, "rgba(220,230,255,0)");
      ctx.fillStyle = halo;
      ctx.beginPath();
      ctx.arc(cx, cy, mr * 2.4, 0, TAU);
      ctx.fill();
    }

    function drawClearGlow(t) {
      // No particles — render a single calm sun (day) or moon (night) glow that
      // breathes very slowly, plus a few stars at night.
      var breathe = 0.5 + 0.5 * Math.sin(t * 0.25);
      var cx = W * 0.82, cy = H * 0.18, r = Math.min(W, H) * 0.5;
      if (night) {
        // Ambient sky glow around the moon.
        var g = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
        g.addColorStop(0, "rgba(220,230,255," + (0.12 + 0.05 * breathe) + ")");
        g.addColorStop(1, "rgba(220,230,255,0)");
        ctx.fillStyle = g;
        ctx.fillRect(0, 0, W, H);
        drawMoon(cx, cy, r * 0.16);
      } else {
        var gd = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
        gd.addColorStop(0, "rgba(255,224,150," + (0.30 + 0.10 * breathe) + ")");
        gd.addColorStop(0.5, "rgba(255,214,120," + (0.10 + 0.04 * breathe) + ")");
        gd.addColorStop(1, "rgba(255,214,120,0)");
        ctx.fillStyle = gd;
        ctx.fillRect(0, 0, W, H);
      }
    }

    // Deterministic star field for clear nights (seeded so they don't jitter).
    var stars = [];
    function buildStars() {
      stars = [];
      var n = 60;
      for (var i = 0; i < n; i++) {
        // Cheap LCG-ish spread keyed by index — stable across frames.
        var sx = ((i * 73 + 11) % 100) / 100;
        var sy = ((i * 37 + 7) % 100) / 100;
        stars.push({ x: sx, y: sy * 0.7, r: 0.6 + (i % 3) * 0.5, ph: (i % 7) });
      }
    }
    function drawStars(t) {
      for (var i = 0; i < stars.length; i++) {
        var s = stars[i];
        var tw = 0.4 + 0.6 * (0.5 + 0.5 * Math.sin(t * 0.8 + s.ph));
        ctx.fillStyle = "rgba(255,255,255," + (0.5 * tw) + ")";
        ctx.beginPath();
        ctx.arc(s.x * W, s.y * H, s.r, 0, Math.PI * 2);
        ctx.fill();
      }
    }

    function drawPetals(p) {
      // Five-petal almond/cherry blossom viewed face-on, with a yellow center.
      var s = p.size;
      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.rotate(p.rot);
      ctx.fillStyle = "hsla(" + p.hue + "," + p.sat + "%," + p.light + "%," + p.alpha + ")";
      for (var i = 0; i < 5; i++) {
        var a = (i / 5) * Math.PI * 2 - Math.PI / 2;
        var cx = Math.cos(a) * s * 0.32;
        var cy = Math.sin(a) * s * 0.32;
        ctx.beginPath();
        ctx.ellipse(cx, cy, s * 0.38, s * 0.24, a, 0, Math.PI * 2);
        ctx.fill();
      }
      ctx.fillStyle = "hsla(48,90%,68%," + p.alpha + ")";
      ctx.beginPath();
      ctx.arc(0, 0, s * 0.13, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    }

    function drawLeaf(p) {
      var s = p.size;
      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.rotate(p.rot);
      var g = ctx.createLinearGradient(-s * 0.5, 0, s * 0.5, 0);
      g.addColorStop(0, "hsla(" + p.hue + ",70%," + (p.light - 10) + "%," + p.alpha + ")");
      g.addColorStop(1, "hsla(" + p.hue + ",75%," + (p.light + 10) + "%," + p.alpha + ")");
      ctx.fillStyle = g;
      // Almond/lozenge leaf — pointed at both ends.
      ctx.beginPath();
      ctx.moveTo(-s * 0.55, 0);
      ctx.quadraticCurveTo(0, -s * 0.38, s * 0.55, 0);
      ctx.quadraticCurveTo(0, s * 0.38, -s * 0.55, 0);
      ctx.closePath();
      ctx.fill();
      // Midrib vein.
      ctx.strokeStyle = "hsla(" + p.hue + ",60%," + (p.light - 22) + "%," + (p.alpha * 0.7) + ")";
      ctx.lineWidth = 0.8;
      ctx.beginPath();
      ctx.moveTo(-s * 0.5, 0);
      ctx.lineTo(s * 0.5, 0);
      ctx.stroke();
      // Small stem nub at the base.
      ctx.strokeStyle = "hsla(" + p.hue + ",55%," + (p.light - 25) + "%," + (p.alpha * 0.7) + ")";
      ctx.lineWidth = 1.0;
      ctx.beginPath();
      ctx.moveTo(-s * 0.55, 0);
      ctx.lineTo(-s * 0.7, -s * 0.04);
      ctx.stroke();
      ctx.restore();
    }

    function drawMote(p, t) {
      var twinkle = 0.5 + 0.5 * Math.sin(t * p.twinkleFreq + p.twinklePhase);
      // Night keeps a pale glowing mote against the dark palette. Day/dawn/dusk
      // use a deeper saturated honey so the motes stand out against warm cream
      // and peach backgrounds; alpha also gets a bump.
      var color, a;
      if (night) {
        color = "255,240,180";
        a = p.alpha * (0.45 + 0.55 * twinkle);
      } else {
        color = "130,80,180";
        a = Math.min(1, (p.alpha + 0.2) * (0.6 + 0.4 * twinkle));
      }
      // Soft glow halo.
      var g = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size * 5);
      g.addColorStop(0, "rgba(" + color + "," + (a * 0.55) + ")");
      g.addColorStop(1, "rgba(" + color + ",0)");
      ctx.fillStyle = g;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size * 5, 0, Math.PI * 2);
      ctx.fill();
      // Bright core.
      ctx.fillStyle = "rgba(" + color + "," + a + ")";
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
      ctx.fill();
    }

    var last = performance.now();
    var raf = 0;

    function step(now) {
      var dt = Math.min(0.05, (now - last) / 1000);
      last = now;
      var t = now / 1000;
      ctx.clearRect(0, 0, W, H);
      refreshNight();

      if (variant === "clear") {
        drawClearGlow(t);
        if (night) drawStars(t);
        raf = requestAnimationFrame(step);
        return;
      }

      // Storm: drive the lightning flash before drawing rain over it.
      if (variant === "storm") {
        nextBolt -= dt;
        if (nextBolt <= 0) {
          flash = 1;
          nextBolt = rand(2.5, 7);
        }
        if (flash > 0) {
          ctx.fillStyle = "rgba(235,240,255," + (flash * 0.5) + ")";
          ctx.fillRect(0, 0, W, H);
          flash = Math.max(0, flash - dt * 4); // ~0.25s decay
        }
      }

      for (var i = 0; i < particles.length; i++) {
        var p = particles[i];
        if (variant === "rain" || variant === "storm") {
          p.x += p.vx * dt;
          p.y += p.vy * dt;
          if (p.y > H + 20 || p.x < -40) {
            particles[i] = spawn(false);
            particles[i].x = rand(0, W + 60);
            continue;
          }
          drawRain(p);
        } else if (variant === "snow") {
          p.y += p.vy * dt;
          var sxOff = Math.sin(t * p.swayFreq + p.swayPhase) * p.swayAmp;
          if (p.y > H + 10) { particles[i] = spawn(false); continue; }
          var ox = p.x; p.x = ox + sxOff;
          drawSnow(p);
          p.x = ox;
        } else if (variant === "clouds") {
          p.x += p.vx * dt;
          if (p.x - p.size * 1.3 > W + 40) { particles[i] = spawn(false); continue; }
          drawCloud(p);
        } else if (variant === "fog") {
          p.x += p.vx * dt;
          if (p.x - p.w / 2 > W + 40) { particles[i] = spawn(false); continue; }
          drawFog(p);
        } else if (variant === "motes") {
          p.x += p.vx * dt;
          p.y += p.vy * dt;
          if (p.y > H + 10 || p.x < -10 || p.x > W + 10) {
            particles[i] = spawn(false);
            continue;
          }
          drawMote(p, t);
        } else {
          // Drifting variants: petals, jacaranda, leaves.
          p.y += p.vy * dt;
          p.rot += p.vrot * dt;
          var sway = Math.sin(t * p.swayFreq + p.swayPhase) * p.swayAmp;
          var origX = p.x;
          p.x = origX + sway;
          if (p.y > H + 30) {
            particles[i] = spawn(false);
            continue;
          }
          if (variant === "jacaranda" || variant === "petals") drawPetals(p);
          else drawLeaf(p);
          p.x = origX;
        }
      }
      raf = requestAnimationFrame(step);
    }

    function start() {
      if (raf) return;
      last = performance.now();
      raf = requestAnimationFrame(step);
    }
    function stop() {
      if (raf) cancelAnimationFrame(raf);
      raf = 0;
      ctx.clearRect(0, 0, W, H);
    }

    // Build the particle pool for a variant. The controller decides whether to
    // start rendering afterwards.
    function set(v, inten) {
      variant = aliases[v] || v || null;
      intensity = inten || "heavy";
      flash = 0; nextBolt = rand(1.5, 4);
      particles = [];
      if (variant === "clear") { buildStars(); }
      if (!variant) { stop(); return; }
      var N = Math.round((counts[variant] || 0) * (mults[intensity] || 1));
      for (var i = 0; i < N; i++) particles.push(spawn(true));
    }

    return { name: "canvas2d", set: set, start: start, stop: stop };
  };
})();
