/*
  game.js — Save Pet
  ---------------------------------------------------------------
  The core engine: canvas lifecycle, pointer-drawn ink lines,
  bee spawning/steering, line & pet collision, particles, scoring,
  and the render loop. UI screens/modals live in ui.js and talk to
  this module through the small SP.UI.on*() hooks called below.
*/
window.SP = window.SP || {};

SP.Game = (function () {
  "use strict";
  const U = SP.Utils, CFG = SP.Config, EN = SP.Entities, PA = SP.Particles;

  let canvas, ctx, raf = null, lastTime = 0;
  let starField = null;

  const state = {
    status: "idle", mode: "level", level: null,
    width: 0, height: 0, dpr: 1, scale: 1,
    pet: null, hives: [], bees: [],
    lines: [], currentDraw: null, drawing: false,
    ink: 0, inkBudget: 0, inkRegen: 0,
    timeRemaining: 0, elapsed: 0,
    hitsTaken: 0, beesPoppedThisRun: 0, hintUsedThisRun: false,
    spawnTimer: 0.9, shake: { time: 0, duration: 0, mag: 0 },
    particles: [], hintLine: null, hintTimer: 0, windMag: 10,
    _lastTickSec: -1
  };

  function rand(min, max) { return min + Math.random() * (max - min); }
  function hexToRgb(hex) {
    hex = hex.replace("#", "");
    if (hex.length === 3) hex = hex.split("").map(function (c) { return c + c; }).join("");
    const num = parseInt(hex, 16);
    return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255 };
  }
  function hexAlpha(hex, alpha) {
    const c = hexToRgb(hex);
    return "rgba(" + c.r + "," + c.g + "," + c.b + "," + alpha + ")";
  }

  /* ---------------- setup ---------------- */
  function init(canvasEl) {
    canvas = canvasEl;
    ctx = canvas.getContext("2d");
    canvas.style.touchAction = "none";
    U.on(canvas, "pointerdown", onPointerDown);
    U.on(canvas, "pointermove", onPointerMove);
    U.on(window, "pointerup", onPointerUp);
    U.on(window, "pointercancel", onPointerUp);
    U.on(window, "resize", onResize);
    U.on(window, "orientationchange", function () { setTimeout(onResize, 60); });
    onResize();
    render();
  }

  function onResize() {
    if (!canvas) return;
    const parent = canvas.parentElement;
    const rect = parent.getBoundingClientRect();
    if (rect.width < 2 || rect.height < 2) return;
    const oldW = state.width, oldH = state.height;
    const dpr = window.devicePixelRatio || 1;
    canvas.width = Math.round(rect.width * dpr);
    canvas.height = Math.round(rect.height * dpr);
    canvas.style.width = rect.width + "px";
    canvas.style.height = rect.height + "px";
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    state.width = rect.width; state.height = rect.height; state.dpr = dpr;
    state.scale = state.height / 800;
    starField = null;
    if (oldW > 0 && oldH > 0 && (state.status === "playing" || state.status === "paused") && state.pet) {
      rescaleAll(oldW, oldH, state.width, state.height);
    }
    render();
  }

  function rescaleAll(oldW, oldH, newW, newH) {
    const sx = newW / oldW, sy = newH / oldH;
    if (!isFinite(sx) || !isFinite(sy) || sx <= 0 || sy <= 0) return;
    const savg = (sx + sy) / 2;
    state.pet.x *= sx; state.pet.y *= sy; state.pet.radius *= savg;
    state.hives.forEach(function (h) { h.x *= sx; h.y *= sy; h.radius *= savg; });
    state.bees.forEach(function (b) { b.x *= sx; b.y *= sy; b.radius *= savg; });
    state.lines.forEach(function (l) { l.points.forEach(function (p) { p.x *= sx; p.y *= sy; }); });
    if (state.currentDraw) state.currentDraw.points.forEach(function (p) { p.x *= sx; p.y *= sy; });
    state.inkBudget *= savg; state.ink *= savg; state.inkRegen *= savg;
  }

  /* ---------------- input: drawing ink lines ---------------- */
  function getPos(e) {
    const rect = canvas.getBoundingClientRect();
    return { x: e.clientX - rect.left, y: e.clientY - rect.top };
  }

  function onPointerDown(e) {
    if (state.status !== "playing") return;
    if (state.ink <= 0.5) {
      if (SP.UI) SP.UI.showToast("Out of ink!");
      SP.Audio.play("error");
      return;
    }
    e.preventDefault();
    state.drawing = true;
    state.currentDraw = { points: [getPos(e)] };
  }

  function onPointerMove(e) {
    if (!state.drawing || !state.currentDraw) return;
    const p = getPos(e);
    const pts = state.currentDraw.points;
    const last = pts[pts.length - 1];
    const d = U.dist(p.x, p.y, last.x, last.y);
    if (d < CFG.MIN_DRAW_POINT_DISTANCE) return;
    if (state.ink - d <= 0) {
      const ratio = Math.max(0, state.ink) / d;
      pts.push({ x: last.x + (p.x - last.x) * ratio, y: last.y + (p.y - last.y) * ratio });
      state.ink = 0;
      finalizeDraw();
      if (SP.UI) SP.UI.updateInk(0, state.inkBudget);
      return;
    }
    state.ink -= d;
    pts.push(p);
    if (SP.UI) SP.UI.updateInk(state.ink, state.inkBudget);
  }

  function onPointerUp() {
    if (!state.drawing) return;
    finalizeDraw();
  }

  function finalizeDraw() {
    state.drawing = false;
    if (state.currentDraw && state.currentDraw.points.length >= 2) {
      state.lines.push(state.currentDraw);
      SP.Audio.play("whoosh");
    }
    state.currentDraw = null;
  }

  function lineLength(line) {
    let total = 0;
    for (let i = 0; i < line.points.length - 1; i++) {
      total += U.dist(line.points[i].x, line.points[i].y, line.points[i + 1].x, line.points[i + 1].y);
    }
    return total;
  }

  function undoLastLine() {
    if (state.status !== "playing" || state.lines.length === 0) return;
    const line = state.lines.pop();
    state.ink = Math.min(state.inkBudget, state.ink + lineLength(line));
    if (SP.UI) SP.UI.updateInk(state.ink, state.inkBudget);
    SP.Audio.play("click");
  }

  /* ---------------- level lifecycle ---------------- */
  function startLevel(levelConfig, mode) {
    state.mode = mode || "level";
    state.level = levelConfig;
    state.status = "playing";
    state.elapsed = 0; state.hitsTaken = 0; state.beesPoppedThisRun = 0; state.hintUsedThisRun = false;
    state.lines = []; state.currentDraw = null; state.drawing = false;
    state.particles = []; state.hintLine = null; state.hintTimer = 0;
    state.shake = { time: 0, duration: 0, mag: 0 };
    state.windMag = levelConfig.windEnabled ? 22 : 10;
    state._lastTickSec = -1;
    state.bees = [];
    state.spawnTimer = 1.0;

    const petR = 46 * state.scale;
    state.pet = EN.createPet(levelConfig.petPos.x * state.width, levelConfig.petPos.y * state.height, petR);
    state.hives = levelConfig.hives.map(function (h) {
      return EN.createHive(h.x * state.width, h.y * state.height, 34 * state.scale);
    });

    state.inkBudget = levelConfig.inkBudgetRef * state.scale;
    state.ink = state.inkBudget;
    state.inkRegen = (levelConfig.inkRegenPerSecond || 0) * state.scale;
    state.timeRemaining = levelConfig.surviveTime || 0;

    if (SP.UI) SP.UI.onLevelStart(levelConfig, state.mode);
    if (SP.Analytics) SP.Analytics.track("level_start", { level_id: String(levelConfig.id), mode: state.mode });
    SP.Audio.playMusic("game");
    startLoop();
  }

  function pause() {
    if (state.status !== "playing") return;
    state.status = "paused";
    stopLoop();
    render();
  }
  function resume() {
    if (state.status !== "paused") return;
    state.status = "playing";
    lastTime = performance.now();
    startLoop();
  }
  function restart() {
    if (!state.level) return;
    startLevel(state.level, state.mode);
  }
  function quitToMenu() {
    state.status = "idle";
    stopLoop();
    SP.Audio.playMusic("menu");
  }

  function skipCurrentLevel() {
    if (!state.level || state.mode !== "level") return;
    const data = SP.Storage.data;
    const key = String(state.level.id);
    const prev = data.levels[key] || { stars: 0, completed: false, hintUsed: false };
    data.levels[key] = { stars: Math.max(prev.stars, 1), completed: true, hintUsed: prev.hintUsed };
    if (state.level.id === data.unlockedLevel && state.level.id < CFG.TOTAL_LEVELS) {
      data.unlockedLevel = state.level.id + 1;
    }
    SP.Storage.save();
    stopLoop();
    state.status = "idle";
    SP.Achievements.check();
    if (SP.UI) SP.UI.onLevelSkipped(state.level.id);
  }

  function useHint() {
    if (state.status !== "playing" || !state.hives.length) return false;
    const data = SP.Storage.data;
    if (data.coins < CFG.HINT_COST) {
      if (SP.UI) SP.UI.showToast("Not enough coins for a hint");
      SP.Audio.play("error");
      return false;
    }
    data.coins -= CFG.HINT_COST;
    SP.Storage.save();
    if (SP.UI) SP.UI.updateCoins(data.coins);
    state.hintUsedThisRun = true;

    let best = null, bestD = Infinity;
    state.hives.forEach(function (h) {
      const d = U.dist(h.x, h.y, state.pet.x, state.pet.y);
      if (d < bestD) { bestD = d; best = h; }
    });
    const dx = state.pet.x - best.x, dy = state.pet.y - best.y;
    const d = Math.hypot(dx, dy) || 1;
    const ux = dx / d, uy = dy / d, px = -uy, py = ux;
    const midX = best.x + dx * 0.55, midY = best.y + dy * 0.55;
    const halfLen = Math.min(state.width, state.height) * 0.16;
    state.hintLine = { x1: midX - px * halfLen, y1: midY - py * halfLen, x2: midX + px * halfLen, y2: midY + py * halfLen };
    state.hintTimer = 3;
    SP.Audio.play("unlock");
    return true;
  }

  /* ---------------- main loop ---------------- */
  function startLoop() {
    if (raf) cancelAnimationFrame(raf);
    lastTime = performance.now();
    raf = requestAnimationFrame(loop);
  }
  function stopLoop() {
    if (raf) cancelAnimationFrame(raf);
    raf = null;
  }
  function loop(now) {
    raf = requestAnimationFrame(loop);
    let dt = (now - lastTime) / 1000;
    lastTime = now;
    dt = Math.min(dt, 0.05);
    if (state.status === "playing") update(dt);
    render();
  }

  function currentSpawnInterval() {
    const lv = state.level;
    if (state.mode === "endless") {
      const k = Math.min(1, state.elapsed / 180);
      return U.lerp(lv.spawnIntervalMax, 0.45, k) * rand(0.85, 1.15);
    }
    return rand(lv.spawnIntervalMin, lv.spawnIntervalMax);
  }

  function spawnBee(hive, lv) {
    let type = "normal";
    const roll = Math.random();
    if (roll < (lv.tankChance || 0)) type = "tank";
    else if (roll < (lv.tankChance || 0) + (lv.fastChance || 0)) type = "fast";

    let speedFrac;
    if (state.mode === "endless") {
      const k = Math.min(1, state.elapsed / 180);
      speedFrac = U.lerp(lv.beeSpeedMin, lv.beeSpeedMax + 0.10, k);
    } else {
      speedFrac = rand(lv.beeSpeedMin, lv.beeSpeedMax);
    }
    let speedPx = speedFrac * state.height;
    if (type === "fast") speedPx *= 1.5;
    if (type === "tank") speedPx *= 0.72;

    const bee = EN.createBee(hive.x + rand(-6, 6) * state.scale, hive.y + rand(-6, 6) * state.scale,
      { type: type, speed: speedPx, scale: state.scale });
    state.bees.push(bee);
  }

  function spawnWave() {
    const lv = state.level;
    if (!state.hives.length) return;
    const hive = state.hives[Math.floor(Math.random() * state.hives.length)];
    const burst = Math.random() < (lv.burstChance || 0);
    const count = burst ? 2 + Math.floor(Math.random() * 2) : 1;
    for (let i = 0; i < count; i++) spawnBee(hive, lv);
  }

  function updateBeesAndGetAlert(dt) {
    let nearest = Infinity;
    state.bees.forEach(function (b) {
      EN.updateBee(b, dt, state.pet.x, state.pet.y, state.windMag, state.elapsed);
      const d = U.dist(b.x, b.y, state.pet.x, state.pet.y);
      if (d < nearest) nearest = d;
    });
    const threshold = 170 * state.scale;
    return nearest < Infinity ? U.clamp(1 - nearest / threshold, 0, 1) : 0;
  }

  function onBeeHitLine(bee) {
    bee.hp -= 1;
    if (bee.hp <= 0) {
      bee.dead = true;
      state.beesPoppedThisRun++;
      SP.Storage.data.stats.totalBeesPopped++;
      const isTank = bee.type === "tank";
      PA.spawnBurst(state.particles, bee.x, bee.y, {
        count: isTank ? 16 : 10, color: ["#FFD54F", "#FFF3C4", "#FFC93C"],
        speed: isTank ? 170 : 130, life: 0.5, size: isTank ? 5 : 4
      });
      SP.Audio.play(isTank ? "popTank" : "pop");
      U.vibrate(12);
      if ((state.mode === "endless" || state.mode === "random") && Math.random() < 0.12) {
        addCoins(1);
      }
    } else {
      bee.hitFlash = 0.15;
      SP.Audio.play("pop");
    }
  }

  function onBeeHitPet(bee) {
    bee.dead = true;
    state.hitsTaken++;
    EN.hitPet(state.pet);
    state.shake = { time: 0.28, duration: 0.28, mag: 10 * state.scale };
    PA.spawnBurst(state.particles, bee.x, bee.y, {
      count: 6, color: "#FF6B9D", speed: 90, life: 0.5, size: 5, shape: "heart", gravity: 60, upBias: 60
    });
    SP.Audio.play("hit");
    U.vibrate([0, 40, 30, 40]);
    if (SP.UI) SP.UI.onHit(state.pet.health);
  }

  function handleCollisions() {
    const thickness = CFG.LINE_THICKNESS * state.scale;
    state.bees.forEach(function (bee) {
      if (bee.dead) return;
      for (let li = 0; li < state.lines.length; li++) {
        const pts = state.lines[li].points;
        let hit = false;
        for (let i = 0; i < pts.length - 1; i++) {
          const d = U.distToSegment(bee.x, bee.y, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y);
          if (d <= bee.radius + thickness / 2) { hit = true; break; }
        }
        if (hit) { onBeeHitLine(bee); break; }
      }
    });
    state.bees.forEach(function (bee) {
      if (bee.dead) return;
      const d = U.dist(bee.x, bee.y, state.pet.x, state.pet.y);
      if (d <= bee.radius + state.pet.radius * 0.8) onBeeHitPet(bee);
    });
  }

  function addCoins(n) {
    SP.Storage.data.coins += n;
    SP.Storage.data.stats.totalCoinsEarnedLifetime += n;
    SP.Storage.save();
    if (SP.UI) SP.UI.updateCoins(SP.Storage.data.coins);
  }

  function starsFromHits(hits) {
    if (hits <= 0) return 3;
    if (hits === 1) return 2;
    return 1;
  }

  function applyLevelProgress(id, stars, won) {
    const data = SP.Storage.data;
    const key = String(id);
    const prev = data.levels[key] || { stars: 0, completed: false, hintUsed: false };
    const firstClear = won && !prev.completed;
    let coinsEarned = 0;
    if (won) {
      coinsEarned = stars * CFG.COINS_PER_STAR + (firstClear ? CFG.FIRST_CLEAR_BONUS : 0);
      data.levels[key] = {
        stars: Math.max(stars, prev.stars),
        completed: true,
        hintUsed: prev.hintUsed || state.hintUsedThisRun
      };
      if (id === data.unlockedLevel && id < CFG.TOTAL_LEVELS) data.unlockedLevel = id + 1;
    }
    if (coinsEarned > 0) addCoins(coinsEarned); else SP.Storage.save();
    if (SP.UI) SP.UI.setLastResult({ coinsEarned: coinsEarned, stars: stars, firstClear: firstClear });
  }

  function applyDailyProgress(dateStr) {
    const data = SP.Storage.data, daily = data.daily;
    if (daily.lastCompletedDate === dateStr) return;
    const yesterday = U.todayKey(new Date(Date.now() - 86400000));
    daily.streakCount = daily.lastCompletedDate === yesterday ? daily.streakCount + 1 : 1;
    daily.lastCompletedDate = dateStr;
    daily.totalCompleted = (daily.totalCompleted || 0) + 1;
    const bonus = 40 + Math.min(60, daily.streakCount * 5);
    addCoins(bonus);
    if (SP.UI) SP.UI.setLastResult({ coinsEarned: bonus, dailyStreak: daily.streakCount });
  }

  function applyEndlessProgress(seconds, popped) {
    const data = SP.Storage.data;
    const improved = seconds > data.endless.bestTimeSeconds;
    if (improved) data.endless.bestTimeSeconds = seconds;
    if (popped > data.endless.bestBeesPopped) data.endless.bestBeesPopped = popped;
    const coinsEarned = Math.floor(seconds * 0.6) + popped;
    addCoins(coinsEarned);
    if (SP.UI) SP.UI.setLastResult({ coinsEarned: coinsEarned, best: data.endless.bestTimeSeconds, improved: improved, seconds: seconds });
  }

  function applyRandomProgress(seconds) {
    const data = SP.Storage.data;
    const improved = seconds > data.random.bestTimeSeconds;
    if (improved) data.random.bestTimeSeconds = seconds;
    const coinsEarned = Math.floor(seconds * 0.8);
    addCoins(coinsEarned);
    if (SP.UI) SP.UI.setLastResult({ coinsEarned: coinsEarned, best: data.random.bestTimeSeconds, improved: improved, seconds: seconds });
  }

  function finalizeRunResult(won) {
    const result = { mode: state.mode, won: won, hitsTaken: state.hitsTaken, beesPopped: state.beesPoppedThisRun, elapsed: state.elapsed, level: state.level };
    if (state.mode === "level") {
      result.stars = won ? starsFromHits(state.hitsTaken) : 0;
      applyLevelProgress(state.level.id, result.stars, won);
    } else if (state.mode === "daily") {
      result.stars = won ? starsFromHits(state.hitsTaken) : 0;
      if (won) applyDailyProgress(state.level.dateStr);
    } else if (state.mode === "endless") {
      applyEndlessProgress(state.elapsed, state.beesPoppedThisRun);
    } else if (state.mode === "random") {
      applyRandomProgress(state.elapsed);
    }
    if (SP.UI) SP.UI.onRunEnd(result);
    if (SP.Analytics) {
      SP.Analytics.track(won ? "level_complete" : "level_failed", {
        level_id: String(state.level ? state.level.id : "unknown"), mode: state.mode,
        stars: result.stars || 0, hits: state.hitsTaken, duration: Math.round(state.elapsed)
      });
    }
    SP.Achievements.check();
  }

  function endRun(won) {
    state.status = won ? "won" : "lost";
    if (won) {
      SP.Audio.play("win");
      state.pet.happyTimer = 1.2;
      PA.spawnConfetti(state.particles, state.width / 2, state.height * 0.35, 30);
      U.vibrate([0, 30, 40, 30, 40, 60]);
    } else {
      SP.Audio.play("lose");
      U.vibrate([0, 60, 50, 60, 50, 80]);
    }
    setTimeout(function () { finalizeRunResult(won); }, won ? 900 : 700);
  }

  function update(dt) {
    state.elapsed += dt;
    if (state.mode !== "endless") state.timeRemaining -= dt;
    if (state.inkRegen > 0) state.ink = Math.min(state.inkBudget, state.ink + state.inkRegen * dt);

    state.spawnTimer -= dt;
    if (state.spawnTimer <= 0) { spawnWave(); state.spawnTimer = currentSpawnInterval(); }

    const alert = updateBeesAndGetAlert(dt);
    EN.updatePet(state.pet, dt, alert);
    handleCollisions();
    state.bees = state.bees.filter(function (b) { return !b.dead; });
    PA.update(state.particles, dt);

    if (state.hintTimer > 0) { state.hintTimer -= dt; if (state.hintTimer <= 0) state.hintLine = null; }
    if (state.shake.time > 0) state.shake.time -= dt;

    if (state.mode !== "endless") {
      const tr = state.timeRemaining;
      if (tr <= 3.05 && tr > 0 && Math.floor(tr) !== state._lastTickSec) {
        state._lastTickSec = Math.floor(tr);
        SP.Audio.play("tick");
      }
    }

    if (state.pet.health <= 0) { endRun(false); return; }
    if (state.mode !== "endless" && state.timeRemaining <= 0) { endRun(true); return; }

    if (SP.UI) SP.UI.onTick(state);
  }

  /* ---------------- rendering ---------------- */
  function drawStars(w, h) {
    if (!starField) {
      starField = { pts: [] };
      for (let i = 0; i < 60; i++) {
        starField.pts.push({ x: Math.random() * w, y: Math.random() * h * 0.7, r: Math.random() * 1.8 + 0.5, phase: Math.random() * Math.PI * 2 });
      }
    }
    ctx.fillStyle = "#FFFFFF";
    starField.pts.forEach(function (p) {
      const tw = 0.5 + 0.5 * Math.sin(state.elapsed * 2 + p.phase);
      ctx.globalAlpha = 0.4 + 0.6 * tw;
      ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fill();
    });
    ctx.globalAlpha = 1;
    ctx.fillStyle = "rgba(255,250,230,0.9)";
    ctx.beginPath(); ctx.arc(w * 0.82, h * 0.16, 28 * state.scale, 0, Math.PI * 2); ctx.fill();
  }

  function drawSparkles(w, h, accent) {
    ctx.fillStyle = accent;
    for (let i = 0; i < 20; i++) {
      const x = (i * 97) % w, y = ((i * 53) % (h * 0.7));
      const tw = 0.4 + 0.6 * Math.abs(Math.sin(state.elapsed * 1.5 + i));
      ctx.globalAlpha = 0.18 * tw;
      ctx.beginPath(); ctx.arc(x, y, 3 * state.scale, 0, Math.PI * 2); ctx.fill();
    }
    ctx.globalAlpha = 1;
  }

  function drawFlowerDots(w, groundY, accent) {
    ctx.fillStyle = accent;
    ctx.globalAlpha = 0.7;
    for (let i = 0; i < 10; i++) {
      const x = (i * 137 + 40) % w;
      ctx.beginPath(); ctx.arc(x, groundY + 24 * state.scale, 3.5 * state.scale, 0, Math.PI * 2); ctx.fill();
    }
    ctx.globalAlpha = 1;
  }

  function drawWaveLines(w, groundY) {
    ctx.strokeStyle = "rgba(255,255,255,0.5)";
    ctx.lineWidth = 2 * state.scale;
    for (let row = 0; row < 2; row++) {
      ctx.beginPath();
      const y = groundY + 22 * state.scale + row * 14 * state.scale;
      const waveStep = Math.max(1, 14 * state.scale);
      for (let x = 0; x <= w; x += waveStep) {
        ctx.lineTo(x, y + Math.sin(x / 40 + state.elapsed * 1.5 + row) * 3 * state.scale);
      }
      ctx.stroke();
    }
  }

  function drawBackground() {
    const lv = state.level;
    const w = state.width, h = state.height;
    const sky = lv ? lv.sky : ["#BEEBFF", "#EAFBEF"];
    const ground = lv ? lv.ground : "#8FE39A";
    const accent = lv ? lv.accent : "#FFD54F";
    const theme = lv ? lv.theme : "hills";

    const grad = ctx.createLinearGradient(0, 0, 0, h);
    grad.addColorStop(0, sky[0]); grad.addColorStop(1, sky[1]);
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, w, h);

    if (theme === "night") drawStars(w, h);
    else if (theme === "golden") drawSparkles(w, h, accent);

    const groundY = h * 0.86;
    ctx.fillStyle = hexAlpha(ground, theme === "night" ? 0.92 : 1);
    ctx.beginPath();
    ctx.moveTo(0, h);
    ctx.lineTo(0, groundY + 18 * state.scale);
    const hillStep = Math.max(1, w / 8);
    for (let x = 0; x <= w; x += hillStep) {
      const wobble = Math.sin((x / w) * Math.PI * 2 + 1.3) * 10 * state.scale;
      ctx.lineTo(x, groundY + wobble);
    }
    ctx.lineTo(w, h);
    ctx.closePath();
    ctx.fill();

    if (theme === "hills") drawFlowerDots(w, groundY, accent);
    else if (theme === "open") drawWaveLines(w, groundY);
  }

  function strokePath(points, style, width) {
    if (points.length < 2) return;
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);
    for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
    ctx.strokeStyle = style;
    ctx.lineWidth = width;
    ctx.stroke();
  }

  function drawLines() {
    const thickness = CFG.LINE_THICKNESS * state.scale;
    ctx.lineCap = "round"; ctx.lineJoin = "round";
    state.lines.forEach(function (line) {
      strokePath(line.points, "rgba(78,205,196,0.45)", thickness * 1.7);
      strokePath(line.points, "#2FBFB3", thickness);
      strokePath(line.points, "rgba(255,255,255,0.9)", thickness * 0.4);
    });
    if (state.currentDraw) {
      strokePath(state.currentDraw.points, "rgba(78,205,196,0.35)", thickness * 1.7);
      strokePath(state.currentDraw.points, "rgba(47,191,179,0.85)", thickness);
    }
  }

  function drawHintLine() {
    ctx.save();
    ctx.globalAlpha = 0.5 + 0.3 * Math.sin(state.elapsed * 8);
    ctx.setLineDash([10 * state.scale, 8 * state.scale]);
    ctx.strokeStyle = "#FFD54F";
    ctx.lineWidth = CFG.LINE_THICKNESS * state.scale * 0.7;
    ctx.lineCap = "round";
    ctx.beginPath();
    ctx.moveTo(state.hintLine.x1, state.hintLine.y1);
    ctx.lineTo(state.hintLine.x2, state.hintLine.y2);
    ctx.stroke();
    ctx.restore();
  }

  function render() {
    if (!ctx) return;
    if (state.width <= 0 || state.height <= 0) return;
    ctx.save();
    if (state.shake.time > 0) {
      const k = state.shake.time / state.shake.duration;
      ctx.translate((Math.random() - 0.5) * state.shake.mag * k, (Math.random() - 0.5) * state.shake.mag * k);
    }
    ctx.clearRect(-40, -40, state.width + 80, state.height + 80);
    drawBackground();
    state.hives.forEach(function (h) { EN.drawHive(ctx, h, state.elapsed); });
    drawLines();
    if (state.hintLine) drawHintLine();
    state.bees.forEach(function (b) { EN.drawBee(ctx, b); });
    PA.draw(ctx, state.particles);
    if (state.pet) EN.drawPet(ctx, state.pet);
    ctx.restore();
  }

  return {
    init: init, onResize: onResize, startLevel: startLevel,
    pause: pause, resume: resume, restart: restart, quitToMenu: quitToMenu,
    undoLastLine: undoLastLine, useHint: useHint, skipCurrentLevel: skipCurrentLevel,
    getState: function () { return state; }
  };
})();
