<script lang="ts">
  /**
   * 문서 본문 canonical markdown 렌더러 (Phase 1C).
   *
   * 우선순위:
   *   1. md_content (Phase 1B 의 marker_worker 가 채운 canonical markdown)
   *   2. extracted_text fallback (기존 본문)
   *   3. placeholder
   *
   * 기능:
   *   - md_frontmatter (JSONB) 가 비어있지 않으면 본문 위에 메타 박스
   *   - heading anchor (h1~h6 id 자동 부여 + # 링크)
   *   - 이미지는 placeholder card 로 렌더 (1B.5 ImgAuth wiring 전)
   *   - KaTeX ($...$ inline / $$...$$ block)
   *   - DOMPurify sanitize
   *   - md_status badge (processing/success/skipped/failed) — MarkdownStatusBadge 위임
   */
  import { renderDocMarkdown } from '$lib/utils/docMarkdown';
  import { getAccessToken } from '$lib/api';
  import MarkdownStatusBadge from '$lib/components/MarkdownStatusBadge.svelte';

  type Props = {
    /** Phase 1B.5: docimg:img_NNN ref 를 /api/documents/{id}/images/{key}/raw 로 변환할 때 필요. */
    documentId?: number | null;
    mdContent?: string | null;
    mdFrontmatter?: Record<string, unknown> | null;
    extractedText?: string | null;
    mdStatus?: string | null;
    mdExtractionError?: string | null;
    mdExtractionQuality?: Record<string, unknown> | null;
    /** 개요 점프용 anchor: {chunk_id: md_content 내 char offset}. 렌더 전 해당 위치에
     *  <span id="sec-{chunk_id}"> 주입(점프 타깃). buildAnchorMap(outlineAnchors) 산출물. */
    anchorMap?: Record<number, number> | null;
    placeholder?: string;
    /** 추가 래퍼 클래스. tailwind prose-* / spacing 등을 호출 측에서 입혀야 할 때. */
    class?: string;
  };

  let {
    documentId = null,
    mdContent = null,
    mdFrontmatter = null,
    extractedText = null,
    mdStatus = null,
    mdExtractionError = null,
    mdExtractionQuality = null,
    anchorMap = null,
    placeholder = '*텍스트 추출 대기 중*',
    class: klass = '',
  }: Props = $props();

  // 개요 anchor 주입: body 의 각 offset(내림차순)에 빈 <span id="sec-N"> 삽입(점프 타깃).
  // [C3 불변식] char_start(BE) 는 호출측이 넘긴 md_content(raw, untransformed)에 대한 UTF-16 offset 이다.
  //   이 함수는 그 동일 문자열을 'out' 으로 받아 trim/CRLF-normalize/replace 없이 slice 해야 한다 —
  //   prop→out 사이 어떤 변환도 char_start 를 drift 시킨다. (현재 out = text(=body=mdContent prop) 무변환.)
  function spliceAnchors(text: string, map: Record<number, number> | null): string {
    if (!map) return text;
    const ents = Object.entries(map)
      .map(([id, off]) => [id, Number(off)] as [string, number])
      .filter(([, o]) => Number.isFinite(o) && o >= 0 && o <= text.length)
      .sort((a, b) => b[1] - a[1]);
    if (!ents.length) return text;
    let out = text;
    for (const [id, off] of ents) {
      out = out.slice(0, off) + `<span id="sec-${id}" class="md-anchor"></span>\n` + out.slice(off);
    }
    return out;
  }

  let usingMarkdown = $derived(!!(mdContent && mdContent.trim()));
  let body = $derived(
    usingMarkdown
      ? (mdContent as string)
      : extractedText && extractedText.trim()
        ? extractedText
        : placeholder,
  );
  let renderedHtml = $derived(renderDocMarkdown(spliceAnchors(body, anchorMap)));

  let frontmatterEntries = $derived.by(() => {
    if (!usingMarkdown || !mdFrontmatter) return [] as [string, unknown][];
    return Object.entries(mdFrontmatter).filter(
      ([, v]) => v !== null && v !== undefined && v !== '',
    );
  });

  function formatValue(v: unknown): string {
    if (v === null || v === undefined) return '';
    if (typeof v === 'object') {
      try {
        return JSON.stringify(v);
      } catch {
        return String(v);
      }
    }
    return String(v);
  }

  let containerRef: HTMLDivElement | undefined = $state();

  // heading anchor 후처리 — gfmHeadingId 가 부여한 id 에 # 링크 prepend.
  // string-level 정규식 처리 대신 DOM 후처리 — id 에 따옴표/HTML 메타 들어와도 안전.
  $effect(() => {
    // renderedHtml 변할 때마다 재실행
    void renderedHtml;
    if (!containerRef) return;
    const headings = containerRef.querySelectorAll<HTMLHeadingElement>(
      'h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]',
    );
    for (const h of headings) {
      if (h.querySelector('a.heading-anchor')) continue;
      const id = h.getAttribute('id');
      if (!id) continue;
      const a = document.createElement('a');
      a.className = 'heading-anchor';
      a.setAttribute('href', `#${id}`);
      a.setAttribute('aria-label', '이 항목으로 링크');
      a.textContent = '#';
      h.insertBefore(a, h.firstChild);
    }
  });

  // Phase 1B.5: docimg:img_NNN placeholder → 실제 <img> swap.
  // - data-md-image-internal="1" 인 figure 만 대상 (외부 URL placeholder 는 그대로 유지)
  // - documentId 미전달 시 swap 안 함 (prop 누락 / list view 등 안전)
  // - fetch 실패 시 placeholder 유지 (onerror 로 figure 복원)
  $effect(() => {
    void renderedHtml;
    if (!containerRef || documentId == null) return;
    const placeholders = containerRef.querySelectorAll<HTMLElement>(
      'figure.md-image-placeholder[data-md-image-internal="1"]',
    );
    for (const ph of placeholders) {
      if (ph.dataset.mdImageSwapped === '1') continue;
      const ref = ph.getAttribute('data-md-image-src');
      if (!ref) continue;
      const key = ref.split(':', 2)[1];
      if (!key) continue;
      const alt = ph.getAttribute('data-md-image-alt') ?? '';

      // <img> 는 Authorization header 를 못 보내므로 ?token= 쿼리 파라미터로 access
      // token 전달 (기존 /api/documents/{id}/file?token= iframe 패턴과 동일).
      const accessToken = getAccessToken();
      if (!accessToken) {
        // 로그아웃 상태 — placeholder 유지
        continue;
      }
      const img = document.createElement('img');
      img.src = `/api/documents/${documentId}/images/${encodeURIComponent(key)}/raw?token=${encodeURIComponent(accessToken)}`;
      img.alt = alt;
      img.loading = 'lazy';
      img.className = 'md-image';
      img.onerror = () => {
        // fetch 실패 → placeholder 복원 (img 만 제거, figure 는 그대로)
        try { img.remove(); } catch {}
        delete ph.dataset.mdImageSwapped;
      };
      // figure 안 내용을 img 로 교체 (figure 자체는 보존 → 다음 effect 재실행 시 idempotent 마커 검사 가능)
      ph.replaceChildren(img);
      ph.dataset.mdImageSwapped = '1';
    }
  });
</script>

<div class="mb-2">
  <MarkdownStatusBadge {mdStatus} {mdExtractionError} {mdExtractionQuality} />
</div>

{#if frontmatterEntries.length}
  <dl
    class="md-frontmatter mb-4 grid grid-cols-[max-content,1fr] gap-x-3 gap-y-1 rounded border border-border/60 bg-bg/40 px-4 py-3 text-xs"
  >
    <div class="col-span-2 mb-1 text-[10px] uppercase tracking-wide text-dim">메타</div>
    {#each frontmatterEntries as [k, v] (k)}
      <dt class="text-dim">{k}</dt>
      <dd class="break-words text-text">{formatValue(v)}</dd>
    {/each}
  </dl>
{/if}

<div
  bind:this={containerRef}
  class="markdown-body markdown-doc leading-relaxed {klass}"
>
  {@html renderedHtml}
</div>
