/* =========================================================
   三藏閣 — 본문 렌더 (단락, 각주, 좌우 대조 + 용어 링크)
   ========================================================= */

const { Icon: BIcon } = window;

/* 본문 텍스트에 박힌 <fn n="N"/> 마커 변환 + 용어 링크 */
function renderBodyHTML(htmlString) {
  /* 1) fn 마커 → 풍선 트리거 */
  let out = htmlString.replace(
    /<fn n="(\d+)"\s*\/>/g,
    (_, n) => `<button class="r-fn-marker" data-fn="${n}">${n}</button>`
  );
  /* 2) 용어 링크 */
  out = window.linkifyTerms(out);
  return { __html: out };
}
function renderSourceHTML(htmlString) {
  /* 원문에는 fn 마커가 없지만, 한자 용어 링크는 적용 */
  return { __html: window.linkifyTerms(htmlString) };
}

const sourceClass = (lang) => 'r-paragraph__source' + (lang === 'pi' ? ' r-paragraph__source--pi' : lang === 'tib' ? ' r-paragraph__source--tib' : '');

function escapeHTML(text) {
  const el = document.createElement('span');
  el.textContent = text;
  return el.innerHTML;
}

function htmlText(htmlString) {
  const el = document.createElement('div');
  el.innerHTML = htmlString || '';
  return (el.textContent || '').replace(/\s+/g, ' ').trim();
}

function numberKey(text) {
  const match = String(text || '').match(/^\s*(\d+(?:\.\d+)*)\.?(?=\s|$)/);
  return match ? match[1] : null;
}

function describeBlock(html) {
  const host = document.createElement('div');
  host.innerHTML = html || '';
  const element = host.firstElementChild;
  const tag = element?.tagName?.toLowerCase() || 'p';
  const text = htmlText(html);
  return {
    html,
    text,
    length: Math.max(1, text.replace(/\s+/g, '').length),
    numberKey: numberKey(text),
    isHeading: /^h[1-6]$/.test(tag),
  };
}

/* 외부 코퍼스의 원문은 한 <p> 안에 <br>로 이어져 있어 읽기 단위로 풀어낸다. */
function extractSourceUnits(htmlString) {
  const host = document.createElement('div');
  host.innerHTML = htmlString || '';
  const units = [];

  Array.from(host.childNodes).forEach(node => {
    if (node.nodeType === Node.TEXT_NODE) {
      const text = (node.textContent || '').trim();
      if (text) units.push(`<p>${escapeHTML(text)}</p>`);
      return;
    }
    if (node.nodeType !== Node.ELEMENT_NODE) return;
    const element = node;
    if (/<br\s*\/?\s*>/i.test(element.innerHTML)) {
      element.innerHTML.split(/<br\s*\/?\s*>/i).forEach(part => {
        if (htmlText(part)) units.push(`<p>${part.trim()}</p>`);
      });
    } else if (htmlText(element.outerHTML)) {
      units.push(element.outerHTML);
    }
  });

  if (!units.length && htmlText(htmlString)) units.push(htmlString);
  return units.map(describeBlock);
}

/* 번역의 최상위 문단·제목을 보존해 대조 행의 기준으로 삼는다. */
function extractBodyBlocks(htmlString) {
  const host = document.createElement('div');
  host.innerHTML = htmlString || '';
  let nodes = Array.from(host.childNodes);
  const onlyElement = nodes.filter(node => node.nodeType === Node.ELEMENT_NODE);
  if (onlyElement.length === 1 && nodes.every(node => node.nodeType !== Node.TEXT_NODE || !(node.textContent || '').trim())) {
    const wrapper = onlyElement[0];
    if (['DIV', 'ARTICLE', 'SECTION', 'MAIN'].includes(wrapper.tagName) && wrapper.childNodes.length > 1) {
      nodes = Array.from(wrapper.childNodes);
    }
  }

  const blocks = [];
  nodes.forEach(node => {
    if (node.nodeType === Node.TEXT_NODE) {
      const text = (node.textContent || '').trim();
      if (text) blocks.push(`<p>${escapeHTML(text)}</p>`);
    } else if (node.nodeType === Node.ELEMENT_NODE && htmlText(node.outerHTML)) {
      blocks.push(node.outerHTML);
    }
  });
  if (!blocks.length && htmlText(htmlString)) blocks.push(htmlString);
  return blocks.map(describeBlock);
}

/* Page-level collection/work titles are already rendered in the passage header.
   Remove only duplicated Pali front matter before sequence alignment. */
function removeDuplicateSourceFrontMatter(units) {
  const firstProse = units.findIndex(unit => /^Evaṁ\s+me\s+sutaṁ/iu.test(unit.text));
  if (firstProse < 0) return units;

  return units.filter((unit, index) => {
    if (index >= firstProse || unit.numberKey) return true;
    return !(/\bNikāya(?:\s+\d+(?:\.\d+)*)?$/iu.test(unit.text) || /suttaṁ?$/iu.test(unit.text));
  });
}

function groupedBySequence(longItems, shortItems) {
  const longCount = longItems.length;
  const shortCount = shortItems.length;
  if (!longCount || !shortCount || longCount < shortCount) return null;

  const totalLong = longItems.reduce((sum, item) => sum + item.length, 0);
  const totalShort = shortItems.reduce((sum, item) => sum + item.length, 0);
  const ratio = totalShort / Math.max(1, totalLong);
  const maxGroup = Math.min(48, longCount - shortCount + 1);
  const dp = Array.from({ length: shortCount + 1 }, () => Array(longCount + 1).fill(Infinity));
  const previous = Array.from({ length: shortCount + 1 }, () => Array(longCount + 1).fill(0));
  const prefix = [0];
  longItems.forEach(item => prefix.push(prefix[prefix.length - 1] + item.length));
  dp[0][0] = 0;

  for (let shortIndex = 1; shortIndex <= shortCount; shortIndex += 1) {
    const minEnd = shortIndex;
    const maxEnd = longCount - (shortCount - shortIndex);
    for (let end = minEnd; end <= maxEnd; end += 1) {
      const largestGroup = Math.min(maxGroup, end - (shortIndex - 1));
      for (let groupSize = 1; groupSize <= largestGroup; groupSize += 1) {
        const start = end - groupSize;
        if (!Number.isFinite(dp[shortIndex - 1][start])) continue;
        const groupedLength = prefix[end] - prefix[start];
        const expected = Math.max(1, ratio * groupedLength);
        const actual = shortItems[shortIndex - 1].length;
        const lengthCost = Math.log(actual / expected) ** 2;
        const groupCost = 0.004 * (groupSize - 1) ** 2;
        const candidate = dp[shortIndex - 1][start] + lengthCost + groupCost;
        if (candidate < dp[shortIndex][end]) {
          dp[shortIndex][end] = candidate;
          previous[shortIndex][end] = groupSize;
        }
      }
    }
  }

  if (!Number.isFinite(dp[shortCount][longCount])) return null;
  const groups = Array(shortCount);
  let end = longCount;
  for (let shortIndex = shortCount; shortIndex > 0; shortIndex -= 1) {
    const groupSize = previous[shortIndex][end];
    groups[shortIndex - 1] = longItems.slice(end - groupSize, end);
    end -= groupSize;
  }
  return groups;
}

function alignedRow(paragraph, sourceUnits, bodyBlocks, method) {
  return {
    ...paragraph,
    source: sourceUnits.map(unit => unit.html).join(''),
    body: bodyBlocks.map(block => block.html).join(''),
    alignedRow: true,
    alignmentMethod: method,
  };
}

function alignSequence(paragraph, sourceUnits, bodyBlocks) {
  if (!sourceUnits.length) {
    return bodyBlocks.map(block => alignedRow(paragraph, [], [block], 'unmatched-body'));
  }
  if (!bodyBlocks.length) {
    return sourceUnits.map(unit => alignedRow(paragraph, [unit], [], 'unmatched-source'));
  }

  if (sourceUnits.length >= bodyBlocks.length) {
    const groups = groupedBySequence(sourceUnits, bodyBlocks);
    if (groups) {
      return bodyBlocks.map((block, index) => alignedRow(paragraph, groups[index], [block], 'sequence'));
    }
  } else {
    const groups = groupedBySequence(bodyBlocks, sourceUnits);
    if (groups) {
      return sourceUnits.map((unit, index) => alignedRow(paragraph, [unit], groups[index], 'sequence'));
    }
  }

  return [alignedRow(paragraph, sourceUnits, bodyBlocks, 'section')];
}

function alignParagraph(paragraph) {
  if (paragraph.glyph || !paragraph.source || !paragraph.body) return [paragraph];
  const sourceUnits = removeDuplicateSourceFrontMatter(extractSourceUnits(paragraph.source));
  const bodyBlocks = extractBodyBlocks(paragraph.body);
  if (sourceUnits.length <= 1 || bodyBlocks.length <= 1) return [paragraph];

  const anchors = [];
  let sourceCursor = 0;
  bodyBlocks.forEach((block, bodyIndex) => {
    if (!block.isHeading || !block.numberKey) return;
    const sourceIndex = sourceUnits.findIndex((unit, index) => (
      index >= sourceCursor &&
      unit.numberKey === block.numberKey &&
      unit.text.length < 180
    ));
    if (sourceIndex < 0) return;
    anchors.push({ sourceIndex, bodyIndex });
    sourceCursor = sourceIndex + 1;
  });

  const rows = [];
  let sourceStart = 0;
  let bodyStart = 0;
  anchors.forEach(anchor => {
    rows.push(...alignSequence(
      paragraph,
      sourceUnits.slice(sourceStart, anchor.sourceIndex),
      bodyBlocks.slice(bodyStart, anchor.bodyIndex)
    ));
    rows.push(alignedRow(
      paragraph,
      [sourceUnits[anchor.sourceIndex]],
      [bodyBlocks[anchor.bodyIndex]],
      'heading-anchor'
    ));
    sourceStart = anchor.sourceIndex + 1;
    bodyStart = anchor.bodyIndex + 1;
  });
  rows.push(...alignSequence(paragraph, sourceUnits.slice(sourceStart), bodyBlocks.slice(bodyStart)));
  return rows;
}

window.PassageBody = ({ passage, mode, onCite, tocTrail = [] }) => {
  const displayParagraphs = React.useMemo(() => {
    if (!passage) return [];
    let alignedSection = 0;
    return (passage.paragraphs || []).flatMap(alignParagraph).map(paragraph => {
      if (paragraph.glyph) return paragraph;
      if (!paragraph.alignedRow) return paragraph;
      alignedSection += 1;
      return { ...paragraph, section: String(alignedSection) };
    });
  }, [passage]);
  if (!passage) return null;
  const head = passage.head;
  const canon = window.CANON_DATA[passage.canon];

  return (
    <div className="r-body__column" style={{ '--c': canon.color }}>

      <header className="r-head">
        {tocTrail.length > 0 && (
          <div className="r-head__trail" aria-label="경전 계층">
            {tocTrail.map((item, index) => (
              <React.Fragment key={`${item.label}-${index}`}>
                {index > 0 && <span className="r-head__trail-sep">›</span>}
                <span className="r-head__trail-item">
                  <strong>{item.label}</strong>
                  {item.pali && <em>{item.pali}</em>}
                </span>
              </React.Fragment>
            ))}
          </div>
        )}
        <div className="r-head__roman">{head.roman}</div>
        <div className="r-head__han">{head.han}</div>
        <div className="r-head__kr">{head.kr}</div>
        <div className="r-head__skt">{head.skt}</div>
        <div className="r-head__meta">
          {head.meta.map((m, i) => (
            <div key={i} className="r-head__meta-item">
              <span>{m.k}</span>
              <strong>{m.v}</strong>
            </div>
          ))}
        </div>
      </header>

      <div className="r-passage">
        {displayParagraphs.map((p, i) => {
          if (p.glyph) return <div key={i} className="r-glyph">⁂</div>;

          const id = `p-${i+1}`;
          const bodyHTML = p.body;

          return (
            <div
              key={i}
              id={id}
              className="r-paragraph"
              data-section={p.section}
            >
              {p.source && (
                <div
                  className={sourceClass(p.sourceLang)}
                  dangerouslySetInnerHTML={renderSourceHTML(p.source)}
                />
              )}
              <div
                className="r-paragraph__body"
                dangerouslySetInnerHTML={renderBodyHTML(bodyHTML)}
              />
            </div>
          );
        })}
      </div>

      <section className="r-footnotes">
        <div className="r-footnotes__title">FOOTNOTES · 註</div>
        <ol className="r-footnotes__list">
          {passage.footnotes.map(fn => (
            <li key={fn.n} id={`fn-${fn.n}`} className="r-fn">
              <span className="r-fn__num">{fn.n}</span>
              <div>
                <span className="r-fn__key">{fn.key}</span>
                <span dangerouslySetInnerHTML={{ __html: fn.body }} />
              </div>
            </li>
          ))}
        </ol>
      </section>

      {!String(passage.id || '').startsWith('ext-') && (
        <div className="r-pager">
          <button className="r-pager__btn r-pager__btn--prev">
            <span className="r-pager__btn-label">← 이전</span>
            <span className="r-pager__btn-title">목차로</span>
          </button>
          <button className="r-pager__btn r-pager__btn--next">
            <span className="r-pager__btn-label">다음 →</span>
            <span className="r-pager__btn-title">제2권</span>
          </button>
        </div>
      )}
    </div>
  );
};

/* 각주 호버 풍선 + 용어 클릭 핸들러 */
window.FootnotePopover = ({ passage, onTermClick }) => {
  const [pop, setPop] = React.useState(null);

  React.useEffect(() => {
    const onOver = (e) => {
      const t = e.target.closest && e.target.closest('.r-fn-marker');
      if (!t || !passage) { setPop(null); return; }
      const n = +t.dataset.fn;
      const fn = (passage.footnotes || []).find(f => f.n === n);
      if (!fn) return;
      const r = t.getBoundingClientRect();
      setPop({ fn, x: r.left + r.width/2, y: r.top });
    };
    const onOut = (e) => {
      if (e.target.closest && e.target.closest('.r-fn-marker')) setPop(null);
    };
    const onClick = (e) => {
      const t = e.target.closest && e.target.closest('.term-link');
      if (!t) return;
      e.preventDefault();
      const r = t.getBoundingClientRect();
      onTermClick && onTermClick({
        term: t.dataset.term,
        x: r.left + r.width/2,
        y: r.bottom,
      });
    };
    document.addEventListener('mouseover', onOver);
    document.addEventListener('mouseout', onOut);
    document.addEventListener('click', onClick);
    return () => {
      document.removeEventListener('mouseover', onOver);
      document.removeEventListener('mouseout', onOut);
      document.removeEventListener('click', onClick);
    };
  }, [passage, onTermClick]);

  if (!pop) return null;
  return (
    <div
      className="r-fn-pop is-visible"
      style={{ left: pop.x, top: pop.y - 12, transform: 'translate(-50%, -100%)' }}
    >
      <span className="r-fn__key">{pop.fn.key}</span>
      <span dangerouslySetInnerHTML={{ __html: pop.fn.body }} />
    </div>
  );
};
