"""pdf2zh-next-mcp: MCP server for PDF translation with full-context LLM translation.

2-pass 방식으로 동작:
  Pass 1 (extract_segments): pdf2zh-next로 레이아웃 분석 + 세그먼트 추출
  LLM이 전체 세그먼트를 한 번에 번역
  Pass 2 (assemble_translated): 번역 결과를 주입하여 최종 PDF 생성
"""

import json
import os
import platform  # noqa: used for Windows detection
import shlex
import shutil
import subprocess
import threading
import time
import uuid
from pathlib import Path

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("pdf2zh-next-mcp")

SCRIPTS_DIR = Path(__file__).parent / "scripts"
COLLECT_SCRIPT = SCRIPTS_DIR / "collect.py"
LOOKUP_SCRIPT = SCRIPTS_DIR / "lookup.py"

# 페이지네이션: MCP tool result 25K 토큰 제한 대응
# 69,000자 ≈ 23K 토큰 (보수적 3자/토큰 기준), 메타데이터/instruction 여유 2K
MAX_RESPONSE_CHARS = 69000


def _find_pdf2zh_binary() -> str | None:
    """크로스 플랫폼 pdf2zh_next 바이너리 탐색. 못 찾으면 None 반환."""
    system = platform.system()
    home = Path.home()

    # 1. uv tool 설치 경로 (플랫폼별)
    if system == "Windows":
        appdata = os.environ.get("APPDATA", str(home / "AppData" / "Roaming"))
        candidates = [
            Path(appdata) / "uv" / "data" / "tools" / "pdf2zh-next" / "Scripts" / "pdf2zh_next.exe",
        ]
    else:  # macOS, Linux
        candidates = [
            home / ".local" / "share" / "uv" / "tools" / "pdf2zh-next" / "bin" / "pdf2zh_next",
        ]

    for path in candidates:
        if path.exists():
            return str(path)

    # 2. PATH에서 탐색 (shutil.which는 크로스 플랫폼)
    found = shutil.which("pdf2zh_next")
    if found:
        return found

    return None


def _find_python_binary() -> str | None:
    """크로스 플랫폼 Python 바이너리 탐색. 못 찾으면 None 반환.

    pdf2zh-next의 가상환경 내 Python을 우선 사용한다.
    CLITranslator가 이 Python으로 collect.py/lookup.py를 실행하므로
    시스템 Python과 독립적이어야 한다.
    """
    system = platform.system()
    home = Path.home()

    if system == "Windows":
        appdata = os.environ.get("APPDATA", str(home / "AppData" / "Roaming"))
        candidates = [
            Path(appdata) / "uv" / "data" / "tools" / "pdf2zh-next" / "Scripts" / "python.exe",
        ]
    else:
        candidates = [
            home / ".local" / "share" / "uv" / "tools" / "pdf2zh-next" / "bin" / "python3",
        ]

    for path in candidates:
        if path.exists():
            return str(path)

    # fallback: 시스템 Python
    for name in ("python3", "python"):
        found = shutil.which(name)
        if found:
            return found

    return None


# Lazy 초기화 + 캐싱
_pdf2zh_bin: str | None = None
_python_bin: str | None = None


def _get_pdf2zh_bin() -> str | None:
    global _pdf2zh_bin
    if _pdf2zh_bin is None:
        _pdf2zh_bin = _find_pdf2zh_binary()
    return _pdf2zh_bin


def _get_python_bin() -> str | None:
    global _python_bin
    if _python_bin is None:
        _python_bin = _find_python_binary()
    return _python_bin


def _check_binaries() -> str | None:
    """바이너리 확인. 문제 있으면 에러 JSON 문자열 반환, 정상이면 None."""
    if _get_pdf2zh_bin() is None:
        return json.dumps(
            {
                "status": "error",
                "message": (
                    "pdf2zh-next가 설치되어 있지 않습니다.\n"
                    "다음 명령어로 설치해주세요: uv tool install pdf2zh-next\n"
                    "설치 확인: pdf2zh_next --version"
                ),
            },
            ensure_ascii=False,
        )
    if _get_python_bin() is None:
        return json.dumps(
            {
                "status": "error",
                "message": "Python을 찾을 수 없습니다. Python 3.12 이상이 설치되어 있는지 확인해주세요.",
            },
            ensure_ascii=False,
        )
    return None


def _build_cli_command(script: Path, *args: Path) -> str:
    """shlex.quote로 경로를 감싸서 공백/백슬래시에 안전한 CLI 커맨드 문자열을 생성한다."""
    parts = [shlex.quote(_get_python_bin()), shlex.quote(str(script))]
    parts.extend(shlex.quote(str(a)) for a in args)
    return " ".join(parts)


def _build_pdf2zh_cmd(
    file: str,
    lang_in: str,
    lang_out: str,
    cli_command: str,
    output_dir: Path,
    pool_max_workers: int = 0,
    pages: str = "",
) -> list[str]:
    """pdf2zh_next CLI 명령어 리스트를 생성한다."""
    cmd = [
        _get_pdf2zh_bin(),
        "--clitranslator",
        "--clitranslator-command", cli_command,
        "--lang-in", lang_in,
        "--lang-out", lang_out,
        "--output", str(output_dir),
        "--disable-config-auto-save",
        "--ignore-cache",
    ]
    if pool_max_workers > 0:
        cmd.extend(["--pool-max-workers", str(pool_max_workers)])
    if pages:
        cmd.extend(["--pages", pages])
    cmd.append(file)
    return cmd



def _update_meta_status(meta_file: Path, status: str, **extra):
    """meta.json의 status를 원자적으로 업데이트한다."""
    meta = json.loads(meta_file.read_text(encoding="utf-8"))
    meta["status"] = status
    meta.update(extra)
    meta_file.write_text(json.dumps(meta, ensure_ascii=False), encoding="utf-8")


def _run_extraction_async(cmd, session_dir: Path, pass1_output: Path):
    """백그라운드에서 pdf2zh-next Pass 1을 실행하고, 완료 시 meta.json을 업데이트한다."""
    meta_file = session_dir / "meta.json"
    try:
        result = subprocess.run(
            cmd, capture_output=True, encoding="utf-8", errors="replace", timeout=1800
        )
        if result.returncode == 0:
            _update_meta_status(meta_file, "extracted")
        else:
            _update_meta_status(
                meta_file, "error",
                error=result.stderr[-2000:] if result.stderr else "Unknown error",
            )
    except Exception as e:
        _update_meta_status(meta_file, "error", error=str(e))
    finally:
        shutil.rmtree(pass1_output, ignore_errors=True)


def _run_assembly_async(cmd, session_dir: Path, temp_output_dir: Path, final_dir: Path, file_stem: str, glossary_json: str):
    """백그라운드에서 pdf2zh-next Pass 2를 실행하고, 완료 시 meta.json을 업데이트한다."""
    meta_file = session_dir / "meta.json"
    try:
        result = subprocess.run(
            cmd, capture_output=True, encoding="utf-8", errors="replace", timeout=1800
        )
        if result.returncode != 0:
            _update_meta_status(
                meta_file, "error",
                error=result.stderr[-2000:] if result.stderr else "PDF 조립 실패",
            )
            return

        output_files = _collect_output_files(temp_output_dir, final_dir, file_stem, glossary_json)

        if not output_files or ("mono" not in output_files and "dual" not in output_files):
            _update_meta_status(
                meta_file, "error",
                error="PDF가 생성되지 않았습니다.",
            )
            return

        # 원본 세그먼트 수 읽기
        segments_file = session_dir / "segments.json"
        segment_count = 0
        if segments_file.exists():
            segments = json.loads(segments_file.read_text(encoding="utf-8"))
            segment_count = len(segments)

        _update_meta_status(
            meta_file, "assembled",
            output=output_files,
            segment_count=segment_count,
        )
    except Exception as e:
        _update_meta_status(meta_file, "error", error=str(e))


def _unique_path(path: Path) -> Path:
    """파일이 이미 존재하면 (1), (2) 등 번호를 붙여 고유한 경로를 반환한다.

    예: paper-mono.pdf → paper-mono (1).pdf → paper-mono (2).pdf → ...
    """
    if not path.exists():
        return path
    stem = path.stem
    suffix = path.suffix
    parent = path.parent
    counter = 1
    while True:
        new_path = parent / f"{stem} ({counter}){suffix}"
        if not new_path.exists():
            return new_path
        counter += 1


def _collect_output_files(
    temp_output_dir: Path, final_dir: Path, file_stem: str, glossary_json: str = ""
) -> dict[str, str]:
    """임시 출력 디렉토리에서 생성된 PDF를 찾아 최종 디렉토리로 이동한다.

    파일명 중복 시 번호를 붙여서 기존 파일을 보존한다.
    """
    output_files: dict[str, str] = {}

    # 기대 패턴으로 찾기
    mono_pdf = temp_output_dir / f"{file_stem}-mono.pdf"
    dual_pdf = temp_output_dir / f"{file_stem}-dual.pdf"

    # fallback: BabelDOC 출력 패턴 탐색
    if not mono_pdf.exists():
        candidates = (
            list(temp_output_dir.glob(f"{file_stem}*mono*.pdf"))
            + list(temp_output_dir.glob(f"{file_stem}*translated*.pdf"))
        )
        if candidates:
            mono_pdf = candidates[0]

    if not dual_pdf.exists():
        candidates = list(temp_output_dir.glob(f"{file_stem}*dual*.pdf"))
        if candidates:
            dual_pdf = candidates[0]

    # 최종 위치로 이동 (번호 붙이기)
    if mono_pdf.exists():
        dest = _unique_path(final_dir / f"{file_stem}-mono.pdf")
        shutil.move(str(mono_pdf), str(dest))
        output_files["mono"] = str(dest)

    if dual_pdf.exists():
        dest = _unique_path(final_dir / f"{file_stem}-dual.pdf")
        shutil.move(str(dual_pdf), str(dest))
        output_files["dual"] = str(dest)

    # 용어집 저장
    if glossary_json:
        try:
            glossary = json.loads(glossary_json)
            dest = _unique_path(final_dir / f"{file_stem}-glossary.json")
            dest.write_text(
                json.dumps(glossary, ensure_ascii=False, indent=2), encoding="utf-8"
            )
            output_files["glossary"] = str(dest)
        except json.JSONDecodeError:
            pass

    return output_files


def _build_translation_instruction(segment_count: int, lang_in: str, lang_out: str, session_dir: str) -> str:
    """번역 instruction을 생성한다."""
    return (
        f"위 {segment_count}개 세그먼트를 [{lang_in}]에서 [{lang_out}]로 번역해주세요.\n\n"
        "번역 전에 반드시:\n"
        "1. 모든 세그먼트를 먼저 읽고 핵심 전문 용어를 파악하세요\n"
        "2. 일관된 용어를 사용하여 번역하세요\n"
        "3. 일부 세그먼트는 페이지 경계에서 문장이 잘려 있을 수 있습니다. "
        "앞뒤 세그먼트를 참고하여 자연스럽게 이어지도록 번역하세요\n\n"
        "규칙:\n"
        "- {v0}, {v1} 등의 수식 플레이스홀더는 절대 변경하지 마세요\n"
        "- 학술 논문에 적합한 격식체를 사용하세요\n"
        "- 널리 알려진 고유명사나 약어는 원문 그대로 유지하세요\n"
        "- 참고문헌(References/Bibliography) 섹션의 세그먼트는 번역하지 않고 원문 텍스트를 그대로 반환하세요. "
        "저자명, 저널명, DOI 등이 변경되면 인용 추적이 불가능해집니다\n\n"
        f"1. 모든 세그먼트를 번역한 후 save_translated_segments(session_dir, segments_json) 툴로 저장하세요.\n"
        "   여러 번 호출하면 누적 저장됩니다.\n"
        f"2. assemble_translated(session_dir, glossary_json) 툴을 호출하세요. session_dir: {session_dir}"
    )


@mcp.tool()
def extract_segments(
    file: str,
    lang_in: str = "en",
    lang_out: str = "ko",
    output_dir: str = "",
    pages: str = "",
) -> str:
    """PDF 파일에서 번역 대상 세그먼트를 추출합니다.

    백그라운드에서 비동기로 실행됩니다. 반환된 session_dir로
    check_extraction_status를 호출하여 완료 여부를 확인하세요.

    Args:
        file: PDF 파일의 로컬 절대 경로. 예: /Users/username/Downloads/paper.pdf
            주의: 반드시 사용자가 메시지에 입력한 로컬 경로를 사용하세요.
            첨부 파일의 내부 경로(/mnt/user-data/uploads/...)는 사용할 수 없습니다.
        lang_in: 원본 언어 코드 (기본값: en)
        lang_out: 번역 대상 언어 코드 (기본값: ko)
        output_dir: 출력 디렉토리 경로. 비어 있으면 입력 PDF와 같은 디렉토리에 저장.
        pages: 번역할 페이지 범위. 비어 있으면 전체 페이지.
            형식: '1-7,10' (1~7, 10페이지), '-5' (1~5), '3-' (3~끝).
            참고문헌 등 특정 페이지를 제외하고 싶을 때 사용하세요.
    """
    # 바이너리 확인
    err = _check_binaries()
    if err:
        return err

    file_path = Path(file)
    if not file_path.exists():
        return json.dumps(
            {"status": "error", "message": f"파일을 찾을 수 없습니다: {file}"},
            ensure_ascii=False,
        )

    # 출력 디렉토리 결정
    resolved_output_dir = Path(output_dir) if output_dir else file_path.parent
    if output_dir and not resolved_output_dir.exists():
        resolved_output_dir.mkdir(parents=True, exist_ok=True)

    # 세션 생성 (출력 디렉토리에 세션 폴더 생성)
    session_id = uuid.uuid4().hex[:12]
    session_dir = resolved_output_dir / f"pdf2zh-sessions-{session_id}"
    session_dir.mkdir(parents=True, exist_ok=True)

    segments_file = session_dir / "segments.json"
    pass1_output = session_dir / "pass1_output"
    pass1_output.mkdir(exist_ok=True)

    # 세션 메타데이터 저장
    meta = {
        "file": str(file_path.absolute()),
        "lang_in": lang_in,
        "lang_out": lang_out,
        "output_dir": str(resolved_output_dir.absolute()),
        "pages": pages,
        "status": "extracting",
    }
    (session_dir / "meta.json").write_text(
        json.dumps(meta, ensure_ascii=False), encoding="utf-8"
    )

    # Pass 1: 비동기로 세그먼트 수집
    cli_command = _build_cli_command(COLLECT_SCRIPT, segments_file)
    cmd = _build_pdf2zh_cmd(
        file=str(file_path.absolute()),
        lang_in=lang_in,
        lang_out=lang_out,
        cli_command=cli_command,
        output_dir=pass1_output,
        pool_max_workers=1,  # 순서 보장
        pages=pages,
    )

    thread = threading.Thread(
        target=_run_extraction_async,
        args=(cmd, session_dir, pass1_output),
        daemon=True,
    )
    thread.start()

    return json.dumps(
        {
            "status": "processing",
            "session_dir": str(session_dir),
            "instruction": (
                "세그먼트 추출이 시작되었습니다. "
                f"check_extraction_status(session_dir) 를 호출하여 완료를 확인하세요. "
                f"session_dir: {session_dir}"
            ),
        },
        ensure_ascii=False,
    )


@mcp.tool()
def check_extraction_status(
    session_dir: str,
    offset: int = 0,
) -> str:
    """세그먼트 추출 진행 상황을 확인합니다.

    extract_segments 호출 후 이 툴을 호출하여 완료를 확인하세요.
    서버가 내부에서 대기 후 응답하므로 별도 대기 불필요합니다.
    완료되면 세그먼트와 번역 instruction을 반환합니다.

    Args:
        session_dir: extract_segments에서 반환된 세션 디렉토리 경로
        offset: 세그먼트 시작 인덱스 (기본값: 0). 세그먼트가 많을 때 나눠서 가져올 수 있습니다.
    """
    session_path = Path(session_dir)
    meta_file = session_path / "meta.json"

    if not meta_file.exists():
        return json.dumps(
            {"status": "error", "message": f"세션을 찾을 수 없습니다: {session_dir}"},
            ensure_ascii=False,
        )

    meta = json.loads(meta_file.read_text(encoding="utf-8"))
    status = meta.get("status", "unknown")

    # 진행 중 → 30초 대기 후 확인
    if status == "extracting":
        time.sleep(30)

        meta = json.loads(meta_file.read_text(encoding="utf-8"))
        status = meta.get("status", "unknown")

        if status == "extracting":
            segments_file = session_path / "segments.json"
            collected = 0
            if segments_file.exists():
                try:
                    segs = json.loads(segments_file.read_text(encoding="utf-8"))
                    collected = len(segs)
                except (json.JSONDecodeError, OSError):
                    pass
            return json.dumps(
                {"status": "extracting", "collected": collected},
                ensure_ascii=False,
            )

        # 대기 중 완료/에러로 전환됐으면 아래에서 처리

    # 에러
    if status == "error":
        error_msg = meta.get("error", "알 수 없는 오류")
        # 세션 정리
        shutil.rmtree(session_path, ignore_errors=True)
        return json.dumps(
            {"status": "error", "message": f"세그먼트 추출 실패: {error_msg}"},
            ensure_ascii=False,
        )

    # 완료
    if status == "extracted":
        segments_file = session_path / "segments.json"
        if not segments_file.exists():
            return json.dumps(
                {"status": "error", "message": "세그먼트 파일을 찾을 수 없습니다."},
                ensure_ascii=False,
            )

        all_segments = json.loads(segments_file.read_text(encoding="utf-8"))
        total = len(all_segments)

        # 동적 페이지네이션: 문자 수 기반으로 25K 토큰 제한 내에 맞춤
        page_segments = []
        char_count = 0
        for seg in all_segments[offset:]:
            seg_len = len(seg) + 4  # 따옴표 + 콤마 + 공백
            if char_count + seg_len > MAX_RESPONSE_CHARS and page_segments:
                break
            page_segments.append(seg)
            char_count += seg_len
        has_more = (offset + len(page_segments)) < total

        lang_in = meta.get("lang_in", "en")
        lang_out = meta.get("lang_out", "ko")

        result = {
            "status": "done",
            "segment_count": total,
            "offset": offset,
            "returned": len(page_segments),
            "has_more": has_more,
            "segments": page_segments,
        }

        # 첫 페이지에만 instruction 포함
        if offset == 0:
            result["instruction"] = _build_translation_instruction(
                total, lang_in, lang_out, session_dir
            )

        # 다음 페이지 안내
        if has_more:
            next_offset = offset + len(page_segments)
            result["next_instruction"] = (
                f"세그먼트가 더 있습니다. check_extraction_status(session_dir, offset={next_offset}) "
                f"를 호출하여 나머지 세그먼트를 가져오세요."
            )

        return json.dumps(result, ensure_ascii=False)

    return json.dumps(
        {"status": "error", "message": f"알 수 없는 상태: {status}"},
        ensure_ascii=False,
    )


@mcp.tool()
def save_translated_segments(
    session_dir: str,
    segments_json: str,
) -> str:
    """번역된 세그먼트를 세션에 저장합니다.

    여러 번 호출하면 기존 세그먼트에 누적됩니다.
    모든 세그먼트 저장 후 assemble_translated를 호출하세요.

    Args:
        session_dir: extract_segments에서 반환된 세션 디렉토리 경로
        segments_json: 번역된 세그먼트 JSON 배열 문자열
    """
    session_path = Path(session_dir)
    if not session_path.exists():
        return json.dumps(
            {"status": "error", "message": f"세션 디렉토리를 찾을 수 없습니다: {session_dir}"},
            ensure_ascii=False,
        )

    try:
        new_segments = json.loads(segments_json)
    except json.JSONDecodeError as e:
        return json.dumps(
            {"status": "error", "message": f"segments_json 파싱 실패: {e}"},
            ensure_ascii=False,
        )

    if not isinstance(new_segments, list):
        return json.dumps(
            {"status": "error", "message": "segments_json은 JSON 배열이어야 합니다."},
            ensure_ascii=False,
        )

    trans_path = session_path / "translated_segments.json"

    # 기존 세그먼트가 있으면 누적
    if trans_path.exists():
        existing = json.loads(trans_path.read_text(encoding="utf-8"))
        existing.extend(new_segments)
    else:
        existing = new_segments

    trans_path.write_text(json.dumps(existing, ensure_ascii=False), encoding="utf-8")

    return json.dumps(
        {"status": "success", "saved_count": len(existing), "new_count": len(new_segments)},
        ensure_ascii=False,
    )


@mcp.tool()
def assemble_translated(
    session_dir: str,
    glossary_json: str = "",
) -> str:
    """번역된 세그먼트로 최종 PDF를 생성합니다.

    백그라운드에서 비동기로 실행됩니다. 반환된 session_dir로
    check_assembly_status를 호출하여 완료 여부를 확인하세요.

    Args:
        session_dir: extract_segments에서 반환된 세션 디렉토리 경로
        glossary_json: (선택) 용어집 JSON 문자열. 번역 시 생성된 용어집.
    """
    # 바이너리 확인
    err = _check_binaries()
    if err:
        return err

    session_path = Path(session_dir)
    meta_file = session_path / "meta.json"

    if not meta_file.exists():
        return json.dumps(
            {"status": "error", "message": f"세션을 찾을 수 없습니다: {session_dir}"},
            ensure_ascii=False,
        )

    # 메타데이터 로드
    meta = json.loads(meta_file.read_text(encoding="utf-8"))
    file = meta["file"]
    lang_in = meta["lang_in"]
    lang_out = meta["lang_out"]
    pages = meta.get("pages", "")
    final_dir = Path(meta.get("output_dir", str(Path(file).parent)))

    # 번역 데이터 로드
    trans_path = session_path / "translated_segments.json"
    if not trans_path.exists():
        return json.dumps(
            {"status": "error", "message": f"세션 디렉토리에 translated_segments.json이 없습니다: {session_dir}. save_translated_segments 툴로 저장해주세요."},
            ensure_ascii=False,
        )
    try:
        translated = json.loads(trans_path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as e:
        return json.dumps(
            {"status": "error", "message": f"번역 파일 JSON 파싱 실패: {e}"},
            ensure_ascii=False,
        )

    if not isinstance(translated, list):
        return json.dumps(
            {"status": "error", "message": "번역 결과는 JSON 배열이어야 합니다."},
            ensure_ascii=False,
        )

    # 원본 세그먼트 로드
    segments_file = session_path / "segments.json"
    segments = json.loads(segments_file.read_text(encoding="utf-8"))

    if len(translated) != len(segments):
        return json.dumps(
            {
                "status": "error",
                "message": f"세그먼트 수 불일치: 원본 {len(segments)}개, 번역 {len(translated)}개",
            },
            ensure_ascii=False,
        )

    # 번역 딕셔너리 생성 + 저장
    translations = dict(zip(segments, translated))
    translations_file = session_path / "translations.json"
    translations_file.write_text(
        json.dumps(translations, ensure_ascii=False), encoding="utf-8"
    )

    # 임시 출력 디렉토리 (pdf2zh-next가 여기에 생성 → 최종 위치로 이동)
    temp_output_dir = session_path / "pass2_output"
    temp_output_dir.mkdir(exist_ok=True)

    # meta.json 상태 업데이트
    _update_meta_status(meta_file, "assembling")

    # Pass 2: 비동기로 PDF 조립
    cli_command = _build_cli_command(LOOKUP_SCRIPT, translations_file)
    cmd = _build_pdf2zh_cmd(
        file=file,
        lang_in=lang_in,
        lang_out=lang_out,
        cli_command=cli_command,
        output_dir=temp_output_dir,
        pages=pages,
    )

    file_stem = Path(file).stem
    thread = threading.Thread(
        target=_run_assembly_async,
        args=(cmd, session_path, temp_output_dir, final_dir, file_stem, glossary_json),
        daemon=True,
    )
    thread.start()

    return json.dumps(
        {
            "status": "processing",
            "session_dir": str(session_path),
            "instruction": (
                "PDF 조립이 시작되었습니다. "
                f"check_assembly_status(session_dir) 를 호출하여 완료를 확인하세요. "
                f"session_dir: {session_dir}"
            ),
        },
        ensure_ascii=False,
    )


@mcp.tool()
def check_assembly_status(
    session_dir: str,
) -> str:
    """PDF 조립 진행 상황을 확인합니다.

    assemble_translated 호출 후 이 툴을 호출하여 완료를 확인하세요.
    서버가 내부에서 대기 후 응답하므로 별도 대기 불필요합니다.

    Args:
        session_dir: extract_segments에서 반환된 세션 디렉토리 경로
    """
    session_path = Path(session_dir)
    meta_file = session_path / "meta.json"

    if not meta_file.exists():
        return json.dumps(
            {"status": "error", "message": f"세션을 찾을 수 없습니다: {session_dir}"},
            ensure_ascii=False,
        )

    meta = json.loads(meta_file.read_text(encoding="utf-8"))
    status = meta.get("status", "unknown")

    # 진행 중 → 30초 대기 후 다시 확인
    if status == "assembling":
        time.sleep(30)

        meta = json.loads(meta_file.read_text(encoding="utf-8"))
        status = meta.get("status", "unknown")

        if status == "assembling":
            return json.dumps(
                {"status": "assembling"},
                ensure_ascii=False,
            )

        # 대기 중 완료/에러로 전환됐으면 아래에서 처리

    # 에러
    if status == "error":
        error_msg = meta.get("error", "알 수 없는 오류")
        shutil.rmtree(session_path, ignore_errors=True)
        return json.dumps(
            {"status": "error", "message": f"PDF 조립 실패: {error_msg}"},
            ensure_ascii=False,
        )

    # 완료
    if status == "assembled":
        output_files = meta.get("output", {})
        segment_count = meta.get("segment_count", 0)

        # 세션 정리
        shutil.rmtree(session_path, ignore_errors=True)

        return json.dumps(
            {
                "status": "success",
                "message": f"번역 완료! {segment_count}개 세그먼트 처리됨.",
                "output": output_files,
            },
            ensure_ascii=False,
        )

    return json.dumps(
        {"status": "error", "message": f"알 수 없는 상태: {status}"},
        ensure_ascii=False,
    )


def main():
    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()
