#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Wants Parser: Extract WANTS and DON'T_WANTS from natural language (V43.13)

Parses user prompts to identify:
- WANTS: What the user explicitly requests
- DON'T_WANTS: What the user explicitly rejects or avoids
- Priority signals: Urgency indicators
- Domain mapping: Which brain domain the request belongs to

Dependencies: Python stdlib only (Law 4)
"""

import re
from datetime import datetime, timezone
from typing import Dict, List

# Re-export from wants_classify sub-module (knowledge_extractor.py imports these)
from pulse.brain.extraction.wants_classify import (
    _classify_domain, _compute_priority, _detect_visibility, _deduplicate,
)


# === SIGNAL PATTERNS ===

# Positive want signals (user asks FOR something)
WANT_PATTERNS = [
    # Immediate action
    r"(?:^|\s)(?:add|create|build|implement|write|setup|enable)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:i want|i need|we need|please)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:can you|could you|would you)\s+(?!explain|check|tell|look|see|read|show|help|clarify|confirm)(.+?)(?:\.|$)",
    r"(?:^|\s)(?:let's|lets)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:upgrade|improve|enhance|fix|update)\s+(.+?)(?:\.|$)",
    # Future-oriented / aspirational
    r"(?:^|\s)(?:we should|i should)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:next we|next time|going forward)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:i'd like|i would like|we'd like)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:ideally|eventually|long.?term)\s+(.+?)(?:\.|$)",
    # Implicit needs (gap detection)
    r"(?:^|\s)(?:we lack|we're missing|missing|we don't have)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:it would be (?:good|great|nice|better) (?:to|if))\s+(.+?)(?:\.|$)",
    # Planning intent
    r"(?:^|\s)(?:i want to|we want to|we need to)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:i'm|we're|i am|we are)\s+(?:gonna|going to)\s+(.+?)(?:\.|$)",
    # Conversational planning
    r"(?:^|\s)(?:shall we|what about|how about)\s+(.+?)(?:\?|\.|$)",
    r"(?:^|\s)(?:i think we should|i think we could)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:maybe (?:tomorrow|later|next) we (?:could|should|can))\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:let's note|note for (?:next|later|tomorrow))\s+(.+?)(?:\.|$)",
]

# Temporal scope mapping: pattern index → scope
# long_term: should/ideally/eventually/next time/i think we should/what about/note for
# short_term: going to/planning to/the plan is/shall we/maybe tomorrow
# immediate: everything else (add/create/build/I want/fix/upgrade/lack/missing)
_LONG_TERM_INDICES = {5, 6, 7, 8, 14, 16}  # should, next time, i'd like, ideally, i think we should, note for
_SHORT_TERM_INDICES = {12, 13, 15}           # the plan is/gonna/going to, shall we/what about, maybe tomorrow

# Negative want signals (user rejects something)
DONT_WANT_PATTERNS = [
    r"(?:^|\s)(?:don't|dont|do not|must not|should not|never|stop)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:no more|eliminate|get rid of)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:remove|delete|drop|disable|avoid)\s+(.+?)(?:\.|$)",
    r"(?:^|\s)(?:this is wrong|redo|revert|rollback)\s*(.*)(?:\.|$)",
    r"(?:^|\s)(?:instead of)\s+(.+?)(?:,|\.|$)",
]

# Rejection context signals (negative feedback on agent output)
REJECTION_SIGNALS = [
    "this is wrong", "that's not right", "incorrect",
    "redo", "try again", "not what i asked", "not what i meant",
    "revert", "undo", "go back", "start over",
]


def _temporal_scope(pattern_idx: int) -> str:
    """Classify temporal scope based on which pattern triggered the match."""
    if pattern_idx in _LONG_TERM_INDICES:
        return "long_term"
    if pattern_idx in _SHORT_TERM_INDICES:
        return "short_term"
    return "immediate"


_CONVERSATIONAL_STARTERS = re.compile(
    r"^(?:ok|okay|sure|yeah|yes|no|right|well|so|hmm|hm|ah|oh|"
    r"what|why|how|who|where|when|which|got it|i see|thanks|thank you|"
    r"alright|fine|good|great|nice|cool|yep|nope|nah)\b",
    re.IGNORECASE
)


def parse_wants(text: str) -> tuple[List[Dict], List[Dict]]:
    """
    Extract WANTS from user prompt text.

    Returns tuple (wants, dont_wants) where dont_wants contains reclassified negated wants.
    """
    wants = []
    dont_wants = []  # QR8 P13 fix: was undefined, causing NameError on polarity branch
    text_lower = text.lower().strip()

    for idx, pattern in enumerate(WANT_PATTERNS):
        matches = re.findall(pattern, text_lower, re.IGNORECASE)
        for match in matches:   
            match = match.strip()
            # Quality gate: min 3 words AND 10 chars to avoid noise
            word_count = len(match.split())
            if word_count < 3 or len(match) < 10 or len(match) > 200:
                continue        
            # Reject truncated fragments (ends mid-word/sentence)
            if match.endswith("\\n") or match.endswith("\\"):   
                continue        
            # Reject conversational fragments
            if _CONVERSATIONAL_STARTERS.match(match):
                continue        
            # Polarity check: reclassify negated wants as dont_wants
            _match_lower = match.lower()
            if any(neg in _match_lower for neg in ["never ", "not ", "don't ", "dont ", "avoid ", "stop ", "disable "]):        
                dont_wants.append({"dont_want": match, "priority": _compute_priority(text_lower, _classify_domain(match)),      
                    "domain": _classify_domain(match), "timestamp": datetime.now(timezone.utc).isoformat(), "source": "polarity_fix"})
                continue        
            domain = _classify_domain(match)
            want = {
                "want": match,  
                "priority": _compute_priority(text_lower, domain),
                "domain": domain,
                "visibility": _detect_visibility(match),        
                "temporal_scope": _temporal_scope(idx),
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "source": "auto_parsed",
            }
            wants.append(want)  

    # Deduplicate by content similarity
    return _deduplicate(wants), _deduplicate(dont_wants)

def parse_dont_wants(text: str) -> List[Dict]:
    """
    Extract DON'T_WANTS from user prompt text.

    Returns list of dont_want dicts with description, reason, domain.
    """
    dont_wants = []
    text_lower = text.lower().strip()

    for pattern in DONT_WANT_PATTERNS:
        matches = re.findall(pattern, text_lower, re.IGNORECASE)
        for match in matches:
            match = match.strip()
            # Quality gate: min 5 words AND 10 chars to avoid noise
            word_count = len(match.split())
            if word_count < 5 or len(match) < 10 or len(match) > 200:
                continue
            if match.endswith("\\n") or match.endswith("\\"):
                continue
            dw = {
                "dont_want": match,
                "reason": "user_explicit_rejection",
                "domain": _classify_domain(match),
                "visibility": _detect_visibility(match),
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "source": "auto_parsed",
            }
            dont_wants.append(dw)

    return _deduplicate(dont_wants)


def detect_rejection(user_text: str, agent_text: str) -> Dict:
    """
    Detect if user is rejecting/correcting agent output.

    Returns rejection dict or None if no rejection detected.
    """
    user_lower = user_text.lower().strip()

    for signal in REJECTION_SIGNALS:
        if signal in user_lower:
            # Store the user's rejection context, not agent's response text
            rejection_context = user_text.strip()[:150]
            return {
                "dont_want": rejection_context,
                "reason": f"user_rejection: {signal}",
                "domain": _classify_domain(user_lower),
                "visibility": _detect_visibility(user_lower),
                "timestamp": datetime.now(timezone.utc).isoformat(),
                "source": "rejection_detection",
                "signal": signal,
            }
    return None


def compute_emotional_intensity(text: str) -> float:
    """
    Score 0.0-1.0 emotional intensity of user text (Symposium P0 — Phase 2).

    Higher intensity = more important interaction. Used to boost salience
    so emotionally charged facts/decisions are remembered more strongly.
    Only score USER text — agent text is not emotional.
    """
    if not text or len(text) < 3:
        return 0.0

    score = 0.0

    # ALL CAPS words (3+ chars, outside code blocks) — excitement/frustration
    # Skip words that look like constants (SNAKE_CASE with underscores)
    caps_words = [w for w in re.findall(r'\b[A-Z]{3,}\b', text)
                  if '_' not in w and w not in ("THE", "AND", "FOR", "BUT", "NOT", "YOU", "ARE")]
    score += min(len(caps_words) * 0.15, 0.4)

    # Exclamation marks
    score += min(text.count('!') * 0.1, 0.3)

    # Question mark clusters (???)
    if '???' in text or '?!?' in text:
        score += 0.2

    # Emoticons and internet expressions
    emotive = len(re.findall(
        r'(?:T_T|xD|lmao|lol|omg|wtf|smh|bruh|damn|dude|bro!|yooo|lmfao)',
        text, re.IGNORECASE))
    score += min(emotive * 0.1, 0.3)

    # Frustration/excitement words
    emotion_words = len(re.findall(
        r'\b(?:frustrated?|annoying|painful|awful|terrible|nightmare|hell|'
        r'excited|amazing|brilliant|insane|crazy|awesome|incredible|'
        r'hate|love|furious|thrilled|devastat|disaster|gorgeous)\b',
        text, re.IGNORECASE))
    score += min(emotion_words * 0.15, 0.3)

    # Repeated punctuation (!!!, ..., ???)
    if re.search(r'[!?]{3,}', text):
        score += 0.15

    return min(round(score, 2), 1.0)


def parse_full(user_text: str, agent_text: str = "") -> Dict:   
    """
    Full parsing pipeline: extract all signals from a prompt+response pair.

    Returns dict with wants, dont_wants, rejection, priority, domain,
    and emotional_intensity (0.0-1.0).
    """
    wants, polarity_dont_wants = parse_wants(user_text)
    dont_wants = parse_dont_wants(user_text)
    
    # Combine with dont_wants found during want parsing (polarity fix)
    dont_wants.extend(polarity_dont_wants)
    
    rejection = detect_rejection(user_text, agent_text) if agent_text else None

    if rejection:
        dont_wants.append(rejection)

    primary_domain = _classify_domain(user_text.lower())
    priority = _compute_priority(user_text.lower(), primary_domain)
    visibility = _detect_visibility(user_text.lower())

    return {
        "wants": wants,
        "dont_wants": dont_wants,
        "has_rejection": rejection is not None,
        "primary_domain": primary_domain,
        "priority": priority,
        "visibility": visibility,
        "emotional_intensity": compute_emotional_intensity(user_text),
        "timestamp": datetime.now(timezone.utc).isoformat(),
    }


# === CLI ===

if __name__ == "__main__":
    import sys
    import json

    if len(sys.argv) < 2:
        print("Usage: python wants_parser.py <user_prompt> [agent_response]")
        print("Parses WANTS and DON'T_WANTS from natural language.")
        sys.exit(1)

    user = sys.argv[1]
    agent = sys.argv[2] if len(sys.argv) > 2 else ""
    result = parse_full(user, agent)
    print(json.dumps(result, indent=2))
