/*!
 * game.js — Game loop, canvas rendering, level lifecycle, autosave.
 */
(function (global) {
  'use strict';
  const LAB = (global.LAB = global.LAB || {});
  const SAVE_KEY = 'labyrinthGame.save.v1';

  function defaultSettings() {
    return { theme: 'light', volume: 0.7, muted: false, sensitivity: 1.0, control: 'joystick' };
  }
  function loadSave() {
    try {
      const raw = localStorage.getItem(SAVE_KEY);
      if (!raw) throw new Error('no save');
      const parsed = JSON.parse(raw);
      return Object.assign({ unlockedLevel: 1, lastPlayed: 1, bestTimes: {}, settings: defaultSettings() }, parsed, {
        settings: Object.assign(defaultSettings(), parsed.settings || {})
      });
    } catch (e) {
      return { unlockedLevel: 1, lastPlayed: 1, bestTimes: {}, settings: defaultSettings() };
    }
  }

  function Game(canvas) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.save = loadSave();
    this.currentId = 1;
    this.level = null;
    this.ball = null;
    this.checkpoint = null;
    this.tilt = { x: 0, y: 0 };
    this.state = 'playing'; // playing | paused | complete | failed
    this.elapsed = 0;
    this.lastTime = 0;
    this.viewW = 0; this.viewH = 0;
    this.woodTexture = document.createElement('canvas');
    this.onLevelComplete = null;
    this.onFail = null;
    this.onCheckpoint = null;
    window.addEventListener('resize', () => this.resize());
  }

  Game.prototype.persist = function () {
    try { localStorage.setItem(SAVE_KEY, JSON.stringify(this.save)); } catch (e) { /* storage unavailable (private mode, quota, etc.) */ }
  };

  Game.prototype.resetProgress = function () {
    this.save = { unlockedLevel: 1, lastPlayed: 1, bestTimes: {}, settings: this.save.settings };
    this.persist();
    this.loadLevel(1);
  };

  Game.prototype.resize = function () {
    const parent = this.canvas.parentElement;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    const w = parent.clientWidth, h = parent.clientHeight;
    this.canvas.width = Math.max(1, Math.round(w * dpr));
    this.canvas.height = Math.max(1, Math.round(h * dpr));
    this.canvas.style.width = w + 'px';
    this.canvas.style.height = h + 'px';
    this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    this.viewW = w; this.viewH = h;
  };

  Game.prototype.loadLevel = function (id) {
    id = LAB.Physics.clamp(id, 1, LAB.Levels.TOTAL_LEVELS);
    this.currentId = id;
    this.save.lastPlayed = id;
    this.persist();
    this.level = LAB.Levels.generateLevel(id);
    this.ball = { x: this.level.start.x, y: this.level.start.y, vx: 0, vy: 0, radius: 12, _padCooldown: 0, _portalCooldown: 0 };
    this.checkpoint = { x: this.level.start.x, y: this.level.start.y };
    this.elapsed = 0;
    this.state = 'playing';
    this.bakeWoodTexture();
    LAB.Audio.startMusic();
  };

  Game.prototype.bakeWoodTexture = function () {
    const c = this.woodTexture;
    c.width = Math.max(1, this.level.pixelWidth);
    c.height = Math.max(1, this.level.pixelHeight);
    const ctx = c.getContext('2d');
    const rng = LAB.Levels._internal.mulberry32(this.currentId * 97 + 13);
    const dark = this.save.settings.theme === 'dark';
    const base = dark ? ['#3a2718', '#2a1c10'] : ['#d8b483', '#c9a06a'];
    const grad = ctx.createLinearGradient(0, 0, c.width, c.height);
    grad.addColorStop(0, base[0]); grad.addColorStop(1, base[1]);
    ctx.fillStyle = grad; ctx.fillRect(0, 0, c.width, c.height);
    ctx.globalAlpha = dark ? 0.10 : 0.16;
    for (let i = 0; i < c.width * 0.5; i++) {
      const y = rng() * c.height;
      ctx.strokeStyle = rng() < 0.5 ? '#000000' : '#ffffff';
      ctx.beginPath();
      let x = rng() * c.width;
      ctx.moveTo(x, y);
      for (let s = 0; s < 6; s++) { x += rng() * 40 - 20; ctx.lineTo(x, y + s * 3 + rng() * 6); }
      ctx.stroke();
    }
    ctx.globalAlpha = 1;
  };

  Game.prototype.setTilt = function (x, y) {
    const s = this.save.settings.sensitivity;
    this.tilt.x = LAB.Physics.clamp(x * s, -1, 1);
    this.tilt.y = LAB.Physics.clamp(y * s, -1, 1);
  };

  Game.prototype.tick = function (now) {
    requestAnimationFrame((t) => this.tick(t));
    if (!this.lastTime) this.lastTime = now;
    let dt = (now - this.lastTime) / 1000;
    this.lastTime = now;
    dt = Math.min(dt, 0.05);

    if (this.state === 'playing' && this.level) {
      this.elapsed += dt;
      const events = LAB.Physics.step(this.ball, this.level, dt, this.tilt.x, this.tilt.y, this.elapsed);
      LAB.Audio.setRolling(events.speed);
      if (events.impact > 60) LAB.Audio.playCollision(Math.min(1, events.impact / 500));
      if (events.checkpoint) { this.checkpoint = events.checkpoint; if (this.onCheckpoint) this.onCheckpoint(); }
      if (events.hazard) this.handleFail();
      else if (events.finished) this.handleComplete();
    } else {
      LAB.Audio.setRolling(0);
    }
    this.render();
  };

  Game.prototype.handleFail = function () {
    this.state = 'failed';
    LAB.Audio.playFail();
    if (this.onFail) this.onFail();
    const cp = this.checkpoint;
    setTimeout(() => {
      if (!this.ball) return;
      this.ball.x = cp.x; this.ball.y = cp.y; this.ball.vx = 0; this.ball.vy = 0;
      if (this.state === 'failed') this.state = 'playing';
    }, 650);
  };

  Game.prototype.handleComplete = function () {
    this.state = 'complete';
    LAB.Audio.playVictory();
    const time = this.elapsed;
    const prevBest = this.save.bestTimes[this.currentId];
    if (!prevBest || time < prevBest) this.save.bestTimes[this.currentId] = time;
    if (this.currentId + 1 > this.save.unlockedLevel) this.save.unlockedLevel = Math.min(this.currentId + 1, LAB.Levels.TOTAL_LEVELS);
    this.persist();
    const par = Math.max(4, this.level.parLength * 0.55);
    let stars = 1;
    if (time < par) stars = 3; else if (time < par * 1.6) stars = 2;
    if (this.onLevelComplete) this.onLevelComplete({ time, stars, id: this.currentId, isLast: this.currentId >= LAB.Levels.TOTAL_LEVELS });
  };

  Game.prototype.pause = function () { if (this.state === 'playing') this.state = 'paused'; };
  Game.prototype.resume = function () { if (this.state === 'paused') this.state = 'playing'; };
  Game.prototype.restart = function () { this.loadLevel(this.currentId); };
  Game.prototype.next = function () { if (this.currentId < this.save.unlockedLevel) this.loadLevel(this.currentId + 1); };
  Game.prototype.prev = function () { if (this.currentId > 1) this.loadLevel(this.currentId - 1); };

  Game.prototype.drawZone = function (ctx, z) {
    const fills = {
      ice: 'rgba(150,220,255,0.55)', sticky: 'rgba(90,60,30,0.55)',
      speedpad: 'rgba(255,210,90,0.5)', wind: 'rgba(210,255,235,0.22)'
    };
    if (fills[z.type]) { ctx.fillStyle = fills[z.type]; ctx.fillRect(z.x, z.y, z.w, z.h); }
    const cx = z.x + z.w / 2, cy = z.y + z.h / 2, r = Math.min(z.w, z.h) / 2;

    if (z.type === 'hole') {
      const g = ctx.createRadialGradient(cx, cy, 1, cx, cy, r);
      g.addColorStop(0, '#000000'); g.addColorStop(1, '#2b1a0e');
      ctx.fillStyle = g; ctx.beginPath(); ctx.arc(cx, cy, r, 0, 6.2832); ctx.fill();
    } else if (z.type === 'finish') {
      const g = ctx.createRadialGradient(cx, cy, 1, cx, cy, r);
      g.addColorStop(0, '#1c4a1c'); g.addColorStop(1, '#0a1a0a');
      ctx.fillStyle = g; ctx.beginPath(); ctx.arc(cx, cy, r, 0, 6.2832); ctx.fill();
      ctx.strokeStyle = '#7CFC00'; ctx.lineWidth = 2; ctx.stroke();
    } else if (z.type === 'spike') {
      ctx.fillStyle = '#c7ccd1';
      for (let i = 0; i < 3; i++) {
        const sx = z.x + (i + 0.5) * (z.w / 3);
        ctx.beginPath(); ctx.moveTo(sx, z.y); ctx.lineTo(sx - z.w / 6, z.y + z.h); ctx.lineTo(sx + z.w / 6, z.y + z.h); ctx.closePath(); ctx.fill();
      }
    } else if (z.type === 'checkpoint') {
      const on = z.triggered ? '#3ddc5a' : '#9aa0a6';
      ctx.strokeStyle = on; ctx.lineWidth = 3;
      ctx.beginPath(); ctx.moveTo(cx, cy - r); ctx.lineTo(cx, cy + r); ctx.stroke();
      ctx.fillStyle = on;
      ctx.beginPath(); ctx.moveTo(cx, cy - r); ctx.lineTo(cx + r, cy - r * 0.5); ctx.lineTo(cx, cy); ctx.closePath(); ctx.fill();
    } else if (z.type === 'switch') {
      ctx.fillStyle = z.triggered ? '#3ddc5a' : '#d64545';
      ctx.beginPath(); ctx.arc(cx, cy, r, 0, 6.2832); ctx.fill();
      ctx.strokeStyle = '#00000055'; ctx.lineWidth = 2; ctx.stroke();
    } else if (z.type === 'key') {
      if (!z.collected) {
        ctx.fillStyle = '#f4c542';
        ctx.beginPath(); ctx.arc(cx - r * 0.3, cy, r * 0.55, 0, 6.2832); ctx.fill();
        ctx.fillRect(cx, cy - r * 0.15, r * 1.1, r * 0.3);
      }
    } else if (z.type === 'portal') {
      ctx.strokeStyle = z.color === 'a' ? '#7b5cff' : '#ff5ca0'; ctx.lineWidth = 3;
      ctx.beginPath(); ctx.arc(cx, cy, r, 0, 6.2832); ctx.stroke();
      ctx.beginPath(); ctx.arc(cx, cy, r * 0.5, 0, 6.2832); ctx.stroke();
    } else if (z.type === 'wind') {
      ctx.strokeStyle = 'rgba(255,255,255,0.55)'; ctx.lineWidth = 2;
      for (let i = 0; i < 3; i++) {
        const yy = z.y + (i + 1) * z.h / 4;
        ctx.beginPath(); ctx.moveTo(z.x + z.w * 0.2, yy); ctx.lineTo(z.x + z.w * 0.8, yy); ctx.stroke();
      }
    }
  };

  Game.prototype.render = function () {
    const ctx = this.ctx, lvl = this.level;
    ctx.clearRect(0, 0, this.viewW, this.viewH);
    if (!lvl) return;
    const scale = Math.min(this.viewW / lvl.pixelWidth, this.viewH / lvl.pixelHeight) * 0.94;
    const ox = (this.viewW - lvl.pixelWidth * scale) / 2;
    const oy = (this.viewH - lvl.pixelHeight * scale) / 2;
    ctx.save();
    ctx.translate(ox, oy);
    ctx.scale(scale, scale);

    ctx.drawImage(this.woodTexture, 0, 0);
    lvl.zones.forEach(z => this.drawZone(ctx, z));

    const dark = this.save.settings.theme === 'dark';
    const wallColor = dark ? '#150e08' : '#6b4a2c';
    Object.keys(lvl.wallBuckets).forEach(k => {
      lvl.wallBuckets[k].forEach(w => {
        const gate = w.gateId ? lvl.gates[w.gateId] : null;
        if (gate && gate.open) return;
        ctx.fillStyle = gate ? (gate.isDoor ? '#4a3a8a' : '#8a5a2b') : wallColor;
        ctx.fillRect(w.x, w.y, w.w, w.h);
        if (gate) { ctx.strokeStyle = '#e0a84a'; ctx.lineWidth = 1.5; ctx.strokeRect(w.x, w.y, w.w, w.h); }
      });
    });

    lvl.movingObstacles.forEach(ob => {
      const g = ctx.createRadialGradient(ob.x - 4, ob.y - 4, 1, ob.x, ob.y, ob.radius);
      g.addColorStop(0, '#ffdca8'); g.addColorStop(1, '#a8461d');
      ctx.fillStyle = g; ctx.beginPath(); ctx.arc(ob.x, ob.y, ob.radius, 0, 6.2832); ctx.fill();
    });

    lvl.rotatingBars.forEach(bar => {
      ctx.save(); ctx.translate(bar.cx, bar.cy); ctx.rotate(bar.angle);
      const g = ctx.createLinearGradient(-bar.length / 2, 0, bar.length / 2, 0);
      g.addColorStop(0, '#8d99a8'); g.addColorStop(0.5, '#eef2f5'); g.addColorStop(1, '#8d99a8');
      ctx.fillStyle = g;
      ctx.fillRect(-bar.length / 2, -bar.thickness / 2, bar.length, bar.thickness);
      ctx.restore();
    });

    // ball shadow + metallic ball
    ctx.beginPath();
    ctx.ellipse(this.ball.x, this.ball.y + this.ball.radius * 0.5, this.ball.radius * 0.9, this.ball.radius * 0.4, 0, 0, 6.2832);
    ctx.fillStyle = 'rgba(0,0,0,0.28)'; ctx.fill();

    const bg = ctx.createRadialGradient(
      this.ball.x - this.ball.radius * 0.4, this.ball.y - this.ball.radius * 0.4, 1,
      this.ball.x, this.ball.y, this.ball.radius * 1.15
    );
    bg.addColorStop(0, '#ffffff'); bg.addColorStop(0.35, '#c9d3da'); bg.addColorStop(1, '#54606b');
    ctx.beginPath(); ctx.arc(this.ball.x, this.ball.y, this.ball.radius, 0, 6.2832);
    ctx.fillStyle = bg; ctx.fill();

    ctx.restore();
  };

  Game.prototype.start = function () {
    this.resize();
    this.loadLevel(this.save.lastPlayed || 1);
    requestAnimationFrame((t) => this.tick(t));
  };

  LAB.Game = Game;
})(typeof window !== 'undefined' ? window : globalThis);
