// 이 파일이 하는 일: 학습 문서 하나를 iframe 없이 React 안에서 네이티브로 렌더한다.
//
// ⚠️ 왜 iframe을 걷어냈나 — iframe은 "페이지 속 또 다른 완성된 페이지"라, 스크롤이 이중으로
//   생기고(바깥+안쪽), 모바일에선 문서가 좁은 창에 갇혔다. 앱과 완전히 분리된 세계라 로고도
//   두 번 나왔다. 그렇다고 문서 HTML을 앱 화면에 그냥 부어 넣으면, 문서마다 다른 226줄짜리
//   자체 스타일(.wrap, .callout …)이 앱 CSS와 뒤엉켜 서로를 망가뜨린다.
//
// 해법: Shadow DOM. 문서의 HTML과 자체 <style>을 "격리된 그림자 트리"에 넣는다.
//   · iframe이 아니므로 스크롤은 하나뿐이고, 높이는 내용만큼 자연스럽게 늘어난다(모바일 OK).
//   · Shadow 경계가 스타일을 완벽히 가둔다 — 문서 스타일이 앱으로 새지 않고, 앱 스타일도
//     문서로 새지 않는다. 문서 19개가 서로 다른 클래스 체계를 써도 충돌하지 않는다.
//   문서 HTML(public/docs/*.html)은 한 글자도 안 고치고 그대로 재사용한다.
import { useEffect, useRef, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { ArrowLeft } from 'lucide-react';
import client from '../api/client';

// 학습 포인트: 문서의 자체 스타일 + 본문을 Shadow DOM에 주입하는 작은 컴포넌트.
//   attachShadow로 그림자 뿌리를 만들고 innerHTML로 내용을 넣는다.
//   ⚠️ innerHTML에 외부 문자열을 넣는 건 보통 위험(XSS)하지만, 여기 들어가는 건 우리가 직접
//   작성해 저장소에 넣은 정적 문서다(사용자 입력 아님). 게다가 innerHTML은 <script>를 실행하지
//   않고, 위에서 script 태그도 제거한다. "신뢰할 수 있는 자체 콘텐츠"라는 전제가 성립할 때만
//   이렇게 써도 된다 — 사용자가 입력한 내용이었다면 절대 안 된다.
function DocShadow({ html }) {
  const hostRef = useRef(null);
  useEffect(() => {
    const host = hostRef.current;
    if (!host) return;
    const shadow = host.shadowRoot || host.attachShadow({ mode: 'open' });
    shadow.innerHTML = html;

    // ⚠️ Shadow DOM 안의 목차 앵커(<a href="#id">)는 브라우저 기본 스크롤이 안 먹는다.
    //   브라우저의 #프래그먼트 이동은 light DOM의 id만 찾고, 그림자 트리 안은 못 본다.
    //   그래서 클릭을 직접 가로채, 그림자 안에서 대상을 찾아 스크롤한다.
    //   (스크롤은 가장 가까운 스크롤 조상 .doc-immersive-scroll이 담당한다.)
    const onClick = (e) => {
      const a = e.composedPath().find(
        (el) => el.tagName === 'A' && el.getAttribute?.('href')?.startsWith('#'),
      );
      if (!a) return;
      const id = decodeURIComponent(a.getAttribute('href').slice(1));
      if (!id) return;
      const target =
        shadow.querySelector(`#${CSS.escape(id)}`) ||
        shadow.querySelector(`[name="${CSS.escape(id)}"]`);
      if (target) {
        e.preventDefault();
        target.scrollIntoView({ behavior: 'smooth', block: 'start' });
      }
    };
    shadow.addEventListener('click', onClick);
    return () => shadow.removeEventListener('click', onClick);
  }, [html]);
  return <div ref={hostRef} className="doc-shadow-host" />;
}

export default function DocViewerPage() {
  const { slug } = useParams();
  const navigate = useNavigate();
  const [doc, setDoc] = useState(null);
  const [shadowHtml, setShadowHtml] = useState('');
  const [error, setError] = useState(false);

  // ① slug로 문서 메타(제목·파일경로)를 찾는다. 단건 조회 API가 없어 목록을 재사용.
  useEffect(() => {
    client
      .get('/documents')
      .then((res) => {
        const found = res.data.find((d) => d.slug === slug);
        if (found) setDoc(found);
        else setError(true);
      })
      .catch(() => setError(true));
  }, [slug]);

  // ② 문서 HTML을 받아 Shadow DOM에 넣을 형태로 가공한다.
  useEffect(() => {
    if (!doc) return;
    let cancelled = false;
    // ?v= 캐시 버스터: 옛 보안헤더가 캐시에 눌어붙는 문제 방지(헤더 정책 바뀌면 숫자만 올린다).
    fetch(`/docs/${doc.filePath}?v=2`)
      .then((r) => r.text())
      .then((text) => {
        if (cancelled) return;
        const parsed = new DOMParser().parseFromString(text, 'text/html');
        // 방어적으로 script 제거(현재 문서엔 없지만, 원칙상 실행 코드는 넣지 않는다).
        parsed.querySelectorAll('script').forEach((s) => s.remove());

        // 문서의 모든 <style>을 모은다.
        let styles = [...parsed.querySelectorAll('style')].map((s) => s.textContent).join('\n');
        // 학습 포인트: HTML 조각을 innerHTML로 넣으면 <body> 태그는 파서가 버린다(문서 문맥이
        //   아니라서). 그러면 문서 CSS의 `body { ... }` 기본 규칙(폰트·배경 등)이 적용될
        //   대상이 사라진다. 그래서 본문을 .__docbody div로 감싸고, CSS의 `body` 선택자를
        //   `.__docbody`로 바꿔치기해 규칙이 그 div에 걸리게 한다. (선택자 시작 위치에서만 치환)
        styles = styles
          .replace(/(^|[}{,])(\s*)body\b/g, '$1$2.__docbody')
          // ⚠️ Shadow DOM 안에는 :root(=document의 html)가 없다. 문서가 :root에 정의한
          //   CSS 변수(--ink, --paper …)가 통째로 죽어서, var(--ink)로 칠한 한글 텍스트가
          //   색을 잃고 배경과 같아진다(안드로이드·아이폰 공통). :root를 :host(그림자 뿌리)로
          //   바꿔 변수를 그림자 트리 안에 심는다. data-theme 규칙은 host에 속성이 없어 무시되고
          //   OS 설정(prefers-color-scheme)만 따르는데, 최소한 텍스트가 보이는 게 우선이다.
          .replace(/:root\b/g, ':host');

        const bodyInner = parsed.body.innerHTML;
        const bodyClass = parsed.body.className;
        // 문서가 변수 정의에 실패하더라도 글자가 배경에 묻히지 않도록 기본 배경·글자색을 깐다
        // (문서 자체 규칙이 있으면 구체성으로 그게 이긴다). 라이트 팔레트 기준.
        const base = ':host,.__docbody{background:var(--paper,#FAFBFD);color:var(--ink,#101A28);}';
        setShadowHtml(`<style>${base}${styles}</style><div class="__docbody ${bodyClass}">${bodyInner}</div>`);
      })
      .catch(() => {
        if (!cancelled) setError(true);
      });
    return () => {
      cancelled = true;
    };
  }, [doc]);

  // 학습 포인트: 몰입 화면에선 ESC로 빠져나가는 게 관례다. 키보드만 쓰는 사용자를 위한 배려.
  useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') navigate('/docs');
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [navigate]);

  if (error) {
    return (
      <div>
        <p className="empty">문서를 찾을 수 없어요.</p>
        <button className="btn" onClick={() => navigate('/docs')}>
          문서 목록으로
        </button>
      </div>
    );
  }

  if (!doc || !shadowHtml) {
    return <p className="empty">불러오는 중...</p>;
  }

  return (
    // position: fixed + inset:0 → 앱 상단바까지 덮어 문서가 화면을 가득 쓴다(몰입).
    // 앱 크롬이 물러나므로 문서의 자체 헤더가 "중복 로고"가 아니라 "이 페이지의 제목"이 된다.
    <div className="doc-immersive">
      {/* 얇은 상단 바: 나가는 길 하나만. 문서 제목은 문서 자체에 있으니 반복하지 않는다. */}
      <div className="doc-immersive-bar">
        <button className="btn btn-ghost" onClick={() => navigate('/docs')}>
          <ArrowLeft size={16} strokeWidth={1.5} aria-hidden="true" /> 문서 목록
        </button>
        <span className="doc-immersive-title">{doc.title}</span>
      </div>
      {/* 문서 본문 — 남은 세로 공간을 채우고 여기'만' 스크롤한다(이중 스크롤 없음). */}
      <div className="doc-immersive-scroll">
        <DocShadow html={shadowHtml} />
      </div>
    </div>
  );
}
