# SPDX-License-Identifier: Apache-2.0
"""Contradiction detection: opposing-pair matching between claims.

Extracted from governor's evidence_gate.py. Detects when new claims
contradict prior claims using a hand-curated table of opposing pairs.

Deliberately small and mechanical — not an NLP truth engine. Word-boundary
aware to avoid matching inside unrelated words.

Same-batch contradictions are out of scope. Contradictions are only
between new claims and prior_claims.
"""

from __future__ import annotations

import re

from .claims import Claim, STATUS_SUPPORTED, STATUS_CONTESTED, contest_claim


# Hand-curated opposing pairs. Deliberately minimal and extensible.
OPPOSING_PAIRS: tuple[tuple[str, str], ...] = (
    ("improves", "degrades"),
    ("faster", "slower"),
    ("better", "worse"),
    ("increases", "decreases"),
    ("safe", "unsafe"),
    ("secure", "insecure"),
    ("passes", "fails"),
    ("correct", "incorrect"),
    ("valid", "invalid"),
)


def _normalize_for_matching(text: str) -> str:
    """Normalize text for matching: lowercase, collapse whitespace."""
    return re.sub(r"\s+", " ", text.lower().strip())


def _has_word(text: str, word: str) -> bool:
    """Check if word appears in text at word boundary."""
    return bool(re.search(r"\b" + re.escape(word) + r"\b", text))


def find_contradictions(
    claims: list[Claim],
    prior_claims: list[Claim],
) -> list[Claim]:
    """Check new claims against priors for contradictions.

    Returns new Claim instances with conflicts_with populated
    and status set to CONTESTED. Does NOT modify inputs.

    Only SUPPORTED and CONTESTED priors can trigger contradictions.
    UNSUPPORTED priors are excluded — historical noise should not
    contest everything forever.

    Same-batch contradictions are out of scope: claims within the
    same list do not contest each other.
    """
    if not claims or not prior_claims:
        return list(claims)

    # Filter priors: only SUPPORTED and CONTESTED can trigger
    active_priors = [
        p for p in prior_claims
        if p.status in (STATUS_SUPPORTED, STATUS_CONTESTED)
    ]
    if not active_priors:
        return list(claims)

    result: list[Claim] = []

    for claim in claims:
        norm_claim = _normalize_for_matching(claim.text)
        updated = claim

        for prior in active_priors:
            norm_prior = _normalize_for_matching(prior.text)

            for word_a, word_b in OPPOSING_PAIRS:
                # Check both directions
                if (_has_word(norm_claim, word_a) and _has_word(norm_prior, word_b)) or \
                   (_has_word(norm_claim, word_b) and _has_word(norm_prior, word_a)):
                    updated = contest_claim(updated, prior.text)
                    break  # One contradiction per prior is enough

        result.append(updated)

    return result
