/* ============================================================
   paginate.jsx — measure blocks and distribute across A4 pages
   ============================================================ */

/* A4 dimensions at 96 DPI */
const A4_PX = {
  portrait:  { w: 794,  h: 1123 },
  landscape: { w: 1123, h: 794 },
};

/* ------------------------------------------------------------
   Off-screen measurement host — we render each block once,
   measure its height, then throw it away. Re-used across calls.
   ------------------------------------------------------------ */
let _measureHost = null;
function getMeasureHost(pageStyle, columnWidth) {
  if (!_measureHost) {
    _measureHost = document.createElement('div');
    _measureHost.className = 'paginate-measure-host';
    _measureHost.style.cssText = `
      position: fixed;
      left: -20000px;
      top: 0;
      visibility: hidden;
      pointer-events: none;
      z-index: -1;
    `;
    document.body.appendChild(_measureHost);
  }
  // apply same CSS vars / fonts as the real page, so measurements are accurate
  _measureHost.style.width = columnWidth + 'px';
  Object.keys(pageStyle || {}).forEach(k => {
    if (k.startsWith('--')) _measureHost.style.setProperty(k, pageStyle[k]);
  });
  _measureHost.className = 'paginate-measure-host page';
  return _measureHost;
}

/* ------------------------------------------------------------
   measureBlocks — render each block once in the measure host,
   record its pixel height, return an array of {id, height}.
   Uses ReactDOM to render into the host synchronously.
   ------------------------------------------------------------ */
function measureBlocks(blocks, pageStyle, columnWidth) {
  const host = getMeasureHost(pageStyle, columnWidth);
  // render each block as a plain static element tree using BlockRenderer-lite
  // we can't use React for this (too async), so we build the DOM manually using
  // a small static-render helper that mirrors blocks.jsx output shape.
  host.innerHTML = '';
  const results = [];
  blocks.forEach((b, i) => {
    const el = document.createElement('div');
    el.className = 'doc-block';
    el.style.marginBottom = '0';
    el.innerHTML = staticBlockHTML(b);
    host.appendChild(el);
  });
  // measure after layout settles (synchronous reflow)
  const items = Array.from(host.children);
  blocks.forEach((b, i) => {
    const el = items[i];
    if (!el) { results.push({ id: b.id, height: 100, block: b }); return; }
    const style = window.getComputedStyle(el);
    const mb = parseFloat(style.marginBottom) || 0;
    const mt = parseFloat(style.marginTop)    || 0;
    results.push({
      id: b.id,
      height: el.getBoundingClientRect().height + mb + mt,
      block: b,
    });
  });
  return results;
}

/* staticBlockHTML — a fast, no-React stringification of a block for measurement.
   Does NOT need to handle comments/suggestions/interactions — only visual bulk. */
function staticBlockHTML(b) {
  const esc = (s) => String(s || '').replace(/[&<>"']/g, c => ({
    '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;',
  }[c]));
  switch (b.type) {
    case 'h1': return `<h1 class="doc-h1">${esc(b.text)}</h1>`;
    case 'h2': return `<h2 class="doc-h2">${esc(b.text)}</h2>`;
    case 'h3': return `<h3 class="doc-h3">${esc(b.text)}</h3>`;
    case 'p':  return `<p class="doc-p">${esc(b.text)}</p>`;
    case 'numlist': return `<ol class="doc-numlist level-${b.level || 1}">${
      (b.items || []).map(it => `<li><span>${esc(it)}</span></li>`).join('')
    }</ol>`;
    case 'ul': return `<ul class="doc-ul">${
      (b.items || []).map(it => `<li><span>${esc(it)}</span></li>`).join('')
    }</ul>`;
    case 'callout': return `<div class="doc-callout" data-tone="${esc(b.tone)}">
      <div class="callout-label">${esc(b.label)}</div>
      <div class="callout-title">${esc(b.title)}</div>
      <p class="callout-body">${esc(b.body)}</p>
    </div>`;
    case 'quote': return `<div class="doc-quote">
      <blockquote><span>${esc(b.text)}</span></blockquote>
      <cite>${esc(b.cite)}</cite>
    </div>`;
    case 'photos': {
      const cols = b.cols || 2;
      const cells = Array.from({length: cols}).map(() => '<div class="photo-cell" style="aspect-ratio:4/3;"></div>').join('');
      return `<div class="doc-photos" data-cols="${cols}" style="display:grid;grid-template-columns:repeat(${cols},1fr);gap:14px;">${cells}</div>`;
    }
    case 'diagram': return `<div class="doc-diagram" style="height:220px;"></div>`;
    case 'table': {
      const th = (b.headers || []).map(h => `<th>${esc(h)}</th>`).join('');
      const trs = (b.rows || []).map(r =>
        '<tr>' + r.map(c => `<td>${esc(c)}</td>`).join('') + '</tr>'
      ).join('');
      return `<table class="doc-table"><thead><tr>${th}</tr></thead><tbody>${trs}</tbody></table>`;
    }
    case 'kpis': return `<div class="doc-kpis" style="display:flex;gap:20px;">${
      (b.items || []).map(k => `<div class="kpi"><span class="kpi-n">${esc(k.n)}</span><span class="kpi-unit">${esc(k.unit)}</span><div class="kpi-label">${esc(k.label)}</div></div>`).join('')
    }</div>`;
    case 'pagebreak': return `<hr class="page-break">`;
    default: return `<div>${esc(b.type)}</div>`;
  }
}

/* ------------------------------------------------------------
   paginate — distribute measured blocks across pages, optionally
   balancing across 2 columns within each page.

   Returns: Array<Page>
     Page = { columns: Array<Column> }
     Column = Array<blockId>

   contentHeight = available height per column (page height minus
                   margins, header, footer).
   ------------------------------------------------------------ */
function paginate(measured, { contentHeight, columns = 1, isPagebreak = (b) => b.type === 'pagebreak' }) {
  const pages = [];
  let currentPage = makePage(columns);
  let currentCol = 0;

  const push = (item) => {
    currentPage.columns[currentCol].push(item);
    currentPage.usedHeight[currentCol] += item.height;
  };

  const nextCol = () => {
    if (currentCol < columns - 1) {
      currentCol += 1;
    } else {
      pages.push(currentPage);
      currentPage = makePage(columns);
      currentCol = 0;
    }
  };

  for (const item of measured) {
    // Forced page break
    if (isPagebreak(item.block)) {
      // close this page (even if we were in col 0)
      pages.push(currentPage);
      currentPage = makePage(columns);
      currentCol = 0;
      continue;
    }

    // Does it fit in current column?
    const used = currentPage.usedHeight[currentCol];
    const fits = used + item.height <= contentHeight;
    const isFirstInCol = currentPage.columns[currentCol].length === 0;

    if (fits) {
      push(item);
    } else if (isFirstInCol) {
      // block is taller than full column — push it anyway (single-block overflow)
      // (later we can split, but for now we accept it's ugly)
      push(item);
      nextCol();
    } else {
      // move to next column / page
      nextCol();
      push(item);
    }
  }

  pages.push(currentPage);
  return pages.filter(p => p.columns.some(c => c.length > 0));
}

function makePage(columns) {
  return {
    columns: Array.from({ length: columns }, () => []),
    usedHeight: Array.from({ length: columns }, () => 0),
  };
}

/* ------------------------------------------------------------
   computeContentHeight — given meta + orientation, return the
   available pixel height per column inside a page.
   ------------------------------------------------------------ */
function computeContentHeight(meta) {
  const { h } = A4_PX[meta.orientation === 'landscape' ? 'landscape' : 'portrait'];
  const margin = (meta.margin || 72);
  const headerH = meta.showHeader ? 64 : 0;
  const footerH = meta.showFooter ? 42 : 0;
  return h - margin * 2 - headerH - footerH;
}

function computeContentWidth(meta, columns) {
  const { w } = A4_PX[meta.orientation === 'landscape' ? 'landscape' : 'portrait'];
  const margin = (meta.margin || 72);
  const available = w - margin * 2;
  const gap = 28;
  return (available - gap * (columns - 1)) / columns;
}

Object.assign(window, {
  A4_PX,
  measureBlocks,
  paginate,
  computeContentHeight,
  computeContentWidth,
});
