"""사실 주장의 재현.

계획 검증의 1표 차단(`kind a`, `P-Req` 의 `kind f`)에는 근거가 있다 — 명시된 두
인용이 서로 모순이라는 것은 한 명이 실측으로 확정할 수 있는 사실이고, 사실을
다수결로 기각하면 안 된다. 문제는 1표라는 것이 아니라 **1표에 재현 요구가 없다**는
것이었다. 검증자가 "이 경로는 존재하지 않는다" 라고 쓰면 그대로 차단이 됐다.

여기서 재현은 워커가 아니라 이 코드가 실행한다. 워커가 결과까지 신고하면 판정이
자기신고 위에 서고, 같은 주장이 워커마다 다르게 결론난다.

대가는 표현력이다. 여기서 돌릴 수 있는 형태로만 사실 주장을 쓸 수 있고, 그 밖의
것은 `judgement` 로 내어 정족수로 판정한다. 강등이 아니라 분류다 — 기계가 확인할
수 없는 것에 1표 차단을 주는 것이 애초에 틀렸다.
"""
from __future__ import annotations

from pathlib import Path
from typing import Any, Mapping

REPRODUCED = "reproduced"
NOT_REPRODUCED = "not-reproduced"
NOT_RUNNABLE = "not-runnable"

PROBE_KINDS = (
    "path-exists",
    "path-absent",
    "literal-present",
    "literal-absent",
    "citations-differ",
)

_MAX_READ_BYTES = 4_000_000


def _within(project_root: Path, value: object) -> Path | None:
    """프로젝트 안의 경로. 밖으로 나가거나 모양이 아니면 ``None``.

    재현은 검증자가 쓴 문자열을 그대로 받는다. 담아 두지 않으면 그 문자열 하나로
    프로젝트 밖을 읽게 된다.
    """
    if not isinstance(value, str) or not value.strip():
        return None
    candidate = Path(value)
    resolved = (
        candidate if candidate.is_absolute() else project_root / candidate
    )
    try:
        resolved = resolved.resolve()
        root = project_root.resolve()
    except (OSError, RuntimeError):
        return None
    return resolved if resolved == root or root in resolved.parents else None


def _read(path: Path) -> str | None:
    try:
        if not path.is_file() or path.stat().st_size > _MAX_READ_BYTES:
            return None
        return path.read_text(encoding="utf-8", errors="replace")
    except (OSError, ValueError):
        return None


def reproduce(probe: object, *, project_root: Path) -> str:
    """이 프로브를 실행한 결과.

    ``reproduced`` 는 주장이 성립한다는 뜻이고, ``not-reproduced`` 는 근거가
    성립하지 않는다는 뜻이다. ``not-runnable`` 은 판정하지 않았다는 뜻이며 —
    프로브의 모양이 아니거나, 경로가 프로젝트 밖이거나, 파일을 못 읽은 경우다 —
    소비처는 이것을 `judgement` 와 같게 다뤄야 한다. 못 돌린 것을 재현 실패로
    읽으면 도구의 한계가 주장의 기각이 된다.
    """
    if not isinstance(probe, Mapping):
        return NOT_RUNNABLE
    kind = str(probe.get("kind") or "")
    if kind not in PROBE_KINDS:
        return NOT_RUNNABLE

    if kind in ("path-exists", "path-absent"):
        path = _within(project_root, probe.get("path"))
        if path is None:
            return NOT_RUNNABLE
        exists = path.exists()
        return REPRODUCED if exists == (kind == "path-exists") else NOT_REPRODUCED

    if kind in ("literal-present", "literal-absent"):
        path = _within(project_root, probe.get("path"))
        literal = probe.get("literal")
        if path is None or not isinstance(literal, str) or not literal:
            return NOT_RUNNABLE
        text = _read(path)
        if text is None:
            return NOT_RUNNABLE
        present = literal in text
        return REPRODUCED if present == (kind == "literal-present") else NOT_REPRODUCED

    left, right = probe.get("left"), probe.get("right")
    if not isinstance(left, str) or not isinstance(right, str) or not left or not right:
        return NOT_RUNNABLE
    # 주장은 "이 둘이 서로 모순이다" 이므로, 다르면 재현된 것이다.
    return REPRODUCED if left.strip() != right.strip() else NOT_REPRODUCED
