# -*- coding: utf-8 -*-
"""anu_v3.task_artifact_detector — read-only task artifact presence detector.

task-2553+31 ANU_RUNTIME_RECONCILE_CHECKPOINT leaf module (구현목표 3·4·5).

Detects, for a given task_id, the presence of:
  * result.json            (구현목표 3)
  * .done marker           (구현목표 4)
  * report / decision / activation artifacts (구현목표 5)

NO-CRON / read-only invariant (§7/§10/9-R.1): this module performs ZERO write,
ZERO cron, ZERO merge, ZERO dispatch. It only stats / reads files. Existing
task 산출물(+26/+27/+30 …) are read/parse/reference only — never modified,
registered, or overwritten (§9, fixture verbs §5).
"""
from __future__ import annotations

import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional


@dataclass(frozen=True)
class ArtifactObservation:
    """Read-only artifact presence snapshot for a single task_id."""

    task_id: str
    result_present: bool
    done_present: bool
    report_present: bool
    decision_present: bool
    activation_present: bool
    result_path: Optional[str] = None
    done_path: Optional[str] = None
    extra_paths: Dict[str, str] = field(default_factory=dict)

    def to_json(self) -> Dict[str, object]:
        return {
            "task_id": self.task_id,
            "result_present": self.result_present,
            "done_present": self.done_present,
            "report_present": self.report_present,
            "decision_present": self.decision_present,
            "activation_present": self.activation_present,
            "result_path": self.result_path,
            "done_path": self.done_path,
            "extra_paths": dict(self.extra_paths),
        }


class TaskArtifactDetector:
    """Read-only detector over a repo-root layout.

    events_dir defaults to ``<repo>/memory/events`` and reports_dir to
    ``<repo>/memory/reports``. All accesses are stat/read only.
    """

    def __init__(
        self,
        repo_root: Path,
        events_subdir: str = "memory/events",
        reports_subdir: str = "memory/reports",
    ) -> None:
        self.repo_root = Path(repo_root)
        self.events_dir = self.repo_root / events_subdir
        self.reports_dir = self.repo_root / reports_subdir

    # ---- low level read-only helpers -------------------------------------
    def _exists(self, path: Path) -> bool:
        try:
            return path.is_file()
        except OSError:
            return False

    def read_json(self, path: Path) -> Optional[dict]:
        """Read-only JSON parse. Returns None on absence/parse error."""
        p = Path(path)
        if not self._exists(p):
            return None
        try:
            return json.loads(p.read_text(encoding="utf-8"))
        except (OSError, ValueError):
            return None

    # ---- detection -------------------------------------------------------
    def detect(self, task_id: str) -> ArtifactObservation:
        """Detect artifact presence for ``task_id`` (구현목표 3·4·5)."""
        result_p = self.events_dir / f"{task_id}.result.json"
        done_p = self.events_dir / f"{task_id}.done"
        decision_p = self.events_dir / f"{task_id}.decision.json"
        activation_p = self.events_dir / f"{task_id}.activation-decision.json"
        report_p = self.reports_dir / f"{task_id}.md"

        extra: Dict[str, str] = {}
        if self._exists(result_p):
            extra["result"] = str(result_p)
        if self._exists(done_p):
            extra["done"] = str(done_p)
        if self._exists(decision_p):
            extra["decision"] = str(decision_p)
        if self._exists(activation_p):
            extra["activation"] = str(activation_p)
        if self._exists(report_p):
            extra["report"] = str(report_p)

        return ArtifactObservation(
            task_id=task_id,
            result_present=self._exists(result_p),
            done_present=self._exists(done_p),
            report_present=self._exists(report_p),
            decision_present=self._exists(decision_p),
            activation_present=self._exists(activation_p),
            result_path=str(result_p) if self._exists(result_p) else None,
            done_path=str(done_p) if self._exists(done_p) else None,
            extra_paths=extra,
        )

    def detect_many(self, task_ids: List[str]) -> Dict[str, ArtifactObservation]:
        return {tid: self.detect(tid) for tid in task_ids}

    def terminal_outcome_hint(self, task_id: str) -> Optional[str]:
        """Read the terminal verdict/outcome from result.json (read-only).

        Mirrors actual statuses (regression 20): +26 MERGED/COMPLETE_MERGED,
        +27 PASS/DONE, +30 DONE. Returns None if unreadable.
        """
        doc = self.read_json(self.events_dir / f"{task_id}.result.json")
        if not isinstance(doc, dict):
            return None
        for key in ("verdict", "classification", "outcome", "final_status"):
            val = doc.get(key)
            if isinstance(val, str) and val.strip():
                return val.strip()
        return None
