/* ============ DOOM-CLONE RAYCASTER — "HACK THE STACK" ============ */
/* global React, PrzelomAudio */

// Map legend: 0=empty, 1=wall, 2=glitched-wall, 3=server-rack, 4=door, 5=exit
const LEVEL_1 = [
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 0, 3, 0, 0, 0, 3, 0, 0, 1, 1, 0, 0, 3, 0, 0, 0, 3, 0, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1],
  [1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1],
  [1, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 3, 0, 1],
  [1, 0, 0, 0, 0, 0, 4, 0, 3, 0, 0, 3, 0, 0, 4, 0, 0, 0, 0, 1],
  [1, 0, 3, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 3, 0, 1],
  [1, 0, 0, 0, 0, 0, 1, 0, 3, 0, 0, 3, 0, 0, 1, 0, 0, 0, 0, 1],
  [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 0, 3, 3, 3, 0, 3, 3, 3, 0, 0, 3, 3, 3, 0, 3, 3, 3, 0, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 0, 3, 3, 3, 0, 3, 3, 3, 0, 0, 3, 3, 3, 0, 3, 3, 3, 0, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
];

const LEVEL_2 = [
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1],
  [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1],
  [1, 0, 1, 0, 3, 0, 0, 3, 0, 3, 0, 1, 0, 3, 3, 0, 1, 0, 3, 0, 0, 1, 0, 1],
  [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1, 0, 1],
  [1, 0, 1, 0, 3, 0, 0, 3, 0, 3, 0, 1, 0, 3, 3, 0, 1, 0, 0, 3, 0, 1, 0, 1],
  [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1],
  [1, 0, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 1, 1, 1, 0, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 0, 3, 3, 0, 3, 3, 0, 1, 0, 3, 3, 3, 0, 1, 0, 3, 3, 3, 0, 3, 3, 0, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 5, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 0, 3, 3, 0, 3, 3, 0, 1, 0, 3, 3, 3, 0, 1, 0, 3, 3, 3, 0, 3, 3, 0, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
];

// Enemy types
const ENEMY_TYPES = {
  bug: {
    name: "BUG",
    hp: 20,
    speed: 0.04,
    dmg: 8,
    ammoYield: 5,
    color: "#ff5577",
    shootRange: 0,
    glyph: "🐛",
    size: 0.4,
    points: 50,
  },
  agent: {
    name: "AI_AGENT",
    hp: 40,
    speed: 0.025,
    dmg: 14,
    ammoYield: 8,
    color: "#33ffe1",
    shootRange: 6,
    glyph: "⚙",
    size: 0.5,
    points: 120,
  },
  deadline: {
    name: "DEADLINE",
    hp: 60,
    speed: 0.06,
    dmg: 18,
    ammoYield: 0,
    color: "#ffcc33",
    shootRange: 0,
    glyph: "⏰",
    size: 0.55,
    points: 200,
  },
  legacycode: {
    name: "LEGACY_CODE",
    hp: 80,
    speed: 0.02,
    dmg: 22,
    ammoYield: 12,
    color: "#9966ff",
    shootRange: 8,
    glyph: "☠",
    size: 0.6,
    points: 300,
  },
};

// Pickups
const PICKUP_TYPES = {
  hp: { glyph: "❤", color: "#ff4466", label: "HP +25", value: 25 },
  ammo: { glyph: "◈", color: "#caff33", label: "AMMO +20", value: 20 },
  shield: { glyph: "◉", color: "#33ffe1", label: "SHIELD", value: 1 },
};

const LEVELS = [
  {
    map: LEVEL_1,
    name: "STACK_OVERFLOW",
    enemies: [
      { type: "bug", x: 5.5, y: 2.5 },
      { type: "bug", x: 14.5, y: 2.5 },
      { type: "bug", x: 5.5, y: 5.5 },
      { type: "bug", x: 14.5, y: 5.5 },
      { type: "agent", x: 9.5, y: 8.5 },
      { type: "agent", x: 11.5, y: 11.5 },
      { type: "bug", x: 3.5, y: 8.5 },
      { type: "bug", x: 16.5, y: 8.5 },
      { type: "deadline", x: 5.5, y: 14.5 },
      { type: "deadline", x: 14.5, y: 14.5 },
      { type: "agent", x: 10.5, y: 17.5 },
      { type: "bug", x: 6.5, y: 16.5 },
      { type: "bug", x: 13.5, y: 16.5 },
    ],
    pickups: [
      { type: "ammo", x: 9.5, y: 2.5 },
      { type: "ammo", x: 10.5, y: 2.5 },
      { type: "hp", x: 9.5, y: 11.5 },
      { type: "hp", x: 10.5, y: 11.5 },
      { type: "shield", x: 10.5, y: 10.5 },
      { type: "ammo", x: 2.5, y: 14.5 },
      { type: "ammo", x: 17.5, y: 14.5 },
    ],
    spawn: { x: 2.5, y: 2.5, dir: 0 },
  },
  {
    map: LEVEL_2,
    name: "PROD_INCIDENT",
    enemies: [
      { type: "bug", x: 4.5, y: 3.5 },
      { type: "bug", x: 8.5, y: 3.5 },
      { type: "bug", x: 13.5, y: 4.5 },
      { type: "agent", x: 18.5, y: 3.5 },
      { type: "agent", x: 5.5, y: 6.5 },
      { type: "agent", x: 14.5, y: 6.5 },
      { type: "deadline", x: 9.5, y: 9.5 },
      { type: "deadline", x: 15.5, y: 9.5 },
      { type: "legacycode", x: 11.5, y: 13.5 },
      { type: "legacycode", x: 18.5, y: 12.5 },
      { type: "agent", x: 3.5, y: 13.5 },
      { type: "bug", x: 6.5, y: 12.5 },
      { type: "bug", x: 21.5, y: 13.5 },
      { type: "deadline", x: 2.5, y: 15.5 },
      { type: "agent", x: 21.5, y: 15.5 },
    ],
    pickups: [
      { type: "ammo", x: 3.5, y: 1.5 },
      { type: "ammo", x: 20.5, y: 1.5 },
      { type: "hp", x: 11.5, y: 1.5 },
      { type: "shield", x: 12.5, y: 1.5 },
      { type: "ammo", x: 5.5, y: 9.5 },
      { type: "hp", x: 18.5, y: 9.5 },
      { type: "ammo", x: 9.5, y: 11.5 },
      { type: "hp", x: 14.5, y: 11.5 },
      { type: "shield", x: 11.5, y: 13.5 },
    ],
    spawn: { x: 1.5, y: 1.5, dir: 0 },
  },
];

function DoomApp({ onClose }) {
  const canvasRef = useRef(null);
  const minimapRef = useRef(null);
  const [phase, setPhase] = useState("menu"); // menu, playing, dead, won, intermission
  const [stats, setStats] = useState({
    hp: 100,
    ammo: 50,
    shield: 0,
    score: 0,
    kills: 0,
    level: 0,
  });
  const [hud, setHud] = useState({ msg: "", flash: 0, hurtFlash: 0 });
  const [paused, setPaused] = useState(false);
  const [difficulty, setDifficulty] = useState("normal");
  const stateRef = useRef(null);
  const keysRef = useRef({});
  const mouseRef = useRef({ dx: 0, captured: false });

  const startLevel = useCallback((levelIdx) => {
    const lvl = LEVELS[levelIdx];
    stateRef.current = {
      map: lvl.map.map((r) => [...r]),
      mapW: lvl.map[0].length,
      mapH: lvl.map.length,
      px: lvl.spawn.x,
      py: lvl.spawn.y,
      pa: lvl.spawn.dir,
      enemies: lvl.enemies.map((e) => ({
        ...ENEMY_TYPES[e.type],
        type: e.type,
        x: e.x,
        y: e.y,
        hp: ENEMY_TYPES[e.type].hp,
        alive: true,
        deathT: 0,
        shootCd: Math.random() * 2,
        hurt: 0,
      })),
      pickups: lvl.pickups.map((p) => ({
        ...PICKUP_TYPES[p.type],
        type: p.type,
        x: p.x,
        y: p.y,
        taken: false,
        bob: Math.random() * Math.PI * 2,
      })),
      bullets: [],
      muzzle: 0,
      level: levelIdx,
      levelName: lvl.name,
      time: 0,
      doorOpen: 0,
    };
    setStats((s) => ({ ...s, level: levelIdx }));
    setPhase("playing");
    setHud({
      msg: `LEVEL ${levelIdx + 1}: ${lvl.name}`,
      flash: 90,
      hurtFlash: 0,
    });
  }, []);

  const startGame = () => {
    setStats({ hp: 100, ammo: 50, shield: 0, score: 0, kills: 0, level: 0 });
    PrzelomAudio.sounds.boot();
    startLevel(0);
  };

  // ===== Input =====
  useEffect(() => {
    if (phase !== "playing") return;
    const GAME_KEYS = new Set([
      "KeyW",
      "KeyA",
      "KeyS",
      "KeyD",
      "ArrowUp",
      "ArrowDown",
      "ArrowLeft",
      "ArrowRight",
      "Space",
      "KeyE",
      "Escape",
    ]);
    const kd = (e) => {
      // Ignoruj klawisze gdy fokus jest w input/textarea (np AI LAB textbox)
      const t = e.target;
      if (
        t &&
        (t.tagName === "INPUT" ||
          t.tagName === "TEXTAREA" ||
          t.isContentEditable)
      )
        return;
      if (!GAME_KEYS.has(e.code)) return;
      keysRef.current[e.code] = true;
      if (e.code === "Escape") {
        setPaused((p) => !p);
        e.preventDefault();
        return;
      }
      if (e.code === "Space" && !e.repeat) {
        shoot();
        e.preventDefault();
        return;
      }
      if (e.code === "KeyE" && !e.repeat) {
        interact();
        return;
      }
      // Movement keys: tylko preventDefault dla Arrow keys (zapobiec scroll page)
      if (e.code.startsWith("Arrow")) e.preventDefault();
    };
    const ku = (e) => {
      keysRef.current[e.code] = false;
    };
    window.addEventListener("keydown", kd);
    window.addEventListener("keyup", ku);
    return () => {
      window.removeEventListener("keydown", kd);
      window.removeEventListener("keyup", ku);
      keysRef.current = {};
    };
  }, [phase]);

  // Pointer lock for mouse look
  useEffect(() => {
    if (phase !== "playing") return;
    const c = canvasRef.current;
    if (!c) return;
    const click = () => {
      if (phase === "playing" && !paused) {
        c.requestPointerLock?.();
      }
    };
    const mv = (e) => {
      if (document.pointerLockElement === c) {
        mouseRef.current.dx += e.movementX;
        mouseRef.current.captured = true;
      }
    };
    const lockChange = () => {
      mouseRef.current.captured = document.pointerLockElement === c;
    };
    c.addEventListener("click", click);
    document.addEventListener("mousemove", mv);
    document.addEventListener("pointerlockchange", lockChange);
    return () => {
      c.removeEventListener("click", click);
      document.removeEventListener("mousemove", mv);
      document.removeEventListener("pointerlockchange", lockChange);
    };
  }, [phase, paused]);

  const shoot = () => {
    const s = stateRef.current;
    if (!s) return;
    if (stats.ammo <= 0) {
      PrzelomAudio.sounds.error();
      return;
    }
    setStats((p) => ({ ...p, ammo: p.ammo - 1 }));
    s.muzzle = 6;
    PrzelomAudio.sounds.shoot();
    // Hitscan
    const range = 12;
    const steps = 80;
    let hitEnemy = null,
      hitDist = Infinity;
    for (let i = 1; i < steps; i++) {
      const t = (i / steps) * range;
      const x = s.px + Math.cos(s.pa) * t;
      const y = s.py + Math.sin(s.pa) * t;
      const cx = Math.floor(x),
        cy = Math.floor(y);
      if (cx < 0 || cy < 0 || cx >= s.mapW || cy >= s.mapH) break;
      const cell = s.map[cy][cx];
      if (cell === 1 || cell === 2 || cell === 3) break;
      // Check enemies
      s.enemies.forEach((en) => {
        if (!en.alive) return;
        const d = Math.hypot(en.x - x, en.y - y);
        if (d < en.size && t < hitDist) {
          hitEnemy = en;
          hitDist = t;
        }
      });
      if (hitEnemy) break;
    }
    if (hitEnemy) {
      hitEnemy.hp -= 12;
      hitEnemy.hurt = 8;
      PrzelomAudio.sounds.hit();
      if (hitEnemy.hp <= 0) {
        hitEnemy.alive = false;
        hitEnemy.deathT = 30;
        PrzelomAudio.sounds.enemyDie();
        setStats((p) => ({
          ...p,
          score: p.score + hitEnemy.points,
          kills: p.kills + 1,
          ammo: p.ammo + (hitEnemy.ammoYield || 0),
        }));
      }
    }
  };

  const interact = () => {
    const s = stateRef.current;
    if (!s) return;
    // Check door in front
    const fx = s.px + Math.cos(s.pa) * 0.6;
    const fy = s.py + Math.sin(s.pa) * 0.6;
    const cx = Math.floor(fx),
      cy = Math.floor(fy);
    if (cx >= 0 && cy >= 0 && cx < s.mapW && cy < s.mapH) {
      if (s.map[cy][cx] === 4) {
        s.map[cy][cx] = 0;
        PrzelomAudio.sounds.door();
        setHud((h) => ({ ...h, msg: "DOOR UNLOCKED", flash: 60 }));
      } else if (s.map[cy][cx] === 5) {
        // Exit
        completeLevel();
      }
    }
  };

  const completeLevel = () => {
    PrzelomAudio.sounds.levelup();
    if (stats.level + 1 >= LEVELS.length) {
      setPhase("won");
    } else {
      setPhase("intermission");
    }
  };

  // ===== Game loop =====
  useEffect(() => {
    if (phase !== "playing" || paused) return;
    const c = canvasRef.current;
    if (!c) return;
    const ctx = c.getContext("2d");
    let raf;
    let last = performance.now();

    const loop = (now) => {
      const dt = Math.min((now - last) / 16.67, 2);
      last = now;
      const s = stateRef.current;
      if (!s) {
        raf = requestAnimationFrame(loop);
        return;
      }
      s.time += dt / 60;

      // ===== Input movement =====
      const moveSpeed = 0.06 * dt;
      const rotSpeed = 0.04 * dt;
      const kk = keysRef.current;
      let nx = s.px,
        ny = s.py;
      const fwd = Math.cos(s.pa),
        fwy = Math.sin(s.pa);
      const stx = -Math.sin(s.pa),
        sty = Math.cos(s.pa);
      if (kk.KeyW || kk.ArrowUp) {
        nx += fwd * moveSpeed;
        ny += fwy * moveSpeed;
      }
      if (kk.KeyS || kk.ArrowDown) {
        nx -= fwd * moveSpeed;
        ny -= fwy * moveSpeed;
      }
      if (kk.KeyA) {
        nx += stx * moveSpeed;
        ny += sty * moveSpeed;
      }
      if (kk.KeyD) {
        nx -= stx * moveSpeed;
        ny -= sty * moveSpeed;
      }
      if (kk.ArrowLeft) s.pa -= rotSpeed;
      if (kk.ArrowRight) s.pa += rotSpeed;
      // Mouse look
      if (mouseRef.current.dx !== 0) {
        s.pa += mouseRef.current.dx * 0.0025;
        mouseRef.current.dx = 0;
      }
      // Collision
      const canMove = (x, y) => {
        const cx = Math.floor(x),
          cy = Math.floor(y);
        if (cx < 0 || cy < 0 || cx >= s.mapW || cy >= s.mapH) return false;
        const v = s.map[cy][cx];
        return v === 0 || v === 5;
      };
      if (canMove(nx, s.py)) s.px = nx;
      if (canMove(s.px, ny)) s.py = ny;

      // ===== Pickups =====
      s.pickups.forEach((p) => {
        if (p.taken) return;
        p.bob += 0.1 * dt;
        const d = Math.hypot(p.x - s.px, p.y - s.py);
        if (d < 0.5) {
          p.taken = true;
          PrzelomAudio.sounds.pickup();
          if (p.type === "hp")
            setStats((st) => ({ ...st, hp: Math.min(100, st.hp + p.value) }));
          if (p.type === "ammo")
            setStats((st) => ({ ...st, ammo: st.ammo + p.value }));
          if (p.type === "shield") setStats((st) => ({ ...st, shield: 50 }));
          setHud((h) => ({ ...h, msg: `+${p.label}`, flash: 60 }));
        }
      });

      // ===== Enemy AI =====
      const diffMul =
        difficulty === "easy" ? 0.6 : difficulty === "hard" ? 1.5 : 1;
      s.enemies.forEach((en) => {
        if (!en.alive) {
          if (en.deathT > 0) en.deathT -= dt;
          return;
        }
        if (en.hurt > 0) en.hurt -= dt;
        const dx = s.px - en.x,
          dy = s.py - en.y;
        const dist = Math.hypot(dx, dy);
        if (dist > 12) return; // sleep
        const dir = Math.atan2(dy, dx);
        // Move toward player if line of sight & not too close
        if (dist > 0.8 && hasLineOfSight(s, en.x, en.y, s.px, s.py)) {
          const ms = en.speed * dt * diffMul;
          const newX = en.x + Math.cos(dir) * ms;
          const newY = en.y + Math.sin(dir) * ms;
          if (canMove(newX, en.y)) en.x = newX;
          if (canMove(en.x, newY)) en.y = newY;
        }
        // Damage on contact (melee bug/deadline)
        if (dist < 0.8 && en.shootRange === 0) {
          en.shootCd -= dt / 60;
          if (en.shootCd <= 0) {
            en.shootCd = 0.8;
            damagePlayer(en.dmg * diffMul);
          }
        }
        // Ranged
        if (
          en.shootRange > 0 &&
          dist < en.shootRange &&
          hasLineOfSight(s, en.x, en.y, s.px, s.py)
        ) {
          en.shootCd -= dt / 60;
          if (en.shootCd <= 0) {
            en.shootCd = 1.2 + Math.random() * 0.6;
            // spawn projectile
            s.bullets.push({
              x: en.x,
              y: en.y,
              vx: Math.cos(dir) * 0.12,
              vy: Math.sin(dir) * 0.12,
              life: 80,
              dmg: en.dmg * diffMul,
              color: en.color,
            });
            PrzelomAudio.sounds.shoot();
          }
        }
      });

      // ===== Bullets =====
      s.bullets = s.bullets.filter((b) => {
        b.x += b.vx * dt;
        b.y += b.vy * dt;
        b.life -= dt;
        if (b.life <= 0) return false;
        const cx = Math.floor(b.x),
          cy = Math.floor(b.y);
        if (cx < 0 || cy < 0 || cx >= s.mapW || cy >= s.mapH) return false;
        const v = s.map[cy][cx];
        if (v === 1 || v === 2 || v === 3) return false;
        const d = Math.hypot(b.x - s.px, b.y - s.py);
        if (d < 0.4) {
          damagePlayer(b.dmg);
          return false;
        }
        return true;
      });

      // ===== Muzzle flash decay =====
      if (s.muzzle > 0) s.muzzle -= dt * 0.5;

      // ===== HUD msg decay =====
      setHud((h) => ({
        msg: h.flash > 0 ? h.msg : "",
        flash: Math.max(0, h.flash - dt),
        hurtFlash: Math.max(0, h.hurtFlash - dt),
      }));

      // ===== Render =====
      render(ctx, c.width, c.height, s);

      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [phase, paused, difficulty]);

  const damagePlayer = (dmg) => {
    setStats((st) => {
      let hp = st.hp,
        sh = st.shield;
      if (sh > 0) {
        sh = Math.max(0, sh - dmg);
        hp = Math.max(0, hp - dmg * 0.3);
      } else {
        hp = Math.max(0, hp - dmg);
      }
      if (hp <= 0) {
        PrzelomAudio.sounds.gameover();
        setPhase("dead");
      }
      return { ...st, hp, shield: sh };
    });
    PrzelomAudio.sounds.hurt();
    setHud((h) => ({ ...h, hurtFlash: 18 }));
  };

  // Resize canvas
  useEffect(() => {
    const c = canvasRef.current;
    if (!c) return;
    const resize = () => {
      const r = c.parentElement.getBoundingClientRect();
      c.width = Math.floor(r.width);
      c.height = Math.floor(r.height);
    };
    resize();
    const ro = new ResizeObserver(resize);
    if (c.parentElement) ro.observe(c.parentElement);
    return () => ro.disconnect();
  }, [phase]);

  // ============ MENUS ============
  if (phase === "menu") {
    return (
      <div className="doom doom-menu">
        <div className="doom-logo">
          <div className="doom-logo-sub">// ZAZA PRESENTS</div>
          <div className="doom-logo-main">
            HACK
            <br />
            THE STACK
          </div>
          <div className="doom-logo-tag">
            a 5-minute fps about technical debt
          </div>
        </div>
        <div className="doom-menu-info">
          <div>
            <strong>WSAD / ↑↓</strong> move · <strong>MOUSE</strong> look ·{" "}
            <strong>SPACE</strong> shoot · <strong>E</strong> interact ·{" "}
            <strong>ESC</strong> pause
          </div>
        </div>
        <div className="doom-diff">
          {["easy", "normal", "hard"].map((d) => (
            <button
              key={d}
              className={`doom-diff-btn ${difficulty === d ? "sel" : ""}`}
              onClick={() => {
                setDifficulty(d);
                PrzelomAudio.sounds.click();
              }}
            >
              {d.toUpperCase()}
            </button>
          ))}
        </div>
        <button className="doom-start" onClick={startGame}>
          ▶ START — DEPLOY
        </button>
        <div className="doom-credits">
          2 levels · 28 enemies · ~3min playtime
        </div>
      </div>
    );
  }

  if (phase === "dead") {
    return (
      <div className="doom doom-dead">
        <div className="doom-end-tag">// PROCESS TERMINATED</div>
        <div className="doom-end-h">SEGFAULT</div>
        <div className="doom-end-stats">
          <div>
            <span>SCORE</span>
            <strong>{stats.score}</strong>
          </div>
          <div>
            <span>KILLS</span>
            <strong>{stats.kills}</strong>
          </div>
          <div>
            <span>LEVEL</span>
            <strong>
              {stats.level + 1}/{LEVELS.length}
            </strong>
          </div>
        </div>
        <button className="doom-start" onClick={startGame}>
          ↻ RESPAWN
        </button>
        <button className="doom-back" onClick={() => setPhase("menu")}>
          ← BACK TO MENU
        </button>
      </div>
    );
  }

  if (phase === "intermission") {
    return (
      <div className="doom doom-inter">
        <div className="doom-end-tag">// LEVEL CLEARED</div>
        <div className="doom-end-h">{LEVELS[stats.level].name}</div>
        <div className="doom-end-stats">
          <div>
            <span>SCORE</span>
            <strong>{stats.score}</strong>
          </div>
          <div>
            <span>KILLS</span>
            <strong>{stats.kills}</strong>
          </div>
          <div>
            <span>HP</span>
            <strong>{stats.hp}</strong>
          </div>
          <div>
            <span>AMMO</span>
            <strong>{stats.ammo}</strong>
          </div>
        </div>
        <div className="doom-next">
          NEXT: LEVEL {stats.level + 2} — {LEVELS[stats.level + 1].name}
        </div>
        <button
          className="doom-start"
          onClick={() => startLevel(stats.level + 1)}
        >
          ▶ DEPLOY NEXT
        </button>
      </div>
    );
  }

  if (phase === "won") {
    return (
      <div className="doom doom-won">
        <div className="doom-end-tag">// CI/CD PIPELINE: GREEN</div>
        <div className="doom-end-h">SHIPPED.</div>
        <div className="doom-won-msg">
          All bugs squashed. All deadlines crushed. The stack is yours.
        </div>
        <div className="doom-end-stats">
          <div>
            <span>FINAL SCORE</span>
            <strong>{stats.score}</strong>
          </div>
          <div>
            <span>TOTAL KILLS</span>
            <strong>{stats.kills}</strong>
          </div>
        </div>
        <button className="doom-start" onClick={startGame}>
          ↻ NEW GAME+
        </button>
        <button className="doom-back" onClick={() => setPhase("menu")}>
          ← MAIN MENU
        </button>
      </div>
    );
  }

  // ============ HUD ============
  return (
    <div className="doom doom-play">
      <canvas ref={canvasRef} className="doom-canvas"></canvas>
      <div
        className={`doom-hud-overlay ${hud.hurtFlash > 0 ? "hurt" : ""}`}
      ></div>
      {paused && (
        <div className="doom-paused">
          <div className="doom-end-h">PAUSED</div>
          <button className="doom-start" onClick={() => setPaused(false)}>
            ▶ RESUME
          </button>
          <button className="doom-back" onClick={() => setPhase("menu")}>
            ← QUIT TO MENU
          </button>
        </div>
      )}
      <div className="doom-hud">
        <div className="doom-hud-l">
          <div className="hud-stat">
            <div className="hud-lbl">HP</div>
            <div className={`hud-bar hp ${stats.hp < 25 ? "crit" : ""}`}>
              <div style={{ width: stats.hp + "%" }}></div>
            </div>
            <div className="hud-val">{stats.hp}</div>
          </div>
          <div className="hud-stat">
            <div className="hud-lbl">AMMO</div>
            <div className="hud-val ammo">{stats.ammo}</div>
          </div>
          {stats.shield > 0 && (
            <div className="hud-stat">
              <div className="hud-lbl">SHLD</div>
              <div className="hud-bar sh">
                <div style={{ width: stats.shield * 2 + "%" }}></div>
              </div>
            </div>
          )}
        </div>
        <div className="doom-hud-c">
          <div className="hud-cross">+</div>
          {hud.flash > 0 && <div className="hud-msg">{hud.msg}</div>}
        </div>
        <div className="doom-hud-r">
          <div className="hud-stat">
            <div className="hud-lbl">LVL</div>
            <div className="hud-val">
              {stats.level + 1}/{LEVELS.length}
            </div>
          </div>
          <div className="hud-stat">
            <div className="hud-lbl">SCORE</div>
            <div className="hud-val">{stats.score}</div>
          </div>
          <div className="hud-stat">
            <div className="hud-lbl">KILLS</div>
            <div className="hud-val">{stats.kills}</div>
          </div>
        </div>
      </div>
      <Minimap stateRef={stateRef} />
    </div>
  );
}

function Minimap({ stateRef }) {
  const ref = useRef(null);
  useEffect(() => {
    let raf;
    const draw = () => {
      const c = ref.current;
      if (!c) {
        raf = requestAnimationFrame(draw);
        return;
      }
      const s = stateRef.current;
      if (!s) {
        raf = requestAnimationFrame(draw);
        return;
      }
      c.width = 140;
      c.height = 140;
      const ctx = c.getContext("2d");
      ctx.fillStyle = "rgba(0,0,0,0.7)";
      ctx.fillRect(0, 0, 140, 140);
      const cs = Math.min(140 / s.mapW, 140 / s.mapH);
      for (let y = 0; y < s.mapH; y++)
        for (let x = 0; x < s.mapW; x++) {
          const v = s.map[y][x];
          if (v === 1) ctx.fillStyle = "#caff33";
          else if (v === 3) ctx.fillStyle = "#ffcc33";
          else if (v === 4) ctx.fillStyle = "#ff5577";
          else if (v === 5) ctx.fillStyle = "#33ffe1";
          else continue;
          ctx.fillRect(x * cs, y * cs, cs, cs);
        }
      // Enemies
      s.enemies.forEach((e) => {
        if (!e.alive) return;
        ctx.fillStyle = e.color;
        ctx.fillRect(e.x * cs - 1.5, e.y * cs - 1.5, 3, 3);
      });
      // Player
      ctx.fillStyle = "#fff";
      ctx.beginPath();
      ctx.arc(s.px * cs, s.py * cs, 2.5, 0, Math.PI * 2);
      ctx.fill();
      ctx.strokeStyle = "#fff";
      ctx.beginPath();
      ctx.moveTo(s.px * cs, s.py * cs);
      ctx.lineTo(
        s.px * cs + Math.cos(s.pa) * 8,
        s.py * cs + Math.sin(s.pa) * 8,
      );
      ctx.stroke();
      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);
    return () => cancelAnimationFrame(raf);
  }, []);
  return <canvas ref={ref} className="doom-minimap"></canvas>;
}

// ============ Helpers ============
function hasLineOfSight(s, x1, y1, x2, y2) {
  const dx = x2 - x1,
    dy = y2 - y1;
  const dist = Math.hypot(dx, dy);
  const steps = Math.ceil(dist * 4);
  for (let i = 1; i < steps; i++) {
    const t = i / steps;
    const x = x1 + dx * t,
      y = y1 + dy * t;
    const cx = Math.floor(x),
      cy = Math.floor(y);
    if (cx < 0 || cy < 0 || cx >= s.mapW || cy >= s.mapH) return false;
    const v = s.map[cy][cx];
    if (v === 1 || v === 2 || v === 3) return false;
  }
  return true;
}

// ============ Render ============
function render(ctx, W, H, s) {
  // Sky/floor gradient
  const sky = ctx.createLinearGradient(0, 0, 0, H / 2);
  sky.addColorStop(0, "#1a0a2e");
  sky.addColorStop(1, "#3a1a5e");
  ctx.fillStyle = sky;
  ctx.fillRect(0, 0, W, H / 2);
  const floor = ctx.createLinearGradient(0, H / 2, 0, H);
  floor.addColorStop(0, "#0a1a0a");
  floor.addColorStop(1, "#000");
  ctx.fillStyle = floor;
  ctx.fillRect(0, H / 2, W, H / 2);

  // Synthwave horizon grid
  ctx.strokeStyle = "rgba(202,255,51,0.18)";
  ctx.lineWidth = 1;
  for (let y = 0; y < 8; y++) {
    const yy = H / 2 + (Math.pow(y / 8, 2) * H) / 2;
    ctx.beginPath();
    ctx.moveTo(0, yy);
    ctx.lineTo(W, yy);
    ctx.stroke();
  }

  const FOV = Math.PI / 3;
  const NUM_RAYS = Math.min(W, 320);
  const colW = W / NUM_RAYS;
  const zbuffer = new Float32Array(NUM_RAYS);

  for (let r = 0; r < NUM_RAYS; r++) {
    const rayA = s.pa - FOV / 2 + (r / NUM_RAYS) * FOV;
    const rdx = Math.cos(rayA),
      rdy = Math.sin(rayA);
    // DDA
    let mx = Math.floor(s.px),
      my = Math.floor(s.py);
    const ddx = Math.abs(1 / rdx),
      ddy = Math.abs(1 / rdy);
    let stx, sty, sdx, sdy;
    if (rdx < 0) {
      stx = -1;
      sdx = (s.px - mx) * ddx;
    } else {
      stx = 1;
      sdx = (mx + 1 - s.px) * ddx;
    }
    if (rdy < 0) {
      sty = -1;
      sdy = (s.py - my) * ddy;
    } else {
      sty = 1;
      sdy = (my + 1 - s.py) * ddy;
    }
    let hit = 0,
      side = 0,
      cellV = 0;
    for (let i = 0; i < 64; i++) {
      if (sdx < sdy) {
        sdx += ddx;
        mx += stx;
        side = 0;
      } else {
        sdy += ddy;
        my += sty;
        side = 1;
      }
      if (mx < 0 || my < 0 || mx >= s.mapW || my >= s.mapH) {
        hit = 1;
        cellV = 1;
        break;
      }
      const v = s.map[my][mx];
      if (v === 1 || v === 2 || v === 3 || v === 4 || v === 5) {
        hit = 1;
        cellV = v;
        break;
      }
    }
    let dist = side === 0 ? sdx - ddx : sdy - ddy;
    if (dist < 0.0001) dist = 0.0001;
    const corrected = dist * Math.cos(rayA - s.pa);
    zbuffer[r] = corrected;
    const wallH = Math.min(H * 1.5, H / corrected);
    const y0 = (H - wallH) / 2;
    // Shade by distance + side
    let baseR = 80,
      baseG = 140,
      baseB = 120;
    if (cellV === 1) {
      baseR = 20;
      baseG = 60;
      baseB = 100;
    }
    if (cellV === 2) {
      baseR = 180;
      baseG = 40;
      baseB = 100;
    }
    if (cellV === 3) {
      baseR = 30;
      baseG = 30;
      baseB = 40;
    }
    if (cellV === 4) {
      baseR = 200;
      baseG = 160;
      baseB = 40;
    }
    if (cellV === 5) {
      baseR = 50;
      baseG = 255;
      baseB = 200;
    }
    const shade = Math.max(0.1, 1 - corrected / 14) * (side === 0 ? 1 : 0.7);
    const rC = Math.floor(baseR * shade),
      gC = Math.floor(baseG * shade),
      bC = Math.floor(baseB * shade);
    ctx.fillStyle = `rgb(${rC},${gC},${bC})`;
    ctx.fillRect(r * colW, y0, colW + 1, wallH);
    // Neon wall edges (synthwave outline)
    if (cellV === 1 || cellV === 2) {
      ctx.fillStyle = `rgba(202,255,51,${Math.max(0, 0.3 - corrected / 16)})`;
      ctx.fillRect(r * colW, y0, colW + 1, 1);
      ctx.fillRect(r * colW, y0 + wallH - 1, colW + 1, 1);
    }
    if (cellV === 5) {
      // Exit pulses
      const pulse = 0.5 + 0.5 * Math.sin(s.time * 5);
      ctx.fillStyle = `rgba(50,255,200,${pulse * 0.5})`;
      ctx.fillRect(r * colW, y0, colW + 1, wallH);
    }
  }

  // ===== Sprites: enemies + pickups =====
  const sprites = [];
  s.enemies.forEach((e) => {
    if (!e.alive && e.deathT <= 0) return;
    sprites.push({ x: e.x, y: e.y, kind: "enemy", ref: e });
  });
  s.pickups.forEach((p) => {
    if (p.taken) return;
    sprites.push({ x: p.x, y: p.y, kind: "pickup", ref: p });
  });
  s.bullets.forEach((b) =>
    sprites.push({ x: b.x, y: b.y, kind: "bullet", ref: b }),
  );

  sprites.forEach((sp) => (sp.dist = Math.hypot(sp.x - s.px, sp.y - s.py)));
  sprites.sort((a, b) => b.dist - a.dist);

  sprites.forEach((sp) => {
    const dx = sp.x - s.px,
      dy = sp.y - s.py;
    const angle = Math.atan2(dy, dx) - s.pa;
    let a = angle;
    while (a > Math.PI) a -= Math.PI * 2;
    while (a < -Math.PI) a += Math.PI * 2;
    if (Math.abs(a) > FOV / 2 + 0.3) return;
    const corrected = sp.dist * Math.cos(a);
    if (corrected < 0.2) return;
    const screenX = (W / 2) * (1 + (2 * a) / FOV);
    let size = H / corrected;
    if (sp.kind === "enemy") {
      size *= sp.ref.size;
      drawEnemy(
        ctx,
        screenX,
        H / 2,
        size,
        sp.ref,
        corrected,
        zbuffer,
        NUM_RAYS,
        W,
        s.time,
      );
    } else if (sp.kind === "pickup") {
      size *= 0.35;
      drawPickup(
        ctx,
        screenX,
        H / 2,
        size,
        sp.ref,
        corrected,
        zbuffer,
        NUM_RAYS,
        W,
        s.time,
      );
    } else if (sp.kind === "bullet") {
      size *= 0.15;
      drawBullet(
        ctx,
        screenX,
        H / 2,
        size,
        sp.ref,
        corrected,
        zbuffer,
        NUM_RAYS,
        W,
      );
    }
  });

  // ===== Weapon (player gun) =====
  drawWeapon(ctx, W, H, s);

  // Vignette + scanlines
  const vg = ctx.createRadialGradient(
    W / 2,
    H / 2,
    H * 0.2,
    W / 2,
    H / 2,
    H * 0.7,
  );
  vg.addColorStop(0, "rgba(0,0,0,0)");
  vg.addColorStop(1, "rgba(0,0,0,0.7)");
  ctx.fillStyle = vg;
  ctx.fillRect(0, 0, W, H);
  ctx.fillStyle = "rgba(0,0,0,0.12)";
  for (let y = 0; y < H; y += 3) ctx.fillRect(0, y, W, 1);
}

function drawEnemy(ctx, sx, cy, size, e, dist, zb, NR, W, time) {
  const colW = W / NR;
  const left = sx - size / 2;
  const top = cy - size / 2;
  const startCol = Math.max(0, Math.floor(left / colW));
  const endCol = Math.min(NR - 1, Math.ceil((left + size) / colW));
  // Body (chunky rect + glow)
  for (let c = startCol; c <= endCol; c++) {
    if (zb[c] < dist) continue;
    const x = c * colW;
    const colInSprite = (x - left) / size;
    if (colInSprite < 0 || colInSprite > 1) continue;
    // Death animation: shrink and fade
    let yScale = 1;
    if (!e.alive) {
      yScale = Math.max(0, e.deathT / 30);
    }
    const sH = size * yScale;
    const sTop = cy - sH / 2;
    // Hurt flash
    let col = e.color;
    if (e.hurt > 0) col = "#fff";
    ctx.fillStyle = col;
    ctx.fillRect(x, sTop + sH * 0.3, colW + 1, sH * 0.6);
    ctx.fillStyle = "rgba(0,0,0,0.5)";
    ctx.fillRect(x, sTop + sH * 0.6, colW + 1, sH * 0.05);
    // Head/glyph area
    ctx.fillStyle = e.color;
    ctx.fillRect(x, sTop + sH * 0.1, colW + 1, sH * 0.25);
  }
  // Glyph in center
  if (e.alive) {
    ctx.fillStyle = "#fff";
    ctx.font = `bold ${Math.max(10, size * 0.4)}px JetBrains Mono`;
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const bob = Math.sin(time * 4 + e.x * 3 + e.y * 3) * size * 0.05;
    ctx.fillText(e.glyph, sx, cy - size * 0.15 + bob);
  }
  // Healthbar
  if (e.alive && e.hp < ENEMY_TYPES[e.type].hp) {
    const bw = size * 0.7;
    ctx.fillStyle = "rgba(0,0,0,0.6)";
    ctx.fillRect(sx - bw / 2, cy - size / 2 - 8, bw, 4);
    ctx.fillStyle = "#caff33";
    ctx.fillRect(
      sx - bw / 2,
      cy - size / 2 - 8,
      bw * (e.hp / ENEMY_TYPES[e.type].hp),
      4,
    );
  }
}

function drawPickup(ctx, sx, cy, size, p, dist, zb, NR, W, time) {
  const bob = Math.sin(time * 3 + p.bob) * size * 0.2;
  const left = sx - size / 2;
  const colW = W / NR;
  const startCol = Math.max(0, Math.floor(left / colW));
  const endCol = Math.min(NR - 1, Math.ceil((left + size) / colW));
  let visible = false;
  for (let c = startCol; c <= endCol; c++) {
    if (zb[c] >= dist) {
      visible = true;
      break;
    }
  }
  if (!visible) return;
  ctx.save();
  ctx.shadowBlur = 20;
  ctx.shadowColor = p.color;
  ctx.fillStyle = p.color;
  ctx.font = `bold ${size}px JetBrains Mono`;
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(p.glyph, sx, cy + bob);
  ctx.restore();
}

function drawBullet(ctx, sx, cy, size, b, dist, zb, NR, W) {
  const colW = W / NR;
  const c = Math.floor(sx / colW);
  if (c < 0 || c >= NR) return;
  if (zb[c] < dist) return;
  ctx.save();
  ctx.shadowBlur = 16;
  ctx.shadowColor = b.color;
  ctx.fillStyle = b.color;
  ctx.beginPath();
  ctx.arc(sx, cy, Math.max(2, size / 2), 0, Math.PI * 2);
  ctx.fill();
  ctx.restore();
}

function drawWeapon(ctx, W, H, s) {
  const cx = W / 2 + Math.sin(s.time * 4) * 4;
  const cy = H - 80 + Math.abs(Math.cos(s.time * 4)) * 4;
  // Gun barrel
  ctx.fillStyle = "#1a1a1a";
  ctx.fillRect(cx - 50, cy - 20, 100, 80);
  ctx.fillStyle = "#222";
  ctx.fillRect(cx - 12, cy - 90, 24, 70);
  // Accent details
  ctx.fillStyle = "#caff33";
  ctx.fillRect(cx - 50, cy - 20, 100, 4);
  ctx.fillRect(cx - 12, cy - 90, 24, 3);
  ctx.fillStyle = "#444";
  ctx.fillRect(cx - 8, cy - 86, 16, 60);
  // Muzzle flash
  if (s.muzzle > 0) {
    const m = s.muzzle / 6;
    ctx.save();
    ctx.globalAlpha = m;
    ctx.shadowBlur = 30;
    ctx.shadowColor = "#caff33";
    ctx.fillStyle = "#fff";
    ctx.beginPath();
    ctx.arc(cx, cy - 90, 25 * m, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = "#caff33";
    ctx.beginPath();
    ctx.arc(cx, cy - 90, 40 * m, 0, Math.PI * 2);
    ctx.globalAlpha = m * 0.5;
    ctx.fill();
    ctx.restore();
  }
}

window.DoomApp = DoomApp;
