# -*- coding: utf-8 -*-
"""dispatch.spec_template_validator — mandatory normal-callback clause gate.

task-2553+32 (§4.1 / §4.3 / §6.1 / §6.2 / §6.11).

Validates that an executor task spec / generated dispatch prompt carries the
MANDATORY executor normal completion callback clause, and provides an
idempotent injector so a generated dispatch prompt auto-includes it.

Standalone, zero-mutation: imports/edits ZERO tracked module (dispatch/__init__
.py untouched, §8). Pure stdlib.

NO-CRON note (9-R.1): this module performs ZERO cron register/remove. It only
inspects/normalizes spec text. The clause it enforces describes the EXECUTOR
firing its own completion callback — a designed lifecycle signal, not a
registry/checkpoint ad-hoc cron, and not a +32 cron-remove.
"""
from __future__ import annotations

import re
from dataclasses import dataclass
from typing import List

from dispatch.executor_completion_contract import (
    NO_CRON_CORRECTED_DEFINITION,
)

VALIDATOR_SCHEMA = "dispatch.spec_template_validator.v1"

PASS = "PASS"
FAIL = "FAIL"
HOLD = "HOLD_FOR_CHAIR"

# Markers that, together, evidence the mandatory normal completion callback
# clause in a spec/prompt. Robust to KO/EN phrasing (§6.1/§6.11).
_CALLBACK_INTENT = re.compile(
    r"(normal\s+completion\s+callback|정상\s*완료\s*콜백|"
    r"completion\s+callback\s+cron|normal\s+collector\s+callback)",
    re.IGNORECASE,
)
_MANDATORY = re.compile(
    r"(mandatory|필수|반드시|MUST|required)", re.IGNORECASE
)
_FIRE_VERB = re.compile(
    r"(발사|발화|fire|register|등록|send|발송)", re.IGNORECASE
)

# The canonical clause auto-injected into generated dispatch prompts (§4.1).
MANDATORY_CALLBACK_CLAUSE = (
    "[EXECUTOR COMPLETION CALLBACK — MANDATORY] 작업 완료 직후(정상 종료 "
    "직전) executor 는 ANU 에 normal completion callback cron 을 반드시 "
    "발사해야 한다. 이는 mandatory lifecycle signal 이며 NO-CRON 으로 "
    "생략 불가. callback 4-tuple = {task_id, dispatch_cron_id, "
    "normal_collector_cron_id(필수), fallback_callback_cron_id}. "
    "normal collector 성공 시 fallback cancel-on-success. "
    + NO_CRON_CORRECTED_DEFINITION
)


@dataclass
class SpecValidationResult:
    verdict: str  # PASS | FAIL | HOLD_FOR_CHAIR
    reasons: List[str]

    @property
    def ok(self) -> bool:
        return self.verdict == PASS


def spec_has_normal_callback_clause(spec_text: str) -> bool:
    """True iff the spec/prompt carries the mandatory clause (§6.1)."""
    if not spec_text:
        return False
    has_intent = bool(_CALLBACK_INTENT.search(spec_text))
    has_mandatory = bool(_MANDATORY.search(spec_text))
    has_fire = bool(_FIRE_VERB.search(spec_text))
    return has_intent and has_mandatory and has_fire


def validate_spec(
    spec_text: str, *, spec_location_known: bool = True
) -> SpecValidationResult:
    """§4.3 / §6.1 / §6.2 — gate an executor spec/prompt.

    * clause present                 -> PASS
    * clause missing                 -> FAIL
    * spec template location unknown -> HOLD_FOR_CHAIR (§9 trigger)
    """
    if not spec_location_known:
        return SpecValidationResult(
            HOLD,
            [
                "dispatch/spec template location unresolvable — cannot "
                "codify clause (§9 HOLD_FOR_CHAIR)."
            ],
        )
    if spec_has_normal_callback_clause(spec_text):
        return SpecValidationResult(PASS, [])
    return SpecValidationResult(
        FAIL,
        [
            "executor normal completion callback clause MISSING — every "
            "executor spec/prompt MUST require the executor to fire its "
            "normal completion callback (§4.3/§6.2). NO-CRON does not "
            "exempt this (§6.3/§6.5)."
        ],
    )


def inject_completion_callback_clause(prompt: str) -> str:
    """§4.1 / §6.11 — idempotently ensure the clause is in a dispatch prompt.

    Returns the prompt unchanged if the clause is already present, else
    appends the canonical MANDATORY_CALLBACK_CLAUSE. Pure string transform,
    no side effects, no cron.
    """
    if prompt and spec_has_normal_callback_clause(prompt):
        return prompt
    sep = "" if (not prompt or prompt.endswith("\n")) else "\n"
    return f"{prompt or ''}{sep}\n{MANDATORY_CALLBACK_CLAUSE}\n"
