# -*- coding: utf-8 -*-
"""dispatch.progress_watcher_gate — dispatch 직후 progress watcher 등록 게이트.

task-2729 Phase 1 — progress watcher dispatch gate.

회장 목표:
  dispatch() 가 성공 반환하기 직전, 해당 task 의 progress watcher
  (CI_WATCH_HANDOFF runner) 가 등록되었는지를 검증하는 record-only 게이트.

  ★ ACTIVE=false (Phase 1):
    본 게이트는 record-only 로만 결선된다. annotate_dispatch_result(active=False)
    경로에서는 기존 dispatch result["status"] 를 절대 변경하지 않는다 (기존 behavior 무손상).
    progress_watcher_registered 필드와 incomplete reason 만 기록한다.
    active=True 운영 전환은 별도 회장 승인 대상이다.

  ★ fallback 은 progress trigger 아님:
    callback_fallback_prune fallback 은 dead-man safety-net 일 뿐 progress trigger 가
    아니다. progress watcher 부재 시 fallback 만 등록되어 있어도 게이트는 incomplete
    으로 판정한다 (fallback_only).

본 모듈은 stdlib(typing, dataclasses)만 import 한다. 무거운 import 금지.
"""
from __future__ import annotations

from dataclasses import dataclass
from typing import Dict

# ---------------------------------------------------------------------------
# 상수 (문자열 grep 대상 — verbatim 유지)
# ---------------------------------------------------------------------------
DISPATCH_INCOMPLETE = "DISPATCH_INCOMPLETE"
WATCHER_TERMINAL_CALLBACK_NOT_WIRED = "WATCHER_TERMINAL_CALLBACK_NOT_WIRED"
PROGRESS_WATCHER_REGISTERED_KEY = "progress_watcher_registered"
DISPATCH_OK = "dispatched"

# watcher 가 추적해야 하는 6 상태
# (head change · non-force push · CI · finish-task .done · normal callback · fallback prune)
WATCHER_TRACKED_STATES = (
    "head_change",
    "non_force_push",
    "ci",
    "finish_done",
    "normal_callback",
    "fallback_prune",
)


@dataclass
class GateResult:
    """progress watcher 게이트 판정 결과."""

    dispatch_status: str
    progress_watcher_registered: bool
    incomplete: bool = False
    incomplete_reason: str = ""
    fallback_only: bool = False


def evaluate_progress_watcher_gate(
    *,
    watcher_registered: bool,
    fallback_registered: bool = False,
    base_status: str = DISPATCH_OK,
) -> GateResult:
    """progress watcher 등록 여부로 게이트 결과를 판정.

    - watcher_registered=True → 정상 (incomplete=False, base_status 유지).
    - watcher_registered=False · fallback_registered=True → fallback-only.
        ★ fallback 은 progress trigger 아님 → DISPATCH_INCOMPLETE.
    - watcher_registered=False · fallback_registered=False → no progress watcher.
    """
    if watcher_registered:
        return GateResult(
            dispatch_status=base_status,
            progress_watcher_registered=True,
            incomplete=False,
        )

    if fallback_registered:
        return GateResult(
            dispatch_status=DISPATCH_INCOMPLETE,
            progress_watcher_registered=False,
            incomplete=True,
            fallback_only=True,
            incomplete_reason="fallback_only_no_progress_watcher",
        )

    return GateResult(
        dispatch_status=DISPATCH_INCOMPLETE,
        progress_watcher_registered=False,
        incomplete=True,
        incomplete_reason="no_progress_watcher",
    )


def annotate_dispatch_result(
    result: dict,
    *,
    watcher_registered: bool,
    fallback_registered: bool = False,
    active: bool = False,
) -> dict:
    """dispatch() 반환 dict 에 게이트 결과를 기록 (같은 dict 반환).

    - result[PROGRESS_WATCHER_REGISTERED_KEY] = bool(watcher_registered) 항상 기록.
    - incomplete 면 progress_watcher_gate / progress_watcher_incomplete_reason 기록.
    - ★ active=False (Phase 1 기본) → result["status"] 변경 금지 (기존 behavior 무손상).
    - active=True 일 때만 incomplete 시 result["status"] = DISPATCH_INCOMPLETE.
    """
    # ★ 방어적 hardening (HIGH-2): result 가 dict 아니거나 None 이면 TypeError 로 dispatch 가
    #   중단될 수 있다. record-only 게이트는 비정상 입력 시 원본을 그대로 반환한다 (무손상).
    if not isinstance(result, dict):
        return result

    result[PROGRESS_WATCHER_REGISTERED_KEY] = bool(watcher_registered)

    base_status = result.get("status", DISPATCH_OK)
    gr = evaluate_progress_watcher_gate(
        watcher_registered=watcher_registered,
        fallback_registered=fallback_registered,
        base_status=base_status,
    )

    if gr.incomplete:
        result["progress_watcher_gate"] = DISPATCH_INCOMPLETE
        result["progress_watcher_incomplete_reason"] = gr.incomplete_reason
        if active:
            result["status"] = DISPATCH_INCOMPLETE

    return result


def watcher_terminal_callback_status(
    *,
    terminal_reached: bool,
    callback_fired: bool,
) -> str:
    """watcher terminal 도달 시 ANU normal callback 결선 상태 분류.

    - terminal 도달 · callback 미발사 → WATCHER_TERMINAL_CALLBACK_NOT_WIRED.
    - terminal 도달 · callback 발사 → WATCHER_TERMINAL_CALLBACK_WIRED.
    - terminal 미도달 → WATCHER_NON_TERMINAL.
    """
    if terminal_reached and not callback_fired:
        return WATCHER_TERMINAL_CALLBACK_NOT_WIRED
    if terminal_reached and callback_fired:
        return "WATCHER_TERMINAL_CALLBACK_WIRED"
    return "WATCHER_NON_TERMINAL"


def all_states_tracked(tracked: Dict[str, bool]) -> bool:
    """WATCHER_TRACKED_STATES 의 모든 키가 tracked 에 True 로 있는지."""
    return all(bool(tracked.get(state)) for state in WATCHER_TRACKED_STATES)


__all__ = [
    "DISPATCH_INCOMPLETE",
    "WATCHER_TERMINAL_CALLBACK_NOT_WIRED",
    "PROGRESS_WATCHER_REGISTERED_KEY",
    "DISPATCH_OK",
    "WATCHER_TRACKED_STATES",
    "GateResult",
    "evaluate_progress_watcher_gate",
    "annotate_dispatch_result",
    "watcher_terminal_callback_status",
    "all_states_tracked",
]
