/**
 * levelGridCodec — конвертер между LEVELS-объектом (из скрипта проекта 265)
 * и 2D-сеткой для редактора уровней.
 *
 * 2D-сетка — это плоское представление уровня, на котором редактор рисует тайлы.
 * Координаты в сетке: col (X), row (Y, снизу-вверх).
 *   col 0 = spawnX уровня (левая граница)
 *   row 0 = пол (y=0 в мире)
 *   row 1..N = воздух
 *
 * Тайлы:
 *   '.'  — пусто
 *   '#'  — пол / твёрдый блок (cotton-green сверху, stone внизу — генерируется при экспорте)
 *   '^'  — шип на полу
 *   'v'  — шип на потолке (для gravity flip)
 *   'o'  — jumpOrb (Space в воздухе)
 *   'T'  — trampoline (жёлтый pad на полу)
 *   'S'  — speedPad (голубой pad на полу)
 *   'G'  — gravity portal (флип)
 *   '='  — moving platform
 *   'C'  — ceiling block (потолок для flip-секции)
 *   's'  — step / hanging platform (одиночная платформа в воздухе)
 *   'P'  — start portal (визуал, для редактора)
 *   'F'  — finish portal (визуал)
 *
 * Размеры:
 *   - ширина сетки = width уровня (обычно 240-340 колонок)
 *   - высота сетки = GRID_HEIGHT (12 рядов — достаточно для всех flip/высокой геометрии)
 */

export const GRID_HEIGHT = 12;

export const TILES = {
  EMPTY: '.',
  FLOOR: '#',
  SPIKE: '^',
  SPIKE_TOP: 'v',
  JUMPORB: 'o',
  TRAMP: 'T',
  SPEEDPAD: 'S',
  GRAVITY: 'G',
  MOVING: '=',
  CEILING: 'C',
  STEP: 's',
  START: 'P',
  FINISH: 'F',
};

// icon — имя SVG-иконки (см. Icon.jsx), не эмодзи.
export const TILE_META = {
  [TILES.EMPTY]:     { label: 'Пусто',         icon: null,                color: '#eef' },
  [TILES.FLOOR]:     { label: 'Блок',          icon: 'tile-floor',        color: '#5a4a3a' },
  [TILES.SPIKE]:     { label: 'Шип',           icon: 'tile-spike',        color: '#e94545' },
  [TILES.SPIKE_TOP]: { label: 'Шип сверху',    icon: 'tile-spike',        color: '#a44' },
  [TILES.JUMPORB]:   { label: 'Jump Orb',      icon: 'tile-orb',          color: '#ffe44a' },
  [TILES.TRAMP]:     { label: 'Трамплин',      icon: 'tile-orb',          color: '#ff9533' },
  [TILES.SPEEDPAD]:  { label: 'SpeedPad',      icon: 'tile-pad',          color: '#55ccff' },
  [TILES.GRAVITY]:   { label: 'Грав. портал',  icon: 'tile-gravity',      color: '#b266ff' },
  [TILES.MOVING]:    { label: 'Движ. платф.',  icon: 'tile-moving',       color: '#22cc88' },
  [TILES.CEILING]:   { label: 'Потолок',       icon: 'tile-floor',        color: '#446' },
  [TILES.STEP]:      { label: 'Ступенька',     icon: 'tile-step',         color: '#7755aa' },
  [TILES.START]:     { label: 'Старт',         icon: 'door',              color: '#3a5a9a' },
  [TILES.FINISH]:    { label: 'Финиш',         icon: 'prim-finish',       color: '#ffaa00' },
};

/** Создать пустую сетку нужной ширины (заполнена пол: row=0, остальное пусто). */
export function createEmptyGrid(width) {
  const grid = [];
  for (let row = 0; row < GRID_HEIGHT; row++) {
    grid.push(new Array(width).fill(TILES.EMPTY));
  }
  return grid;
}

/**
 * Декодировать один уровень в 2D-сетку.
 * level — объект из LEVELS[]: { id, name, width, spawnX, finishX, holes, spikes,
 *           spikesTop, tramps, speedPads, jumpOrbs, gravityFlips, movingPlatforms,
 *           steps, hangs, ceilings, doubleJump, ceilingY }
 *
 * Возвращает { width, grid, meta } где meta хранит несетевые поля (doubleJump, ceilingY, name).
 */
export function levelToGrid(level) {
  const sx = level.spawnX || 0;
  const width = level.width || 240;
  const grid = createEmptyGrid(width);

  // 1) Пол — заполняем весь нижний ряд блоками
  for (let col = 0; col < width; col++) {
    grid[0][col] = TILES.FLOOR;
  }
  // 2) Холы — пропуски в полу (holes хранятся относительно spawnX)
  for (const [a, b] of (level.holes || [])) {
    for (let col = a; col < b && col < width; col++) {
      if (col >= 0) grid[0][col] = TILES.EMPTY;
    }
  }
  // 3) Шипы на полу (row=1 — на верхушке пола)
  for (const sxoff of (level.spikes || [])) {
    if (sxoff >= 0 && sxoff < width) grid[1][sxoff] = TILES.SPIKE;
  }
  // 4) Шипы на потолке
  for (const sxoff of (level.spikesTop || [])) {
    if (sxoff >= 0 && sxoff < width) grid[6][sxoff] = TILES.SPIKE_TOP;
  }
  // 5) Трамплины (могут быть массивом чисел или объектов {x:worldX})
  for (const t of (level.tramps || [])) {
    const rel = typeof t === 'number' ? t : (t.x != null ? t.x - sx : 0);
    if (rel >= 0 && rel < width) grid[1][rel] = TILES.TRAMP;
  }
  // 6) Speed pads
  for (const t of (level.speedPads || [])) {
    const rel = typeof t === 'number' ? t : (t.x != null ? t.x - sx : 0);
    if (rel >= 0 && rel < width) grid[1][rel] = TILES.SPEEDPAD;
  }
  // 7) Jump orbs ({worldX, worldY})
  for (const orb of (level.jumpOrbs || [])) {
    const rel = (orb.worldX != null ? orb.worldX - sx : 0) | 0;
    const row = Math.max(2, Math.min(GRID_HEIGHT - 1, Math.round(orb.worldY || 3.5)));
    if (rel >= 0 && rel < width) grid[row][rel] = TILES.JUMPORB;
  }
  // 8) Gravity portals ({worldX})
  for (const gf of (level.gravityFlips || [])) {
    const rel = (gf.worldX != null ? gf.worldX - sx : 0) | 0;
    if (rel >= 0 && rel < width) {
      // занимает по вертикали 2 ряда (видимо как столб)
      grid[2][rel] = TILES.GRAVITY;
      grid[3][rel] = TILES.GRAVITY;
    }
  }
  // 9) Moving platforms ({worldX, yMin, yMax, period})
  for (const mp of (level.movingPlatforms || [])) {
    const rel = (mp.worldX != null ? mp.worldX - sx : 0) | 0;
    const row = Math.max(1, Math.min(GRID_HEIGHT - 1, Math.round(mp.yMin || 1)));
    if (rel >= 0 && rel < width) grid[row][rel] = TILES.MOVING;
  }
  // 10) Steps ({x:worldX, y, sx, sy})
  for (const st of (level.steps || [])) {
    const rel = (st.x != null ? st.x - sx : 0) | 0;
    const row = Math.max(1, Math.min(GRID_HEIGHT - 1, Math.round(st.y || 2)));
    if (rel >= 0 && rel < width) grid[row][rel] = TILES.STEP;
  }
  // 11) Hangs (подвесные платформы)
  for (const h of (level.hangs || [])) {
    const rel = (h.x != null ? h.x - sx : 0) | 0;
    const row = Math.max(1, Math.min(GRID_HEIGHT - 1, Math.round(h.y || 3)));
    if (rel >= 0 && rel < width) grid[row][rel] = TILES.STEP;
  }
  // 12) Ceilings ({worldX, length})
  for (const cl of (level.ceilings || [])) {
    const rel = (cl.worldX != null ? cl.worldX - sx : 0) | 0;
    const len = cl.length || 1;
    const row = Math.max(2, Math.min(GRID_HEIGHT - 1, Math.round(level.ceilingY || 7)));
    for (let i = 0; i < len; i++) {
      const c = rel + i;
      if (c >= 0 && c < width) grid[row][c] = TILES.CEILING;
    }
  }
  // 13) Старт/финиш — визуальные маркеры
  grid[2][0] = TILES.START;
  if (width - 5 >= 0) grid[2][width - 5] = TILES.FINISH;

  return {
    width,
    grid,
    meta: {
      id: level.id,
      name: level.name || `Уровень ${level.id}`,
      spawnX: sx,
      doubleJump: !!level.doubleJump,
      ceilingY: level.ceilingY || null,
    },
  };
}

/**
 * Закодировать 2D-сетку обратно в LEVELS-объект.
 * gridData = результат levelToGrid (или вручную собранный {width, grid, meta}).
 */
export function gridToLevel(gridData) {
  const { width, grid, meta } = gridData;
  const sx = meta.spawnX;

  // 1) Холы — ищем пропуски в row=0
  const holes = [];
  let holeStart = null;
  for (let col = 0; col < width; col++) {
    const isHole = grid[0][col] !== TILES.FLOOR;
    if (isHole && holeStart == null) holeStart = col;
    else if (!isHole && holeStart != null) {
      holes.push([holeStart, col]);
      holeStart = null;
    }
  }
  if (holeStart != null) holes.push([holeStart, width]);

  // 2-9) Сканируем все остальные тайлы
  const spikes = [];
  const spikesTop = [];
  const tramps = [];
  const speedPads = [];
  const jumpOrbs = [];
  const gravityFlips = [];
  const movingPlatforms = [];
  const steps = [];
  const ceilings = [];

  // gravity portal в сетке занимает 2 ряда — учитываем только верхний
  const gravitySeen = new Set();
  const ceilingSeen = new Set();   // чтобы группировать соседние ceiling в отрезки

  for (let row = 1; row < GRID_HEIGHT; row++) {
    for (let col = 0; col < width; col++) {
      const t = grid[row][col];
      if (t === TILES.EMPTY || t === TILES.START || t === TILES.FINISH) continue;
      if (t === TILES.FLOOR) continue;  // блоки в воздухе пока не поддерживаются отдельно
      switch (t) {
        case TILES.SPIKE:
          if (row === 1) spikes.push(col);
          break;
        case TILES.SPIKE_TOP:
          spikesTop.push(col);
          break;
        case TILES.TRAMP:
          if (row === 1) tramps.push(col);
          break;
        case TILES.SPEEDPAD:
          if (row === 1) speedPads.push(col);
          break;
        case TILES.JUMPORB:
          jumpOrbs.push({ worldX: sx + col, worldY: row });
          break;
        case TILES.GRAVITY:
          if (!gravitySeen.has(col)) {
            gravityFlips.push({ worldX: sx + col });
            gravitySeen.add(col);
          }
          break;
        case TILES.MOVING:
          movingPlatforms.push({
            worldX: sx + col, yMin: 1.0, yMax: Math.max(2.5, row + 1),
            period: 3.0, phase: Math.random() * 6.28,
          });
          break;
        case TILES.STEP:
          steps.push({ x: sx + col, y: row, sx: 2, sy: 0.5 });
          break;
        case TILES.CEILING:
          ceilingSeen.add(col);
          break;
        default:
          break;
      }
    }
  }

  // ceilings: группируем соседние col в отрезки
  const ceilCols = Array.from(ceilingSeen).sort((a, b) => a - b);
  let cStart = null, cPrev = null;
  for (const c of ceilCols) {
    if (cStart == null) { cStart = c; cPrev = c; continue; }
    if (c === cPrev + 1) { cPrev = c; }
    else {
      ceilings.push({ worldX: sx + cStart, length: cPrev - cStart + 1 });
      cStart = c; cPrev = c;
    }
  }
  if (cStart != null) ceilings.push({ worldX: sx + cStart, length: cPrev - cStart + 1 });

  // finishX = последний блок пола перед концом, либо width-5
  let finishX = sx + width - 5;
  for (let col = width - 1; col >= 0; col--) {
    if (grid[0][col] === TILES.FLOOR) { finishX = sx + col; break; }
  }

  return {
    id: meta.id,
    name: meta.name,
    width,
    spawnX: sx,
    finishX,
    holes,
    spikes,
    spikesTop,
    tramps,
    speedPads,
    movingPlatforms,
    jumpOrbs,
    gravityFlips,
    ceilings,
    steps,
    doubleJump: !!meta.doubleJump,
    ceilingY: meta.ceilingY || null,
  };
}

/**
 * Извлечь массив LEVELS[] из исходного кода скрипта проекта.
 * Возвращает { levels, startIdx, endIdx } где startIdx/endIdx — позиции
 * внутри строки между [ и ] массива (для последующей подстановки).
 */
export function extractLevelsFromScript(scriptCode) {
  const m = scriptCode.match(/const\s+LEVELS\s*=\s*\[/);
  if (!m) return null;
  const startIdx = m.index + m[0].length;
  let depth = 1;
  let i = startIdx;
  while (i < scriptCode.length && depth > 0) {
    const ch = scriptCode[i];
    if (ch === '[') depth++;
    else if (ch === ']') depth--;
    i++;
  }
  const endIdx = i - 1; // позиция закрывающей ']'
  const arrStr = '[' + scriptCode.slice(startIdx, endIdx) + ']';
  try {
    const levels = JSON.parse(arrStr);
    return { levels, startIdx, endIdx };
  } catch (e) {
    return null;
  }
}

/**
 * Подставить новые уровни в скрипт.
 * Возвращает обновлённый scriptCode.
 */
export function replaceLevelsInScript(scriptCode, newLevels) {
  const found = extractLevelsFromScript(scriptCode);
  if (!found) return scriptCode;
  const arrJson = JSON.stringify(newLevels);
  const inner = arrJson.slice(1, -1);  // без внешних [ ]
  return scriptCode.slice(0, found.startIdx) + inner + scriptCode.slice(found.endIdx);
}
