/* /functional/games/tetris/tetris.js */
(() => {
  'use strict';

  const CFG = {
    cols: 10,
    rows: 20,
    block: 30,           // canvas scale (px per cell) is derived from canvas size; block is used for calculations
    lockDelayMs: 0,
    baseDropMs: 700,
    minDropMs: 70,
    levelEveryLines: 10,
    dasMs: 140,          // delayed auto shift start
    arrMs: 45            // auto repeat rate
  };

  const LANG = (window.VM_TETRIS && window.VM_TETRIS.lang) ? String(window.VM_TETRIS.lang) : 'uk';
  const bestKey = (window.VM_TETRIS && window.VM_TETRIS.bestKey) ? String(window.VM_TETRIS.bestKey) : 'vm_tetris_best_v1';
  const T = (uk, ru, en) => (LANG === 'ru' ? ru : (LANG === 'en' ? en : uk));

  const $ = (id) => document.getElementById(id);

  const canvas = $('tCanvas');
  const ctx = canvas.getContext('2d', { alpha: false });

  const nextCanvas = $('tNext');
  const nctx = nextCanvas.getContext('2d', { alpha: true });

  const elScore = $('tScore');
  const elLines = $('tLines');
  const elLevel = $('tLevel');
  const elBest  = $('tBest');

  const btnStart = $('tStart');
  const btnPause = $('tPause');
  const btnReset = $('tReset');

  const overlay = $('tOverlay');
  const overlayTitle = $('tOverlayTitle');
  const overlaySub = $('tOverlaySub');

  // derive cell size from canvas dimension
  const cell = Math.floor(canvas.width / CFG.cols);
  const hcell = Math.floor(canvas.height / CFG.rows);
  const CELL = Math.min(cell, hcell);

  // Board: rows x cols, 0 empty, 1..7 filled (piece id)
  let board = makeBoard(CFG.rows, CFG.cols);

  const PIECES = {
    I: {
      id: 1,
      // 4x4
      m: [
        [0,0,0,0],
        [1,1,1,1],
        [0,0,0,0],
        [0,0,0,0]
      ]
    },
    O: {
      id: 2,
      m: [
        [0,1,1,0],
        [0,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
      ]
    },
    T: {
      id: 3,
      m: [
        [0,1,0,0],
        [1,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
      ]
    },
    S: {
      id: 4,
      m: [
        [0,1,1,0],
        [1,1,0,0],
        [0,0,0,0],
        [0,0,0,0]
      ]
    },
    Z: {
      id: 5,
      m: [
        [1,1,0,0],
        [0,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
      ]
    },
    J: {
      id: 6,
      m: [
        [1,0,0,0],
        [1,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
      ]
    },
    L: {
      id: 7,
      m: [
        [0,0,1,0],
        [1,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
      ]
    }
  };

  const pieceKeys = Object.keys(PIECES);
  const COLORS = {
    0: '#0f1115',
    1: '#4aa3ff', // I
    2: '#ffd34a', // O
    3: '#b06cff', // T
    4: '#41d17e', // S
    5: '#ff5a6e', // Z
    6: '#4ad6ff', // J
    7: '#ff9b4a'  // L
  };

  let running = false;
  let paused  = false;
  let gameOver = false;

  let score = 0;
  let lines = 0;
  let level = 1;
  let best = loadBest();

  let dropMs = CFG.baseDropMs;

  let current = null; // {id, m, x, y}
  let next = null;

  // input repeat state
  const keyState = {
    left: false,
    right: false,
    down: false
  };
  let leftTimer = 0, rightTimer = 0;
  let leftHeldSince = 0, rightHeldSince = 0;

  // timing
  let lastTs = 0;
  let dropAcc = 0;

  function makeBoard(r, c) {
    const b = [];
    for (let y = 0; y < r; y++) b.push(new Array(c).fill(0));
    return b;
  }

  function cloneMatrix(m) {
    return m.map(row => row.slice());
  }

  function rotateMatrixCW(m) {
    // 4x4 transpose + reverse rows
    const res = [
      [0,0,0,0],
      [0,0,0,0],
      [0,0,0,0],
      [0,0,0,0]
    ];
    for (let y = 0; y < 4; y++) for (let x = 0; x < 4; x++) res[x][3 - y] = m[y][x];
    return res;
  }
  function rotateMatrixCCW(m) {
    const res = [
      [0,0,0,0],
      [0,0,0,0],
      [0,0,0,0],
      [0,0,0,0]
    ];
    for (let y = 0; y < 4; y++) for (let x = 0; x < 4; x++) res[3 - x][y] = m[y][x];
    return res;
  }

  function randPiece() {
    const k = pieceKeys[Math.floor(Math.random() * pieceKeys.length)];
    const p = PIECES[k];
    return {
      id: p.id,
      m: cloneMatrix(p.m),
      x: Math.floor(CFG.cols / 2) - 2,
      y: -1
    };
  }

  function collides(piece, nx = piece.x, ny = piece.y, nm = piece.m) {
    for (let y = 0; y < 4; y++) {
      for (let x = 0; x < 4; x++) {
        if (!nm[y][x]) continue;
        const bx = nx + x;
        const by = ny + y;

        // above top is allowed
        if (by < 0) continue;

        // walls/floor
        if (bx < 0 || bx >= CFG.cols || by >= CFG.rows) return true;

        // blocks
        if (board[by][bx] !== 0) return true;
      }
    }
    return false;
  }

  function merge(piece) {
    for (let y = 0; y < 4; y++) {
      for (let x = 0; x < 4; x++) {
        if (!piece.m[y][x]) continue;
        const bx = piece.x + x;
        const by = piece.y + y;
        if (by < 0) continue;
        if (by >= 0 && by < CFG.rows && bx >= 0 && bx < CFG.cols) {
          board[by][bx] = piece.id;
        }
      }
    }
  }

  function clearLines() {
    let cleared = 0;
    for (let y = CFG.rows - 1; y >= 0; y--) {
      let full = true;
      for (let x = 0; x < CFG.cols; x++) {
        if (board[y][x] === 0) { full = false; break; }
      }
      if (full) {
        board.splice(y, 1);
        board.unshift(new Array(CFG.cols).fill(0));
        cleared++;
        y++; // re-check same y after shifting
      }
    }

    if (cleared > 0) {
      lines += cleared;
      const mult = level;
      if (cleared === 1) score += 100 * mult;
      else if (cleared === 2) score += 300 * mult;
      else if (cleared === 3) score += 500 * mult;
      else if (cleared >= 4) score += 800 * mult;

      const newLevel = Math.floor(lines / CFG.levelEveryLines) + 1;
      if (newLevel !== level) {
        level = newLevel;
        recalcDrop();
      }
      updateStats();
      saveBestIfNeeded();
    }
  }

  function recalcDrop() {
    dropMs = Math.max(CFG.minDropMs, CFG.baseDropMs - (level - 1) * 55);
  }

  function spawn() {
    if (!next) next = randPiece();
    current = next;
    next = randPiece();

    // reset pos
    current.x = Math.floor(CFG.cols / 2) - 2;
    current.y = -1;

    if (collides(current, current.x, current.y, current.m)) {
      // if collides even above, push down once
      current.y = 0;
      if (collides(current, current.x, current.y, current.m)) {
        setGameOver();
      }
    }
    drawNext();
  }

  function move(dx, dy) {
    if (!current || gameOver) return false;
    const nx = current.x + dx;
    const ny = current.y + dy;
    if (!collides(current, nx, ny, current.m)) {
      current.x = nx;
      current.y = ny;
      return true;
    }
    return false;
  }

  function hardDrop() {
    if (!current || gameOver) return;
    let moved = 0;
    while (move(0, 1)) moved++;
    // small reward for hard drop (optional; keep minimal)
    score += moved * 2;
    lockPiece();
    updateStats();
    saveBestIfNeeded();
  }

  function softDropTick() {
    if (!current || gameOver) return;
    if (!move(0, 1)) {
      lockPiece();
    } else {
      score += 1; // tiny reward for soft drop
      updateStats();
      saveBestIfNeeded();
    }
  }

  function lockPiece() {
    merge(current);
    clearLines();
    spawn();
  }

  function rotate(dir) {
    if (!current || gameOver) return;
    const nm = (dir === 'ccw') ? rotateMatrixCCW(current.m) : rotateMatrixCW(current.m);

    // simple kicks
    const kicks = [
      {x:0,y:0},
      {x:1,y:0},
      {x:-1,y:0},
      {x:2,y:0},
      {x:-2,y:0},
      {x:0,y:-1}
    ];

    for (const k of kicks) {
      const nx = current.x + k.x;
      const ny = current.y + k.y;
      if (!collides(current, nx, ny, nm)) {
        current.m = nm;
        current.x = nx;
        current.y = ny;
        return;
      }
    }
  }

  function setGameOver() {
    gameOver = true;
    running = false;
    paused = false;
    btnPause.disabled = true;
    btnStart.disabled = false;

    overlayTitle.textContent = T('Гру завершено', 'Игра окончена', 'Game Over');
    overlaySub.textContent   = T('Натисни "Заново" щоб почати', 'Нажми "Заново" чтобы начать', 'Press "Reset" to restart');
    overlay.classList.remove('d-none');

    saveBestIfNeeded(true);
  }

  function resetGame() {
    board = makeBoard(CFG.rows, CFG.cols);
    score = 0; lines = 0; level = 1;
    recalcDrop();
    gameOver = false;
    running = false;
    paused = false;
    next = randPiece();
    current = null;

    updateStats();
    drawNext();
    draw();
    showOverlay(T('Натисни Старт', 'Нажми Старт', 'Press Start'), T('Клавіші або кнопки знизу', 'Клавиши или кнопки снизу', 'Use keys or on-screen buttons'));
  }

  function startGame() {
    if (gameOver) resetGame();
    if (!running) {
      running = true;
      paused = false;
      btnStart.disabled = true;
      btnPause.disabled = false;
      btnPause.textContent = T('Пауза','Пауза','Pause');

      if (!current) spawn();
      hideOverlay();
      lastTs = performance.now();
      requestAnimationFrame(loop);
    }
  }

  function togglePause() {
    if (!running || gameOver) return;
    paused = !paused;
    if (paused) {
      btnPause.textContent = T('Продовжити','Продолжить','Resume');
      showOverlay(T('Пауза','Пауза','Paused'), T('Натисни P або "Продовжити"', 'Нажми P или "Продолжить"', 'Press P or "Resume"'));
    } else {
      btnPause.textContent = T('Пауза','Пауза','Pause');
      hideOverlay();
      lastTs = performance.now();
      requestAnimationFrame(loop);
    }
  }

  function showOverlay(title, sub) {
    overlayTitle.textContent = title;
    overlaySub.textContent = sub;
    overlay.classList.remove('d-none');
  }
  function hideOverlay() {
    overlay.classList.add('d-none');
  }

  function loop(ts) {
    if (!running || paused) return;

    const dt = ts - lastTs;
    lastTs = ts;

    handleHeldKeys(ts);

    dropAcc += dt;

    // if down held -> faster
    const interval = keyState.down ? Math.max(35, Math.floor(dropMs / 8)) : dropMs;

    if (dropAcc >= interval) {
      dropAcc = 0;
      if (!move(0, 1)) {
        lockPiece();
      }
      updateStats();
      saveBestIfNeeded();
    }

    draw();
    requestAnimationFrame(loop);
  }

  function draw() {
    // background
    ctx.fillStyle = COLORS[0];
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // grid faint
    ctx.strokeStyle = 'rgba(255,255,255,0.06)';
    for (let x = 0; x <= CFG.cols; x++) {
      ctx.beginPath();
      ctx.moveTo(x * CELL, 0);
      ctx.lineTo(x * CELL, CFG.rows * CELL);
      ctx.stroke();
    }
    for (let y = 0; y <= CFG.rows; y++) {
      ctx.beginPath();
      ctx.moveTo(0, y * CELL);
      ctx.lineTo(CFG.cols * CELL, y * CELL);
      ctx.stroke();
    }

    // board blocks
    for (let y = 0; y < CFG.rows; y++) {
      for (let x = 0; x < CFG.cols; x++) {
        const v = board[y][x];
        if (v === 0) continue;
        drawBlock(ctx, x, y, v);
      }
    }

    // current piece
    if (current) {
      for (let y = 0; y < 4; y++) {
        for (let x = 0; x < 4; x++) {
          if (!current.m[y][x]) continue;
          const bx = current.x + x;
          const by = current.y + y;
          if (by < 0) continue;
          drawBlock(ctx, bx, by, current.id);
        }
      }
    }
  }

  function drawNext() {
    nctx.clearRect(0, 0, nextCanvas.width, nextCanvas.height);
    // background
    nctx.fillStyle = 'rgba(15,17,21,0.6)';
    nctx.fillRect(0, 0, nextCanvas.width, nextCanvas.height);

    if (!next) return;

    // compute bounding box of next piece
    let minX = 4, minY = 4, maxX = 0, maxY = 0;
    for (let y = 0; y < 4; y++) for (let x = 0; x < 4; x++) {
      if (next.m[y][x]) { minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x); maxY = Math.max(maxY, y); }
    }
    const bw = (maxX - minX + 1);
    const bh = (maxY - minY + 1);

    const pad = 8;
    const c = Math.floor((Math.min(nextCanvas.width, nextCanvas.height) - pad * 2) / Math.max(bw, bh));
    const ox = Math.floor((nextCanvas.width - bw * c) / 2);
    const oy = Math.floor((nextCanvas.height - bh * c) / 2);

    for (let y = 0; y < 4; y++) {
      for (let x = 0; x < 4; x++) {
        if (!next.m[y][x]) continue;
        const px = ox + (x - minX) * c;
        const py = oy + (y - minY) * c;
        drawMini(nctx, px, py, c, next.id);
      }
    }
  }

  function drawBlock(c2d, gx, gy, id) {
    const x = gx * CELL;
    const y = gy * CELL;

    // base
    c2d.fillStyle = COLORS[id];
    c2d.fillRect(x + 1, y + 1, CELL - 2, CELL - 2);

    // highlight (fake 3D)
    c2d.fillStyle = 'rgba(255,255,255,0.20)';
    c2d.fillRect(x + 2, y + 2, CELL - 4, Math.max(2, Math.floor(CELL * 0.22)));

    // shadow
    c2d.fillStyle = 'rgba(0,0,0,0.28)';
    c2d.fillRect(x + 2, y + CELL - Math.max(3, Math.floor(CELL * 0.18)) - 2, CELL - 4, Math.max(3, Math.floor(CELL * 0.18)));

    // border
    c2d.strokeStyle = 'rgba(255,255,255,0.10)';
    c2d.strokeRect(x + 1.5, y + 1.5, CELL - 3, CELL - 3);
  }

  function drawMini(c2d, x, y, s, id) {
    c2d.fillStyle = COLORS[id];
    c2d.fillRect(x + 1, y + 1, s - 2, s - 2);
    c2d.fillStyle = 'rgba(255,255,255,0.20)';
    c2d.fillRect(x + 2, y + 2, s - 4, Math.max(2, Math.floor(s * 0.22)));
    c2d.strokeStyle = 'rgba(255,255,255,0.14)';
    c2d.strokeRect(x + 1.5, y + 1.5, s - 3, s - 3);
  }

  function updateStats() {
    elScore.textContent = String(score);
    elLines.textContent = String(lines);
    elLevel.textContent = String(level);
    elBest.textContent  = String(best);
  }

  function loadBest() {
    try {
      const v = localStorage.getItem(bestKey);
      const n = v ? parseInt(v, 10) : 0;
      return Number.isFinite(n) ? Math.max(0, n) : 0;
    } catch (_) {
      return 0;
    }
  }

  function saveBestIfNeeded(force = false) {
    if (!force && score <= best) return;
    if (score > best) best = score;
    try { localStorage.setItem(bestKey, String(best)); } catch (_) {}
    elBest.textContent = String(best);
  }

  /* ========= Input ========= */

  function onKeyDown(e) {
    const k = e.key;
    if (['ArrowLeft','ArrowRight','ArrowDown','ArrowUp',' ','Spacebar','z','Z','x','X','p','P','w','W','a','A','s','S','d','D'].includes(k)) {
      e.preventDefault();
    }

    if (k === 'p' || k === 'P') {
      if (running && !gameOver) togglePause();
      return;
    }

    if (!running) {
      // allow start by Enter/Space
      if (k === 'Enter' || k === ' ' || k === 'Spacebar') startGame();
      return;
    }
    if (paused || gameOver) return;

    if (k === 'ArrowLeft' || k === 'a' || k === 'A') {
      if (!keyState.left) {
        move(-1, 0);
        keyState.left = true;
        leftHeldSince = performance.now();
        leftTimer = 0;
      }
    } else if (k === 'ArrowRight' || k === 'd' || k === 'D') {
      if (!keyState.right) {
        move(1, 0);
        keyState.right = true;
        rightHeldSince = performance.now();
        rightTimer = 0;
      }
    } else if (k === 'ArrowDown' || k === 's' || k === 'S') {
      keyState.down = true;
      // instant soft tick
      softDropTick();
    } else if (k === 'ArrowUp' || k === 'w' || k === 'W' || k === 'x' || k === 'X') {
      rotate('cw');
    } else if (k === 'z' || k === 'Z') {
      rotate('ccw');
    } else if (k === ' ' || k === 'Spacebar') {
      hardDrop();
    }
    updateStats();
    saveBestIfNeeded();
  }

  function onKeyUp(e) {
    const k = e.key;
    if (k === 'ArrowLeft' || k === 'a' || k === 'A') keyState.left = false;
    if (k === 'ArrowRight' || k === 'd' || k === 'D') keyState.right = false;
    if (k === 'ArrowDown' || k === 's' || k === 'S') keyState.down = false;
  }

  function handleHeldKeys(ts) {
    // LEFT
    if (keyState.left) {
      if (ts - leftHeldSince >= CFG.dasMs) {
        leftTimer += (ts - (lastTs));
        if (leftTimer >= CFG.arrMs) {
          leftTimer = 0;
          move(-1, 0);
        }
      }
    } else {
      leftTimer = 0;
    }

    // RIGHT
    if (keyState.right) {
      if (ts - rightHeldSince >= CFG.dasMs) {
        rightTimer += (ts - (lastTs));
        if (rightTimer >= CFG.arrMs) {
          rightTimer = 0;
          move(1, 0);
        }
      }
    } else {
      rightTimer = 0;
    }
  }

  /* ========= Touch buttons ========= */
  function bindTouchButtons() {
    const btns = document.querySelectorAll('.tbtn');
    btns.forEach(b => {
      const act = b.getAttribute('data-act');
      const fire = () => {
        if (!running) { startGame(); return; }
        if (paused) { togglePause(); return; }
        if (gameOver) return;

        if (act === 'left') move(-1,0);
        else if (act === 'right') move(1,0);
        else if (act === 'down') softDropTick();
        else if (act === 'rot') rotate('cw');
        else if (act === 'drop') hardDrop();

        updateStats();
        saveBestIfNeeded();
        draw();
      };

      // click
      b.addEventListener('click', (e) => { e.preventDefault(); fire(); }, { passive: false });

      // touchstart for faster feel
      b.addEventListener('touchstart', (e) => { e.preventDefault(); fire(); }, { passive: false });
    });
  }

  /* ========= Buttons ========= */
  btnStart.addEventListener('click', (e) => { e.preventDefault(); startGame(); });
  btnPause.addEventListener('click', (e) => { e.preventDefault(); togglePause(); });
  btnReset.addEventListener('click', (e) => { e.preventDefault(); resetGame(); });

  window.addEventListener('keydown', onKeyDown, { passive: false });
  window.addEventListener('keyup', onKeyUp, { passive: true });

  function init() {
    elBest.textContent = String(best);
    resetGame();
    bindTouchButtons();
    draw();
  }
  // mobile Start/Pause mirror (optional)
  const mStart = document.getElementById('tMStart');
  const mPause = document.getElementById('tMPause');

  if (mStart) mStart.addEventListener('click', (e) => { e.preventDefault(); btnStart.click(); });
  if (mPause) mPause.addEventListener('click', (e) => { e.preventDefault(); btnPause.click(); });

  // keep mobile pause button state in sync
  const syncMobileBtns = () => {
    if (!mPause) return;
    mPause.disabled = btnPause.disabled;
    mPause.textContent = btnPause.textContent;
  };

  // call sync on init and after start/pause/reset
  btnStart.addEventListener('click', syncMobileBtns);
  btnPause.addEventListener('click', syncMobileBtns);
  btnReset.addEventListener('click', syncMobileBtns);
  init();
})();