"""Canonical state comment validation helpers (#495).

These helpers validate durable issue/PR/discussion state handoff comments.
They are intentionally pure and do not post comments; MCP mutation hooks are
owned by #496.
"""

from __future__ import annotations

import re

CANONICAL_HEADINGS = (
    "canonical issue state",
    "canonical pr state",
    "canonical discussion summary",
)

REQUIRED_FIELDS = ("STATE", "WHO_IS_NEXT", "NEXT_ACTION", "NEXT_PROMPT")
ALLOWED_NEXT_ACTORS = {
    "controller",
    "author",
    "reviewer",
    "merger",
    "reconciler",
    "user",
}

VAGUE_NEXT_ACTIONS = {
    "continue",
    "handle this",
    "do it",
    "fix it",
    "proceed",
    "follow up",
    "next",
    "tbd",
    "todo",
    "n/a",
    "none",
}

_FIELD_RE = re.compile(r"^\s*(?:[-*]\s*)?([A-Z][A-Z0-9_ ]+)\s*:\s*(.*)$")
_FULL_SHA_RE = re.compile(r"\b[0-9a-f]{40}\b", re.IGNORECASE)
_CLAIMS_STATE_UPDATE_RE = re.compile(
    r"canonical\s+(?:issue|pr|discussion)?\s*state|"
    r"state\s+comment\s+(?:posted|created|updated)|"
    r"next[- ]action\s+comment\s+(?:posted|created|updated)",
    re.IGNORECASE,
)


def extract_state_fields(text: str | None) -> dict[str, str]:
    """Return upper-case labeled fields from a canonical state block."""
    fields: dict[str, str] = {}
    current_key: str | None = None
    for line in (text or "").splitlines():
        match = _FIELD_RE.match(line)
        if match:
            current_key = match.group(1).strip().upper().replace(" ", "_")
            fields[current_key] = match.group(2).strip()
            continue
        stripped = line.strip()
        if current_key and stripped and not stripped.startswith("#"):
            existing = fields.get(current_key, "")
            fields[current_key] = (
                f"{existing}\n{stripped}" if existing else stripped
            )
    return fields


def contains_canonical_state_block(text: str | None) -> bool:
    lower = (text or "").lower()
    return any(heading in lower for heading in CANONICAL_HEADINGS)


def claims_canonical_state_update(text: str | None) -> bool:
    """Return True when text claims a canonical state/next-action update."""
    return bool(_CLAIMS_STATE_UPDATE_RE.search(text or ""))


def _empty_or_placeholder(value: str | None) -> bool:
    value = (value or "").strip().lower()
    return not value or value in {"none", "n/a", "unknown", "tbd", "<...>"}


def _vague_next_action(value: str | None) -> bool:
    normalized = re.sub(r"\s+", " ", (value or "").strip().lower())
    return normalized in VAGUE_NEXT_ACTIONS


def validate_canonical_state_comment(text: str | None) -> dict:
    """Validate a canonical state comment or embedded final-report block.

    Returns a dict with ``valid`` and ``reasons``. The validator focuses on
    fields that make continuation possible: current state, next actor, next
    action, and paste-ready next prompt, plus a few contradiction checks.
    """
    fields = extract_state_fields(text)
    reasons: list[str] = []

    for field in REQUIRED_FIELDS:
        if _empty_or_placeholder(fields.get(field)):
            reasons.append(f"missing required canonical state field: {field}")

    actor = (fields.get("WHO_IS_NEXT") or "").strip().lower()
    if actor and actor not in ALLOWED_NEXT_ACTORS:
        reasons.append(
            "WHO_IS_NEXT must be one of: "
            + ", ".join(sorted(ALLOWED_NEXT_ACTORS))
        )

    if _vague_next_action(fields.get("NEXT_ACTION")):
        reasons.append("NEXT_ACTION is too vague for durable continuation")

    state = (fields.get("STATE") or "").strip().lower().replace("-", "_")
    if "ready_to_merge" in state or (
        state == "approved" and "pr" in (text or "").lower()
    ):
        review_status = (fields.get("REVIEW_STATUS") or "").lower()
        head_sha = fields.get("HEAD_SHA") or ""
        merge_ready = (fields.get("MERGE_READY") or "").lower()
        if "approved" not in review_status:
            reasons.append("ready-to-merge state requires approved REVIEW_STATUS")
        if not _FULL_SHA_RE.search(head_sha):
            reasons.append("ready-to-merge state requires full HEAD_SHA proof")
        if merge_ready and not merge_ready.startswith(("yes", "true")):
            reasons.append("ready-to-merge state contradicts MERGE_READY")

    if "superseded" in state:
        canonical = fields.get("CANONICAL_ITEM") or fields.get("SUPERSEDED_BY")
        if _empty_or_placeholder(canonical):
            reasons.append("superseded state requires canonical item proof")

    if "blocked" in state:
        blockers = fields.get("BLOCKERS") or ""
        if _empty_or_placeholder(blockers):
            reasons.append("blocked state requires BLOCKERS/unblock condition")

    return {
        "valid": not reasons,
        "fields": fields,
        "reasons": reasons,
    }


def validate_final_report_state_update(report_text: str | None) -> dict:
    """Validate canonical state-update claims inside a final report."""
    text = report_text or ""
    if not claims_canonical_state_update(text) and not contains_canonical_state_block(text):
        return {
            "applicable": False,
            "valid": True,
            "reasons": [],
        }

    if not contains_canonical_state_block(text):
        return {
            "applicable": True,
            "valid": False,
            "reasons": [
                "final report claims a canonical state update but includes no canonical state block"
            ],
        }

    result = validate_canonical_state_comment(text)
    return {
        "applicable": True,
        "valid": result["valid"],
        "fields": result["fields"],
        "reasons": result["reasons"],
    }
