"""LLM prompt templates for auto memory extraction.

Stage 1: Unified extraction in a single LLM call.
Stage 1 (with entities): Also extracts entities and relations for Knowledge Graph.
Stage 3: Batch conflict resolution.
"""
from __future__ import annotations


# ============================================================
# Stage 1: Unified Extraction Prompt (facts only — Phase 8)
# ============================================================

UNIFIED_EXTRACTION_PROMPT = """You are a memory extraction engine for an AI agent system.

Given the conversation below, extract atomic facts that should be remembered.
For each fact, evaluate its reliability.

Rules:
- Each fact must contain exactly ONE piece of information
- Use short, declarative sentences
- Extract: preferences, decisions, personal info, plans, constraints, opinions, factual claims, knowledge, events, status updates, and any other explicitly stated information worth remembering
- Ignore: greetings, filler words, assistant acknowledgments
- Do NOT infer or speculate — only extract explicitly stated information
- Prioritize facts from USER messages (preferences, personal info, decisions, etc.)
- ALSO extract facts from ASSISTANT messages when the assistant provides substantive factual information in response to a user's question (e.g., user asks "Tell me about X" and assistant explains X — extract the key facts from the explanation)
- Do NOT extract assistant's filler responses (e.g., "Got it!", "Sure!", "I understand") as facts
- If the assistant explicitly denies, corrects, or contradicts a user's claim, do NOT extract the user's original claim as a fact, or mark confidence as "low"
- Consider the full conversation context: a fact stated by the user but rejected by the assistant is NOT a confirmed fact
- If the conversation contains no memorable facts, return an empty array

Reliability evaluation (per fact):
- CRITICAL: For claims about who currently holds a position (president, prime minister, CEO, etc.), CHECK CAREFULLY against your knowledge. Former leaders who are no longer in office MUST be "low". Example: "Abe is the Prime Minister of Japan" → "low" (Abe was PM until 2020, then Suga, then Kishida succeeded him). "Obama is the President of the United States" → "low" (Obama's term ended in 2017).
- For other factual claims about the real world (capitals, scientific facts, historical dates), actively verify against your knowledge. If the claim is wrong or outdated, mark as "low".
- high: Specific, plausible, clearly stated, AND factually correct based on your knowledge. Personal facts (preferences, plans, personal info) that are clearly stated are typically "high" since you cannot verify them.
- medium: Vague, uncertain, hedged, hearsay, OR you cannot determine whether the claim is correct (e.g., "I think I might switch frameworks"). Also use for claims about current events beyond your knowledge cutoff — if you are unsure whether the claim is still true, use "medium" rather than "high".
- low: Factually WRONG based on your knowledge (e.g., wrong political leader, wrong capital city, wrong date), or self-contradictory within the conversation. Do NOT mark correct facts as "low" even if they seem obvious or trivial — if a fact is true, it is at least "medium" or "high"

Output format (strict JSON only, no markdown):
{{"facts": [{{"content": "fact text", "confidence": "high|medium|low"}}]}}

If the conversation contains no memorable facts:
{{"facts": []}}

--- Conversation ---
{conversation}"""


# ============================================================
# Stage 1: Unified Extraction with Entities Prompt (Phase 8.5)
# ============================================================

UNIFIED_EXTRACTION_WITH_ENTITIES_PROMPT = """You are a memory extraction engine for an AI agent system.

Given the conversation below, extract:
1. Atomic facts that should be remembered
2. Named entities (people, organizations, locations, products, concepts)
3. Relationships between entities

Rules for facts:
- Each fact must contain exactly ONE piece of information
- Use short, declarative sentences
- Extract: preferences, decisions, personal info, plans, constraints, opinions, factual claims, knowledge, events, status updates, and any other explicitly stated information worth remembering
- Ignore: greetings, filler words, assistant acknowledgments
- Do NOT infer or speculate — only extract explicitly stated information
- Prioritize facts from USER messages (preferences, personal info, decisions, etc.)
- ALSO extract facts from ASSISTANT messages when the assistant provides substantive factual information in response to a user's question (e.g., user asks "Tell me about X" and assistant explains X — extract the key facts from the explanation)
- Do NOT extract assistant's filler responses (e.g., "Got it!", "Sure!", "I understand") as facts
- If the assistant explicitly denies, corrects, or contradicts a user's claim, do NOT extract the user's original claim as a fact, or mark confidence as "low"
- Consider the full conversation context: a fact stated by the user but rejected by the assistant is NOT a confirmed fact

Reliability evaluation (per fact):
- CRITICAL: For claims about who currently holds a position (president, prime minister, CEO, etc.), CHECK CAREFULLY against your knowledge. Former leaders who are no longer in office MUST be "low". Example: "Abe is the Prime Minister of Japan" → "low" (Abe was PM until 2020, then Suga, then Kishida succeeded him). "Obama is the President of the United States" → "low" (Obama's term ended in 2017).
- For other factual claims about the real world (capitals, scientific facts, historical dates), actively verify against your knowledge. If the claim is wrong or outdated, mark as "low".
- high: Specific, plausible, clearly stated, AND factually correct based on your knowledge. Personal facts (preferences, plans, personal info) that are clearly stated are typically "high" since you cannot verify them.
- medium: Vague, uncertain, hedged, hearsay, OR you cannot determine whether the claim is correct (e.g., "I think I might switch frameworks"). Also use for claims about current events beyond your knowledge cutoff — if you are unsure whether the claim is still true, use "medium" rather than "high".
- low: Factually WRONG based on your knowledge (e.g., wrong political leader, wrong capital city, wrong date), or self-contradictory within the conversation. Do NOT mark correct facts as "low" even if they seem obvious or trivial — if a fact is true, it is at least "medium" or "high"

Rules for entities:
- Extract proper nouns and important concepts
- Classify each entity: person, organization, location, product, concept, event, other
- Normalize names (e.g., "Mr. Tanaka" and "Tanaka" should be the same)

Rules for relations:
- Only extract relationships explicitly stated or clearly implied
- Use snake_case predicates (e.g., "works_at", "lives_in", "prefers")
- Each relation must connect exactly two entities from your entity list

Output format (strict JSON only, no markdown):
{{
  "facts": [{{"content": "fact text", "confidence": "high|medium|low"}}],
  "entities": [{{"name": "entity name", "type": "person|organization|location|product|concept|event|other"}}],
  "relations": [{{"subject": "entity name", "predicate": "relation_type", "object": "entity name"}}]
}}

If the conversation contains no memorable information:
{{"facts": [], "entities": [], "relations": []}}

--- Conversation ---
{conversation}"""


# ============================================================
# Stage 3: Batch Conflict Resolution Prompt
# ============================================================

BATCH_CONFLICT_PROMPT = """Compare each new fact with its most similar existing memory and determine the action.

IMPORTANT: The new_fact is ALWAYS more recent than existing_memory. Each pair includes existing_created_at to show when the existing memory was stored.

CRITICAL RULE: Facts with similar wording but key differences (opposite sentiment, different values, negation) are NEVER duplicates — they are CONTRADICTIONS. Surface-level text similarity does NOT mean semantic equivalence.

KEY PRINCIPLE — Single-value vs Multi-value attributes:
- SINGLE-VALUE attributes (a person/entity can only have ONE at a time): gender, marital status, residence, job title, employer, age, birthday, current position holder, price, etc. → Different values = CONTRADICT
- MULTI-VALUE attributes (a person/entity can have MANY simultaneously): preferences, hobbies, skills, languages, friends, favorite foods, favorite flavors, etc. → Different values = ADD (they coexist)
Ask yourself: "Can this entity have multiple values for this attribute at the same time?" If YES → ADD. If NO → CONTRADICT.

Actions:
- ADD: The new fact is different information from the existing memory (different topic or attribute). Also use ADD when the attribute is MULTI-VALUE and both facts can coexist (e.g., "likes strawberry ice cream" and "likes grape ice cream" are BOTH valid preferences — they coexist, NOT a contradiction).
- CONTRADICT: The new fact DIRECTLY conflicts with the existing memory on a SINGLE-VALUE attribute — they CANNOT both be true at the same time. Either a logical incompatibility or an outdated value replaced by newer information (both are kept; confidence determines ranking). Do NOT use CONTRADICT for multi-value attributes where multiple values naturally coexist (e.g., multiple hobbies, multiple favorite foods, multiple favorite flavors, multiple friends).
- NOOP: The new fact conveys the EXACT SAME meaning as the existing memory — only use when both say the same thing in different words (e.g., "likes cats" and "loves cats" are NOOP). NEVER use NOOP when the facts have opposite meanings, different values, or any meaningful difference. If in doubt between NOOP and CONTRADICT, choose CONTRADICT.

NOOP verification checklist (you MUST check ALL before classifying as NOOP):
1. Do both facts express the SAME sentiment/attitude? (likes vs hates = NO)
2. Do both facts state the SAME value? (Tokyo vs Osaka = NO)
3. Is one a negation of the other? (is married vs is not married = YES → CONTRADICT)
4. Can both statements be true simultaneously? (If NO → CONTRADICT)
If ANY check fails, the action MUST be CONTRADICT, not NOOP.

For CONTRADICT, determine the winner:
- winner: "new" — DEFAULT. The new fact is always more recent, so it typically supersedes the existing memory. Use this for: temporal updates (leadership changes, job changes, status changes, price changes, address changes), corrections, and any case where the same attribute has a new value.
- winner: "existing" — RARE. Only use when the new fact is clearly less credible than the existing memory.
- winner: "unknown" — RARE. Only use when both facts express genuinely subjective conflicting opinions with no temporal aspect (e.g., "likes cats" vs "hates cats").

RULE: If the contradiction is about the same attribute having different values (e.g., who holds a position, where someone works, what something costs), the winner is ALWAYS "new" because the new fact is more recent.

And assign the loser's confidence:
- loser_confidence: "low" — the loser is outdated or incorrect (DEFAULT for temporal updates)
- loser_confidence: "medium" — the loser might still have some validity
- loser_confidence: "high" — the loser is still quite credible despite the conflict

Examples:

Example 1 (correct NOOP — same meaning):
  new_fact: "Has cat allergy"
  existing_memory: "Has cat allergy"
  → {{"action": "NOOP", "reason": "identical information"}}

Example 1b (correct NOOP — same meaning, different wording):
  new_fact: "Allergic to cats"
  existing_memory: "Has cat allergy"
  → {{"action": "NOOP", "reason": "same meaning expressed differently"}}

WARNING: The following are NOT NOOP — they look similar but have opposite meanings:
  "Likes cats" vs "Hates cats" → CONTRADICT (opposite sentiment)
  "Is married" vs "Is not married" → CONTRADICT (negation)
  "Lives in Tokyo" vs "Lives in Osaka" → CONTRADICT (different value)
  "Company was founded in 1990" vs "Company was founded in 1985" → CONTRADICT (different date)

Example 2:
  new_fact: "Travel budget is 200,000 yen"
  existing_memory: "Travel budget is 150,000 yen"
  → {{"action": "CONTRADICT", "winner": "new", "loser_confidence": "low", "reason": "budget amount updated from 150k to 200k"}}

Example 3:
  new_fact: "Prefers hotels near train stations"
  existing_memory: "Travel budget is 150,000 yen"
  → {{"action": "ADD", "reason": "hotel preference is different info from budget"}}

Example 4:
  new_fact: "Hates hotels near train stations"
  existing_memory: "Prefers hotels near train stations"
  → {{"action": "CONTRADICT", "winner": "unknown", "loser_confidence": "medium", "reason": "opposite hotel location preferences"}}

Example 5:
  new_fact: "The prime minister is Takaichi"
  existing_memory: "The prime minister is Abe"
  → {{"action": "CONTRADICT", "winner": "new", "loser_confidence": "low", "reason": "prime minister has changed"}}

Example 6:
  new_fact: "Works at Google"
  existing_memory: "Works at Microsoft"
  → {{"action": "CONTRADICT", "winner": "new", "loser_confidence": "low", "reason": "employer has changed"}}

Example 7:
  new_fact: "Is vegetarian"
  existing_memory: "Loves eating steak"
  → {{"action": "CONTRADICT", "winner": "unknown", "loser_confidence": "medium", "reason": "being vegetarian and loving steak cannot both be true"}}

Example 8 (coexistence — NOT a contradiction):
  new_fact: "Likes sushi"
  existing_memory: "Likes pizza"
  → {{"action": "ADD", "reason": "multiple food preferences can coexist (multi-value attribute)"}}

Example 9 (coexistence — NOT a contradiction):
  new_fact: "Speaks French"
  existing_memory: "Speaks English"
  → {{"action": "ADD", "reason": "a person can speak multiple languages (multi-value attribute)"}}

Example 10 (coexistence — same category, different values, both valid):
  new_fact: "Likes grape flavor ice cream"
  existing_memory: "Likes strawberry flavor ice cream"
  → {{"action": "ADD", "reason": "a person can like multiple ice cream flavors (multi-value attribute)"}}

Example 11 (CONTRADICT — single-value attribute):
  new_fact: "Is female"
  existing_memory: "Is male"
  → {{"action": "CONTRADICT", "winner": "new", "loser_confidence": "low", "reason": "gender is a single-value attribute, cannot be both"}}

Pairs to evaluate:
{pairs_json}

Output format (strict JSON only, no markdown):
{{"results": [{{"index": 0, "action": "ADD|CONTRADICT|NOOP", "reason": "brief explanation"}}]}}
For CONTRADICT, include winner and loser_confidence fields."""


# ============================================================
# Similarity threshold for conflict detection
# ============================================================

CONFLICT_SIMILARITY_THRESHOLD = 0.60


# ============================================================
# MinHash pre-filter constants
# ============================================================

MINHASH_NOOP_THRESHOLD = 0.98   # Jaccard >= 0.98 → auto-NOOP (skip LLM call)
MINHASH_NUM_PERM = 128          # Number of MinHash permutations


# ============================================================
# Helper functions
# ============================================================

def format_conversation(messages: list[dict]) -> str:
    """Convert messages list to a formatted conversation string.

    Args:
        messages: List of message dicts with ``role``, ``content``, and
                  optional ``name`` fields::

                      {"role": "user", "name": "alex", "content": "Hello"}

    Returns:
        Formatted string like ``"alex (user): Hello\\nassistant: Hi there"``
    """
    lines = []
    for msg in messages:
        role = msg.get("role", "unknown")
        content = msg.get("content", "")
        name = msg.get("name")
        if name:
            lines.append(f"{name} ({role}): {content}")
        else:
            lines.append(f"{role}: {content}")
    return "\n".join(lines)
