/**
 * table-fit.js — 표 열 너비 내용 기반 최적화 (공용)
 *
 * 셀 내용(글자 종류별 가로폭 추정)으로 각 열의 자연폭을 구하고, 본문 가용폭(total)에
 * 맞춰 정수 폭으로 배분한다. 항상 합 = total → 페이지를 넘지 않음.
 *
 * 단위 무관(자연폭은 상대값, allocate 가 total 단위로 환산). 브라우저(window.TableFit)
 * + node(module.exports) 양쪽.
 *
 * 사용처: md2hwpx(.md→.hwpx 표 변환), 한글 표 딸깍(엑셀/CSV→한글 합성).
 *   → doc/reference/hancom-clipboard-hwpjson.md (HWPUNIT·페이지폭 기준)
 */
(function (root, factory) {
  'use strict';
  const api = factory();
  if (root) root.TableFit = api;
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
})(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : this), function () {
  'use strict';

  /** 글자 1개의 가로폭 비율 (글자 높이=1em 기준) */
  function charEm(ch) {
    if (ch === ' ') return 0.34;
    const code = ch.codePointAt(0);
    // 전각: 한글 자모·완성형·한자·가나·전각기호
    if ((code >= 0x1100 && code <= 0x11FF) ||  // 한글 자모
        (code >= 0x3000 && code <= 0x303F) ||  // CJK 기호
        (code >= 0x3040 && code <= 0x30FF) ||  // 가나
        (code >= 0x3400 && code <= 0x9FFF) ||  // CJK 한자
        (code >= 0xAC00 && code <= 0xD7A3) ||  // 한글 완성형
        (code >= 0xF900 && code <= 0xFAFF) ||  // CJK 호환 한자
        (code >= 0xFF00 && code <= 0xFFEF)) {   // 전각 영숫자
      return 1.0;
    }
    if (/[0-9A-Z@#%&Ww]/.test(ch)) return 0.62;  // 숫자·대문자·넓은 글자
    if (/[ilIjtf.,:;'!|()\[\]{}\/\\ \-]/.test(ch)) return 0.30;  // 좁은 글자
    return 0.52;  // 일반 ASCII 소문자
  }

  /** 문자열(한 줄) 가로폭(em) */
  function textWidthEm(text) {
    let w = 0;
    for (const ch of String(text == null ? '' : text)) w += charEm(ch);
    return w;
  }

  /** 셀(여러 줄 가능) 자연폭(em) = 가장 긴 줄 */
  function cellWidthEm(text) {
    let mx = 0;
    for (const line of String(text == null ? '' : text).split('\n')) {
      const w = textWidthEm(line);
      if (w > mx) mx = w;
    }
    return mx;
  }

  /**
   * grid(2D 배열, 행×열의 셀 텍스트) → 열별 자연폭(em) 배열.
   * 병합 없는 단순 그리드 가정(호출자가 단일 셀 텍스트만 전달).
   */
  function naturalEm(grid, colCnt) {
    const nat = new Array(colCnt).fill(0);
    for (const row of (grid || [])) {
      if (!row) continue;
      for (let c = 0; c < colCnt; c++) {
        const w = cellWidthEm(row[c]);
        if (w > nat[c]) nat[c] = w;
      }
    }
    return nat;
  }

  /**
   * 자연폭 배열 → total 에 맞춘 정수 폭 배열 (합 == total).
   * @param {number[]} natural 열별 자연폭(임의 단위)
   * @param {number}   total   배분할 총폭 (HWPUNIT 등)
   * @param {Object}  [opts] { min: 최소폭, maxFrac: 한 열 상한 비율(0~1), padEm: 자연폭에 더할 여백(em), emUnit: em→폭 환산 }
   */
  function allocate(natural, total, opts) {
    opts = opts || {};
    const n = natural.length;
    if (n === 0) return [];
    if (n === 1) return [Math.round(total)];

    const emUnit = opts.emUnit || 1;
    const padEm = opts.padEm || 0;
    const min = opts.min != null ? opts.min : Math.max(1, Math.floor(total / (n * 3.2)));
    const maxW = opts.maxFrac ? Math.floor(total * opts.maxFrac) : total;

    // 1) (자연폭+패딩)×emUnit, [min, maxW] 클램프
    let w = natural.map(x => {
      const raw = Math.round((x + padEm) * emUnit);
      return Math.max(min, Math.min(maxW, raw));
    });
    let sum = w.reduce((a, b) => a + b, 0);
    if (sum <= 0) { const e = Math.floor(total / n); w = new Array(n).fill(e); sum = e * n; }

    if (sum <= total) {
      // 비례 확대로 total 채움
      const scale = total / sum;
      w = w.map(x => Math.max(min, Math.round(x * scale)));
    } else {
      // 축소: 각 열 min 보장 + 나머지를 (w-min) 비례 배분
      const slack = total - n * min;
      if (slack <= 0) {
        const e = Math.floor(total / n); w = new Array(n).fill(e);
      } else {
        const over = w.map(x => x - min);
        const overSum = over.reduce((a, b) => a + b, 0) || 1;
        w = over.map(o => min + Math.round(o / overSum * slack));
      }
    }

    // 반올림 드리프트 보정 → 합 정확히 total (남는 ±는 가장 넓은/좁은 열에서 조정)
    let diff = total - w.reduce((a, b) => a + b, 0);
    let guard = 0;
    while (diff !== 0 && guard++ < 200000) {
      const step = diff > 0 ? 1 : -1;
      let idx = 0;
      for (let k = 1; k < n; k++) {
        if (step > 0 ? (w[k] < w[idx]) : (w[k] > w[idx])) idx = k;
      }
      w[idx] += step; diff -= step;
    }
    return w;
  }

  /** 편의: grid + total → 정수 폭 배열 */
  function fit(grid, colCnt, total, opts) {
    return allocate(naturalEm(grid, colCnt), total, opts);
  }

  return { charEm, textWidthEm, cellWidthEm, naturalEm, allocate, fit };
});
