"""Post-extraction predicate filter. Rejects claims with junk predicates/subjects."""
from __future__ import annotations

import logging
import re
from typing import List, Set

logger = logging.getLogger(__name__)

BLOCKED_PREDICATES = {
    'implements', 'fixed', 'merged',
    'deploys', 'dispatches', 'spawns', 'configures', 'has_verified',
    'is_working_on', 'has_pending_task', 'debugging',
    'reports', 'is_running',
    'is_ready_for', 'is_waiting_for', 'performs', 'has_recommended_action',
    'has_database_state', 'has_workspace_files', 'has_updated_documents',
    'appended_to_document', 'proposed_edit', 'has_issues_created',
    'has_tagged', 'gated',
    'has_open_pr', 'has_pending_merge', 'has_plan_state', 'has_status',
    'flagged', 'outlines_next_steps', 'plans_to_fix',
    'added', 'removed', 'processed', 'validated', 'checked',
    # Sprint/task noise (added 2026-03-19, audit #1173)
    'has_completed_task', 'has_pending_tasks', 'has_pending_task',
    'has_milestones', 'has_milestone', 'considers', 'considered',
    'assigns_task', 'assigned_task', 'has_ambiguous_item',
    'has_refactor_targets', 'plans_to_refactor', 'proposes',
    'prioritizes', 'prioritized', 'has_plan',
    'noted', 'updates', 'has_agent_metadata',
    'has_draft',
}

BLOCKED_SUBJECT_PATTERNS = [
    re.compile(r'^R-\d+$'),           # Sub-agent report IDs
    re.compile(r'^CodeRabbit$'),       # Automated review bot
    re.compile(r'^Sonnet$'),           # Model names as subjects
    re.compile(r'^Opus$'),
    re.compile(r'^Haiku$'),
]


def _get(obj, key: str, default=""):
    """Get a field from a dict or dataclass."""
    if isinstance(obj, dict):
        return obj.get(key, default)
    return getattr(obj, key, default)


def filter_claims(claims: list) -> list:
    """Remove claims with blocked predicates or subjects.

    Works with both raw dicts and ClaimExtraction dataclass objects.
    """
    filtered = []
    for claim in claims:
        pred = str(_get(claim, 'predicate', '')).lower().strip()
        subj = str(_get(claim, 'subject', '')).strip()
        obj = str(_get(claim, 'object_value', ''))

        # Block known junk predicates
        if pred in BLOCKED_PREDICATES:
            continue
        # Block known junk subjects
        if any(p.match(subj) for p in BLOCKED_SUBJECT_PATTERNS):
            continue
        # Block /tmp/ path references in object
        if re.search(r'/tmp/\S+\.\w+', obj):
            continue

        filtered.append(claim)
    return filtered


def filter_unknown_subjects(claims: list, known_subjects: Set[str]) -> list:
    """Remove claims whose subject is not in the known_subjects set.

    Prevents eval-data leaks and third-party contamination (e.g. LoCoMo
    characters appearing in production memories).

    Claims in relational, episodic, and personal categories are exempt because
    they are explicitly about other people/entities (e.g. "Jackie has_relationship
    first_customer").

    Args:
        claims: List of claim dicts or ClaimExtraction objects.
        known_subjects: Set of allowed subject strings.  Matching is
                        case-insensitive.  Pass an empty set to disable.

    Returns:
        Filtered list with only known-subject claims retained (plus exempt categories).
    """
    if not known_subjects:
        return claims
    allowed = {s.lower() for s in known_subjects}
    EXEMPT_CATEGORIES = {"relational", "episodic", "personal"}
    filtered = []
    for claim in claims:
        subj = str(_get(claim, 'subject', '')).strip().lower()
        category = str(_get(claim, 'category', '')).strip().lower()
        if subj in allowed or category in EXEMPT_CATEGORIES:
            filtered.append(claim)
        else:
            logger.info("Blocked claim with unknown subject: %r (predicate=%r, category=%r)",
                        subj, str(_get(claim, 'predicate', '')), category)
    return filtered


def cap_engineering_claims(claims: list, max_count: int = 8) -> list:
    """Limit decisions (formerly engineering) category claims to max_count per extraction."""
    engineering = [c for c in claims if _get(c, 'category') in ('decisions', 'engineering')]
    if len(engineering) <= max_count:
        return claims
    # Keep only the max_count with highest confidence
    engineering.sort(key=lambda c: float(_get(c, 'confidence', 0)), reverse=True)
    keep = set(id(c) for c in engineering[:max_count])
    return [c for c in claims if _get(c, 'category') not in ('decisions', 'engineering') or id(c) in keep]
