"""Shared detectors for cross-thread recall and continuity greeting signals."""
from __future__ import annotations

import re
from typing import Any

from utils.transcript_roles import is_real_user_message

MUGI_THREAD_PREFIX_RE = re.compile(
    r"mugi-thread:\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})",
    re.IGNORECASE,
)

THREAD_UUID_RE = re.compile(
    r"\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b",
    re.IGNORECASE,
)

CROSS_THREAD_SCOPE_HINT_RE = re.compile(
    r"mugi-thread:\s*[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
    r"|\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b"
    r"|\b(another|other|different|previous|prior|earlier)\s+(thread|chat|conversation)\b"
    r"|\b(in|from)\s+a\s+different\s+(thread|chat|conversation)\b"
    r"|\bcould(?:n['\u2019]?t| not)\s+.{0,48}\bthat\s+(thread|chat)\b",
    re.IGNORECASE,
)

_TECHNICAL_MEMORY_RE = re.compile(
    r"\b("
    r"computer|system|unified|gpu|ram|vram|storage|cache|heap|allocator|"
    r"memory\s+(?:leak|usage|pressure|limit|bandwidth|allocation|profile)"
    r")\b",
    re.IGNORECASE,
)

_EXPLICIT_RECALL_INTENT_RE = re.compile(
    r"\b("
    r"previous\s+(?:conversation|conversations|chat|chats|thread|threads)|"
    r"past\s+(?:conversation|conversations|chat|chats|thread|threads)|"
    r"earlier\s+(?:conversation|conversations|chat|chats|thread|threads)|"
    r"last\s+time|"
    r"(?:what|do|did|can|could|don't|don[\u2019\u2018']t)\s+you\s+(?:remember|recall|know)\b|"
    r"remember\s+(?:when|what|that|how|me|our|we)\b|"
    r"recall\s+(?:when|what|that|how|me|our|we|from)\b|"
    r"(?:we(?:'|[\u2019\u2018'])ve|we\s+have)\s+met|met\s+before|"
    r"know\s+me|what\s+do\s+you\s+know\s+about\s+me|"
    r"(?:your|our|my)\s+memor(?:y|ies)|"
    r"(?:access|look|search|check|look\s+through|look\s+back).{0,40}\b(?:memory|memories|conversation|conversations|history|past)\b|"
    r"what\s+did\s+we\s+(?:discuss|talk\s+about|chat\s+about)\b|"
    r"what\s+were\s+we\s+(?:discussing|talking\s+about)\b|"
    r"(?:we|you|i)\s+(?:talked|discussed)\s+(?:about|through)\b"
    r")",
    re.IGNORECASE,
)

_TEMPORAL_CONVERSATION_RE = re.compile(
    r"\b("
    r"yesterday(?:'s|\s+)(?:conversation|chat|thread|discussion)\b|"
    r"that\s+(?:conversation|chat|thread|discussion)|"
    r"pick\s+up\s+where\s+we\s+left\s+off|"
    r"earlier\s+(?:today|this\s+week)|"
    r"the\s+other\s+day"
    r")\b",
    re.IGNORECASE,
)

_BARE_UUID_HINT_RE = re.compile(
    r"\b(thread|conversation|chat|yesterday|earlier|previous|prior)\b"
    r"|\bthat\s+(conversation|chat|thread|discussion)\b",
    re.IGNORECASE,
)

_CROSS_THREAD_PHRASE_RE = re.compile(
    r"\b(another|other|different|previous|prior|earlier)\s+(thread|chat|conversation)\b"
    r"|\b(in|from)\s+a\s+different\s+(thread|chat|conversation)\b"
    r"|\bcould(?:n['\u2019]?t| not)\s+.{0,48}\bthat\s+(thread|chat)\b",
    re.IGNORECASE,
)

_CONTINUITY_GREETING_RE = re.compile(
    r"\b("
    r"nice\s+to\s+talk\s+(?:with\s+you\s+)?again|"
    r"good\s+to\s+see\s+you\s+again|"
    r"hey\s+again|"
    r"great\s+to\s+talk\s+again|"
    r"so\s+good\s+to\s+(?:talk|chat|see\s+you)\s+again"
    r")\b",
    re.IGNORECASE,
)

# User references prior shared conversation history (not necessarily a direct question).
_PRIOR_CONVERSATION_REFERENCE_RE = re.compile(
    r"\b("
    r"(?:our|my|the|this|that)\s+(?:first|earliest|original|initial|very\s+first)\s+"
    r"(?:chat|conversation|thread|discussion|talk)s?\b|"
    r"(?:previous|prior|earlier|past|old|another|different|some|other|last|latest|recent)\s+"
    r"(?:chat|conversation|thread|discussion|talk)s?\b|"
    r"(?:the\s+)?last\s+(?:chat|conversation|thread|discussion)\s+we\b|"
    r"(?:in|from)\s+(?:a\s+)?(?:previous|prior|earlier|past|another|different|other)\s+"
    r"(?:chat|conversation|thread|discussion)\b|"
    r"(?:we(?:'|[\u2019\u2018'])ve|we\s+have|you\s+and\s+i\s+have)\s+"
    r"(?:talked|chatted|discussed|spoken|been\s+through|worked\s+together)\b|"
    r"\bwe\s+(?:talked|chatted|discussed|spoke)\b|"
    r"\bas\s+(?:i|we)\s+(?:mentioned|said|noted|talked\s+about)\s+(?:in|during|on|about)\b|"
    r"mentioned\s+in\s+another\s+(?:thread|chat|conversation)\b|"
    r"(?:been\s+with\s+me|how\s+far\s+we(?:'ve|\s+have)\s+come)\b|"
    r"(?:from|since)\s+(?:day\s+one|the\s+beginning|the\s+start)\b|"
    r"(?:when|back\s+when)\s+we\s+(?:first\s+)?(?:talked|chatted|discussed|met|started)\b"
    r")",
    re.IGNORECASE,
)

_RECALL_QUERY_FILLER_RE = re.compile(
    r"^(?:thank\s+you(?:\s+for\s+[^.!?]+)?[!.\s]+|"
    r"thanks(?:\s+for\s+[^.!?]+)?[!.\s]+|"
    r"(?:you(?:'|[\u2019\u2018'])re|you\s+are)\s+very\s+kind[!.\s]+)+",
    re.IGNORECASE,
)


def has_explicit_recall_intent(text: str | None) -> bool:
    """Conservative detector for user questions about continuity/past chats."""
    s = (text or "").strip()
    if not s:
        return False
    lowered = s.lower()
    if _TECHNICAL_MEMORY_RE.search(lowered) and not re.search(
        r"\b(?:remember|recall|previous|past|conversation|chat|thread|know\s+me|met\s+before)\b",
        lowered,
    ):
        return False
    return bool(_EXPLICIT_RECALL_INTENT_RE.search(lowered))


def has_prior_conversation_reference(text: str | None) -> bool:
    """True when the user mentions prior shared conversation history (not only explicit recall questions)."""
    s = (text or "").strip()
    if not s:
        return False
    lowered = s.lower()
    if _TECHNICAL_MEMORY_RE.search(lowered) and not re.search(
        r"\b(?:remember|recall|previous|prior|earlier|past|conversation|chat|thread|talked|discussed|mentioned)\b",
        lowered,
    ):
        return False
    return bool(_PRIOR_CONVERSATION_REFERENCE_RE.search(lowered))


def _has_topical_recall_anchor(text: str) -> bool:
    """True when the cue names a concrete topic FTS can match (not bare relational phrasing)."""
    lowered = (text or "").lower()
    if re.search(
        r"\b(?:project|vault|kanban|maker|michigan|hyperframes|research)\b",
        lowered,
    ):
        return True
    if re.search(
        r"\b(?:about|regarding)\s+(?:the\s+)?(?:project|vault|kanban|maker|michigan|hyperframes|research|\w+\s+(?:project|idea|space|plan|trip))\b",
        lowered,
    ):
        return True
    return False


def continuity_anchor_recall_query(text: str | None) -> bool:
    """True for relational past-chat cues that lack a topical FTS anchor (preflight fallback)."""
    s = (text or "").strip()
    if not s or not has_prior_conversation_reference(s):
        return False
    return not _has_topical_recall_anchor(s)


def should_browse_sessions_query(query: str | None) -> bool:
    """True when listing conversations by date beats keyword FTS (vague prior-chat cues)."""
    q = (query or "").strip()
    if not q:
        return False
    if continuity_anchor_recall_query(q):
        return True
    # e.g. "go back and find that conversation" — temporal pointer, no topical anchor
    return conversation_transcript_recall_cue(q) and not _has_topical_recall_anchor(q)


def conversation_transcript_recall_cue(text: str | None) -> bool:
    """True when the user cue is about past chat transcripts (route to search_sessions, not search_memory)."""
    s = (text or "").strip()
    if not s:
        return False
    if has_prior_conversation_reference(s):
        return True
    if MUGI_THREAD_PREFIX_RE.search(s):
        return True
    if _CROSS_THREAD_PHRASE_RE.search(s):
        return True
    if _TEMPORAL_CONVERSATION_RE.search(s):
        return True
    if extract_direct_thread_id(s):
        return True
    lowered = s.lower()
    if re.search(
        r"\b("
        r"what\s+did\s+we\s+(?:discuss|talk\s+about|chat\s+about)|"
        r"what\s+were\s+we\s+(?:discussing|talking\s+about)|"
        r"(?:previous|past|earlier)\s+(?:conversation|conversations|chat|chats|thread|threads)|"
        r"remember\s+(?:when|what\s+that|what\s+we|our\s+conversation|that\s+conversation)|"
        r"recall\s+(?:when|what\s+we|our\s+conversation|from\s+our)|"
        r"(?:we|you|i)\s+(?:talked|discussed)\s+(?:about|through)"
        r")\b",
        lowered,
    ):
        return True
    return False


def extract_recall_search_query(text: str | None) -> str:
    """Derive a focused session-search query from a conversational recall cue."""
    s = (text or "").strip()
    if not s:
        return ""

    def _is_recall_dense(candidate: str) -> bool:
        c = candidate.strip()
        if not c:
            return False
        return has_explicit_recall_intent(c) or has_prior_conversation_reference(c)

    for match in re.finditer(r"\(([^)]+)\)", s):
        candidate = (match.group(1) or "").strip()
        if _is_recall_dense(candidate):
            return candidate[:300]

    for line in s.splitlines():
        candidate = (line or "").strip()
        if _is_recall_dense(candidate):
            return candidate[:300]

    for part in re.split(r"[.!?]+", s):
        candidate = (part or "").strip()
        if _is_recall_dense(candidate):
            return candidate[:300]

    stripped = _RECALL_QUERY_FILLER_RE.sub("", s).strip()
    if stripped and _is_recall_dense(stripped):
        return stripped[:300]
    return s[:300]


def has_continuity_greeting_signal(text: str | None) -> bool:
    """Returning-user phrasing that should surface memory prefetch but not FTS preflight alone."""
    s = (text or "").strip()
    if not s:
        return False
    return bool(_CONTINUITY_GREETING_RE.search(s))


def extract_direct_thread_id(text: str | None) -> str | None:
    """Extract a thread UUID from mugi-thread prefix or bare UUID with hint words."""
    s = (text or "").strip()
    if not s:
        return None
    m = MUGI_THREAD_PREFIX_RE.search(s)
    if m:
        return str(m.group(1)).lower()
    if not _BARE_UUID_HINT_RE.search(s):
        return None
    um = THREAD_UUID_RE.search(s)
    if um:
        return str(um.group(1)).lower()
    return None


def has_cross_thread_recall_signal(text: str | None) -> bool:
    """True when the user references or asks about prior conversations, or gave a thread bridge."""
    s = (text or "").strip()
    if not s:
        return False
    if has_explicit_recall_intent(s):
        return True
    if has_prior_conversation_reference(s):
        return True
    if MUGI_THREAD_PREFIX_RE.search(s):
        return True
    if _CROSS_THREAD_PHRASE_RE.search(s):
        return True
    if _TEMPORAL_CONVERSATION_RE.search(s):
        return True
    if extract_direct_thread_id(s):
        return True
    return False


def _user_message_text(msg: dict[str, Any]) -> str:
    content = msg.get("content")
    if isinstance(content, str) and content.strip():
        return content.strip()
    if isinstance(content, list):
        for part in content:
            if isinstance(part, dict) and part.get("type") == "text":
                text = (part.get("text") or "").strip()
                if text:
                    return text
    return ""


def recent_real_user_texts(
    messages: list[dict[str, Any]] | None,
    *,
    max_user_msgs: int = 2,
) -> list[str]:
    """Return recent real user message texts, oldest first."""
    out: list[str] = []
    for msg in reversed(messages or []):
        if len(out) >= max_user_msgs:
            break
        if not is_real_user_message(msg):
            continue
        text = _user_message_text(msg)
        if text:
            out.append(text[:300])
    out.reverse()
    return out


def has_cross_thread_recall_in_messages(
    messages: list[dict[str, Any]] | None,
    *,
    max_user_msgs: int = 2,
) -> bool:
    return any(has_cross_thread_recall_signal(t) for t in recent_real_user_texts(messages, max_user_msgs=max_user_msgs))


def cross_thread_signal_source(
    messages: list[dict[str, Any]] | None,
    *,
    max_user_msgs: int = 2,
) -> str:
    """Return 'latest', 'history', or 'none' for trace logging."""
    texts = recent_real_user_texts(messages, max_user_msgs=max_user_msgs)
    if not texts:
        return "none"
    if has_cross_thread_recall_signal(texts[-1]):
        return "latest"
    if any(has_cross_thread_recall_signal(t) for t in texts[:-1]):
        return "history"
    return "none"


def has_continuity_greeting_in_messages(
    messages: list[dict[str, Any]] | None,
    *,
    max_user_msgs: int = 2,
) -> bool:
    return any(has_continuity_greeting_signal(t) for t in recent_real_user_texts(messages, max_user_msgs=max_user_msgs))
