"""timeline.json 의 run 항목을 그 run 의 현재 사실로 읽는 read-side 투영.

timeline 항목은 prepare 가 한 번 쓴다 — `render.render_timeline` 의 호출자는
`run._finalize_status_and_render_manifests` 뿐이다. run 이 끝날 때 상태를 쓰는
곳은 `validators/validate-run.py` 이고, 그 코드는 run-manifest(`status`,
`validation`, `workflowSnapshot`)와 task-manifest 만 고친다. 그래서 timeline
항목의 `status` 는 언제나 준비 시점 값(`prepared` / `in-progress`)이고
`workflowSnapshot` 은 준비 시점 스냅샷이다(2026-09-04 실측, dev-10626: 항목
6개 전부 `prepared`, 같은 run 의 run-manifest 4개는 `completed`). 같은 run 의
현재 사실은 그 run 의 run-manifest 가 가지므로, 읽는 쪽은 그것을 권위로 삼는다.

`reportRecordPath` 도 예약이다. 항목이 적는 값은 이번 run 의 기대 리포트 경로인데
리포트 seq 는 파일 존재로 배정되므로, 준비만 되고 돌지 않은 run 의 seq 를 다음
run 이 이어받는다(같은 실측: error-analysis run 001·002 가 같은
`final-report-error-analysis-001.data.json` 을 적었고 파일은 002 가 썼다).
validate-run 이 그 run 의 리포트를 검증했을 때(`validation.status` 가 `not-run`
이 아닐 때)만 그 경로가 그 run 의 리포트다.

task 단위 현재 사실(`currentStatus`, `latestRunStatus`, `workflow.*`,
`latestReportRecordPath`)은 task-manifest 가 권위다. 이 모듈은 run 단위만 다룬다.
"""
from __future__ import annotations

from pathlib import Path
from typing import Any, Mapping

from .json_boundary import JsonBoundaryError, load_owned_object
from .paths import resolve_under_root

VALIDATION_NOT_RUN = "not-run"


def current_run_facts(project_root: Path, run: Mapping[str, Any]) -> dict[str, Any]:
    """timeline 항목에 그 run 의 run-manifest 가 기록한 현재 사실을 덮어쓴 사본.

    - `status`, `workflowSnapshot`: run-manifest 값. 키가 없으면 항목 값 그대로.
    - `reportRecordPath` / `reportPath`: run-manifest 의 `validation.status` 가
      `not-run` 이면 비운다 — 그 run 은 리포트를 낸 적이 없고 경로는 예약이다.
      `validation` 블록이 없는 매니페스트는 판정 근거가 없으므로 그대로 둔다.

    run-manifest 가 없으면(`runManifestPath` 가 비었거나 파일이 없으면) 항목을
    그대로 돌려준다. 있는데 읽을 수 없으면 `JsonBoundaryError` 가 그대로 올라간다
    — 투영이 잘못된 파일을 조용히 건너뛰면 그 run 만 준비 시점 값으로 남는다.
    """
    entry = dict(run)
    manifest = _run_manifest(project_root, run)
    if manifest is None:
        return entry
    status = manifest.get("status")
    if isinstance(status, str) and status:
        entry["status"] = status
    snapshot = manifest.get("workflowSnapshot")
    if isinstance(snapshot, Mapping):
        entry["workflowSnapshot"] = dict(snapshot)
    validation = manifest.get("validation")
    if isinstance(validation, Mapping) and validation.get("status") == VALIDATION_NOT_RUN:
        entry["reportRecordPath"] = ""
        entry["reportPath"] = ""
    return entry


def _run_manifest(project_root: Path, run: Mapping[str, Any]) -> Mapping[str, Any] | None:
    relative = run.get("runManifestPath")
    if not isinstance(relative, str) or not relative:
        return None
    path = resolve_under_root(project_root, relative)
    if path is None:
        raise JsonBoundaryError(Path(relative), "run manifest", "outside this project")
    if not path.is_file():
        return None
    return load_owned_object(path, artifact="run manifest")
