/* field-kit.jsx — общие примитивы «поля» (опора B, KSH-355/356).
   Единый дом для сетки и визуальных пикеров, чтобы ops-field и lk-arrange
   не дублировали одну и ту же математику/виджеты. Модуль чистый: только
   window.React + window.Ic, грузится до ops-field и lk-arrange.

   Экспорт (window.FieldKit):
     nodeCols(node,total) tierOfCols(cols,total) packWithGaps(list,total)
     ColPicker({value,max,onChange,label})   — визуальный выбор ширины (клетки)
     RowPicker({value,max,onChange,label})    — визуальный выбор высоты (ступеньки + «A») */
(function () {
  const R = window.React;
  const Ic = window.Ic;
  if (!R) return;
  const FIELD_COLS = 6;

  /* колонки узла: поле cols; legacy size S/M/L → 2/4/полная */
  const nodeCols = (n, total) => {
    const max = Math.max(1, total || FIELD_COLS);
    if (typeof n.cols === 'number' && n.cols > 0) return Math.min(Math.max(1, n.cols), max);
    if (n.size === 'L') return max;
    if (n.size === 'M') return Math.min(4, max);
    if (n.size === 'S') return Math.min(2, max);
    if (n.type === 'block' && typeof n.w === 'number') return Math.min(Math.max(1, n.w), max);
    return Math.min(2, max);
  };

  /* тир контента по доле ширины */
  const tierOfCols = (cols, total) => {
    const t = Math.max(1, total || FIELD_COLS);
    const c = Math.min(cols, t);
    if (c >= t) return 2;
    if (c >= Math.ceil(t * 0.5)) return 1;
    return 0;
  };

  /* Слот «+Добавить» — единственный и всегда в КОНЦЕ группы (KSH-357).
     Промежуточных дыр не добавляем: перенос между виджетами идёт по наведению
     на сам виджет, а «+» остаётся одной понятной точкой «добавить/в конец».
     cols = остаток последнего ряда; если ряд заполнен ровно — на всю ширину. */
  const packWithGaps = (list, total) => {
    const arr = list || [];
    const items = arr.map((n) => ({ kind: 'node', node: n }));
    if (!arr.length) return items;
    let used = 0;
    arr.forEach((n) => {
      const c = nodeCols(n, total);
      if (used > 0 && used + c > total) used = 0;
      used += c;
      if (used >= total) used = 0;
    });
    const rem = used > 0 ? total - used : total;
    items.push({ kind: 'gap', cols: rem, at: arr.length });
    return items;
  };

  /* визуальный выбор числа колонок (1…max) */
  function ColPicker({ value, max, onChange, label }) {
    const m = Math.max(1, max || FIELD_COLS);
    const v = Math.min(Math.max(1, value || 1), m);
    return (
      R.createElement('div', { className: 'of-colpick' },
        R.createElement('span', { className: 'of-colpick-frac' }, v + '/' + m),
        R.createElement('div', { className: 'of-colpick-track', role: 'group', 'aria-label': label || 'Колонки' },
          Array.from({ length: m }).map((_, i) =>
            R.createElement('button', {
              key: i, type: 'button', className: 'of-colpick-cell' + (i < v ? ' is-on' : ''),
              'aria-pressed': v === i + 1, title: (i + 1) + ' из ' + m, 'aria-label': (i + 1) + ' из ' + m,
              onClick: () => onChange(i + 1),
            }))),
        R.createElement('span', { className: 'of-colpick-end' }, m === FIELD_COLS ? 'вся' : m))
    );
  }

  /* визуальный выбор высоты (авто | 1…max строк) — клетки-«ступеньки». value null/0 = авто */
  function RowPicker({ value, max, onChange, label }) {
    const m = Math.max(1, max || 3);
    const v = Math.min(Math.max(0, value || 0), m);
    return (
      R.createElement('div', { className: 'of-colpick of-rowpick' },
        R.createElement('span', { className: 'of-colpick-frac' }, v ? v + '×' : 'авто'),
        R.createElement('div', { className: 'of-colpick-track of-rowpick-track', role: 'group', 'aria-label': label || 'Высота' },
          R.createElement('button', {
            type: 'button', className: 'of-rowpick-auto' + (!v ? ' is-on' : ''),
            'aria-pressed': !v, title: 'Авто-высота', 'aria-label': 'Авто-высота', onClick: () => onChange(null),
          }, 'A'),
          Array.from({ length: m }).map((_, i) =>
            R.createElement('button', {
              key: i, type: 'button', className: 'of-colpick-cell of-rowpick-cell' + (v >= i + 1 ? ' is-on' : ''),
              'aria-pressed': v === i + 1, title: (i + 1) + ' стр', 'aria-label': (i + 1) + ' строк',
              style: { height: 10 + i * 5 }, onClick: () => onChange(i + 1),
            }))),
        R.createElement('span', { className: 'of-colpick-end' }, m + ' стр'))
    );
  }

  /* masonry: вертикальная упаковка внутри группы (KSH-367). Ширины не трогаем
     (grid-column span), высоту задаём grid-row span из измеренной высоты карточки.
     Мелкий grid-auto-rows + auto-flow dense (в CSS) убирают «воздух» под короткими
     виджетами: под ними встаёт следующий. Меряем внутренний .of-w (height:auto),
     чтобы не упереться в высоту самого трека. */
  function useMasonry(gridRef, opts) {
    const rowUnit = (opts && opts.rowUnit) || 8;
    R.useLayoutEffect(() => {
      const grid = gridRef && gridRef.current;
      if (!grid || !window.ResizeObserver) return undefined;
      let t = 0;
      const relayout = () => {
        const gap = parseFloat(getComputedStyle(grid).rowGap) || 0;
        Array.from(grid.children).forEach((cell) => {
          if (!cell.classList || !cell.classList.contains('of-cell')) return;
          const inner = cell.firstElementChild || cell;
          const h = inner.getBoundingClientRect().height;
          if (!h) return;
          const span = Math.max(1, Math.round((h + gap) / (rowUnit + gap)));
          cell.style.gridRowEnd = 'span ' + span;
        });
      };
      /* setTimeout, а не rAF: rAF не срабатывает в фоновой вкладке, а раскладку
         надо посчитать и там (и для проверки в headless-превью). */
      const schedule = () => { clearTimeout(t); t = setTimeout(relayout, 16); };
      const ro = new ResizeObserver(schedule);
      const observeCells = () => Array.from(grid.children).forEach((c) => {
        if (c.classList && c.classList.contains('of-cell') && c.firstElementChild) ro.observe(c.firstElementChild);
      });
      ro.observe(grid); observeCells();
      const mo = window.MutationObserver ? new MutationObserver(() => { observeCells(); schedule(); }) : null;
      if (mo) mo.observe(grid, { childList: true });
      relayout();
      return () => { clearTimeout(t); ro.disconnect(); if (mo) mo.disconnect(); };
    }, []);
  }

  window.FieldKit = window.FieldKit || { nodeCols, tierOfCols, packWithGaps, ColPicker, RowPicker, useMasonry, FIELD_COLS };
})();
