# %% [markdown]
# # Notebook 2: User Message Deep Dive
# Extract and analyze all user messages: length, intent, specificity, first-message patterns.

# %%
import sys, os, re
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

import pandas as pd
import numpy as np
from utils.connection import init, query_df
from utils import sessions, messages
from IPython.display import display, Markdown

pd.set_option("display.max_columns", 30)
pd.set_option("display.max_rows", 100)
pd.set_option("display.max_colwidth", 120)

conn = init()
display(Markdown("**Connected.**"))

# %% [markdown]
# ## 1 — Load all user messages

# %%
user_msgs = query_df(conn, """
    SELECT m.id, m.session_id, m.content, m.timestamp, m.source,
           s.user_email, s.org, s.repo_name
    FROM   messages m
    JOIN   sessions s ON s.id = m.session_id
    WHERE  m.msg_type = 'user'
    ORDER  BY m.timestamp ASC
""")

display(Markdown(f"**Total user messages:** {len(user_msgs)}"))
display(user_msgs.groupby("user_email").size().to_frame("count"))

# %% [markdown]
# ## 2 — Strip `<INSTRUCTION>` tags (Codex CLI)

# %%
def strip_instruction_tags(text):
    """Remove <INSTRUCTION(S)>...</INSTRUCTION(S)> blocks — these are Codex system prompts, not user content."""
    if text is None:
        return ""
    text = re.sub(r"<INSTRUCTIONS?>.*?</INSTRUCTIONS?>", "", text, flags=re.DOTALL | re.IGNORECASE)
    return text.strip()

user_msgs["clean_content"] = user_msgs["content"].apply(strip_instruction_tags)
user_msgs["char_len"] = user_msgs["clean_content"].str.len()
user_msgs["word_count"] = user_msgs["clean_content"].str.split().str.len().fillna(0).astype(int)

# %% [markdown]
# ## 3 — Message length distribution

# %%
display(Markdown("### Message length (chars) — overall"))
display(user_msgs["char_len"].describe().round(0).to_frame())

display(Markdown("### Message length (words) — per user"))
display(user_msgs.groupby("user_email")["word_count"].describe().round(1))

display(Markdown("### Message length (chars) — per user"))
display(user_msgs.groupby("user_email")["char_len"].describe().round(0))

# %% [markdown]
# ## 4 — Intent classification (keyword-based)

# %%
INTENT_PATTERNS = {
    "fix/debug":   r"\b(fix|bug|error|issue|broken|fail|crash|debug|traceback|exception|wrong|not work)\b",
    "create/add":  r"\b(create|add|implement|build|new|generate|make|write)\b",
    "refactor":    r"\b(refactor|clean|reorganize|restructure|rename|move|extract|simplify)\b",
    "explain":     r"\b(explain|understand|what does|how does|why does|tell me|describe|walk me through)\b",
    "test":        r"\b(test|spec|assert|coverage|jest|pytest|unittest)\b",
    "deploy":      r"\b(deploy|release|publish|ship|push|merge|pr|pull request)\b",
    "review":      r"\b(review|check|look at|inspect|audit|verify)\b",
    "config":      r"\b(config|setup|install|env|environment|dependency|package)\b",
}

for intent, pattern in INTENT_PATTERNS.items():
    user_msgs[f"intent_{intent}"] = user_msgs["clean_content"].str.contains(
        pattern, case=False, na=False
    )

intent_cols = [c for c in user_msgs.columns if c.startswith("intent_")]

display(Markdown("### Intent distribution — overall"))
intent_totals = user_msgs[intent_cols].sum().sort_values(ascending=False)
intent_totals.index = [c.replace("intent_", "") for c in intent_totals.index]
display(intent_totals.to_frame("count"))

display(Markdown("### Intent distribution — per user"))
for email in user_msgs["user_email"].unique():
    mask = user_msgs["user_email"] == email
    display(Markdown(f"#### {email}"))
    user_intent = user_msgs.loc[mask, intent_cols].sum().sort_values(ascending=False)
    user_intent.index = [c.replace("intent_", "") for c in user_intent.index]
    display(user_intent.to_frame("count"))

# %% [markdown]
# ## 5 — Specificity scoring

# %%
def specificity_score(text):
    """Score how specific a prompt is (0-5 scale)."""
    if not text:
        return 0
    score = 0
    # File paths
    if re.search(r"[\w/]+\.\w{1,5}\b", text):
        score += 1
    # Function/class names (camelCase, snake_case, PascalCase)
    if re.search(r"\b[a-z]+[A-Z]\w+\b|\b[a-z]+_[a-z]+\b|\b[A-Z][a-z]+[A-Z]\w+\b", text):
        score += 1
    # Error messages / stack traces
    if re.search(r"(Error|Exception|Traceback|at line|TypeError|ValueError|stack trace)", text, re.IGNORECASE):
        score += 1
    # Line number references
    if re.search(r"\bline\s+\d+\b|\bL\d+\b", text, re.IGNORECASE):
        score += 1
    # Code blocks
    if "```" in text or re.search(r"^\s{4,}\S", text, re.MULTILINE):
        score += 1
    return score

user_msgs["specificity"] = user_msgs["clean_content"].apply(specificity_score)

display(Markdown("### Specificity score distribution"))
display(user_msgs["specificity"].value_counts().sort_index().to_frame("count"))

display(Markdown("### Avg specificity per user"))
display(user_msgs.groupby("user_email")["specificity"].mean().round(2).to_frame("avg_specificity"))

# %% [markdown]
# ## 6 — Vague prompt detection

# %%
def is_vague(text, word_count):
    """Heuristic: vague if very short or generic with no file/code references."""
    if word_count < 10 and not re.search(r"[\w/]+\.\w{1,5}", text or ""):
        return True
    if re.match(r"^(fix|do|make|run|update|change)\s+(this|it|that)\s*\.?$", text or "", re.IGNORECASE):
        return True
    return False

user_msgs["is_vague"] = user_msgs.apply(
    lambda r: is_vague(r["clean_content"], r["word_count"]), axis=1
)

display(Markdown("### Vague prompts"))
vague_pct = user_msgs.groupby("user_email")["is_vague"].mean().round(3) * 100
display(vague_pct.to_frame("vague_pct"))

display(Markdown("### Sample vague prompts"))
display(user_msgs[user_msgs["is_vague"]][["user_email", "clean_content", "word_count"]].head(20))

# %% [markdown]
# ## 7 — First message per session analysis

# %%
first_msgs = user_msgs.sort_values("timestamp").groupby("session_id").first().reset_index()

display(Markdown(f"### First messages: {len(first_msgs)} sessions"))

display(Markdown("### First message length — per user"))
display(first_msgs.groupby("user_email")["word_count"].describe().round(1))

display(Markdown("### First message specificity — per user"))
display(first_msgs.groupby("user_email")["specificity"].mean().round(2).to_frame("avg_specificity"))

display(Markdown("### Sample first messages (longest)"))
display(first_msgs.nlargest(10, "word_count")[["user_email", "word_count", "specificity", "clean_content"]])

display(Markdown("### Sample first messages (shortest)"))
display(first_msgs.nsmallest(10, "word_count")[["user_email", "word_count", "specificity", "clean_content"]])

# %% [markdown]
# ## 8 — Code inclusion in messages

# %%
user_msgs["has_code_block"] = user_msgs["clean_content"].str.contains(r"```", na=False)
user_msgs["has_error_trace"] = user_msgs["clean_content"].str.contains(
    r"(Traceback|Error:|Exception:|at line|\bat\s+\S+:\d+)", case=False, na=False
)

display(Markdown("### Code/error inclusion rates per user"))
for email in user_msgs["user_email"].unique():
    mask = user_msgs["user_email"] == email
    n = mask.sum()
    display(Markdown(f"**{email}** ({n} msgs): "
        f"code blocks={user_msgs.loc[mask, 'has_code_block'].sum()} "
        f"({user_msgs.loc[mask, 'has_code_block'].mean()*100:.1f}%), "
        f"error traces={user_msgs.loc[mask, 'has_error_trace'].sum()} "
        f"({user_msgs.loc[mask, 'has_error_trace'].mean()*100:.1f}%)"))

# %% [markdown]
# ## 9 — User comparison summary

# %%
display(Markdown("### Head-to-head: user message style comparison"))
comparison = user_msgs.groupby("user_email").agg(
    total_messages=("id", "count"),
    avg_word_count=("word_count", "mean"),
    median_word_count=("word_count", "median"),
    avg_specificity=("specificity", "mean"),
    vague_pct=("is_vague", "mean"),
    has_code_pct=("has_code_block", "mean"),
    has_error_pct=("has_error_trace", "mean"),
).round(3)
comparison["vague_pct"] = (comparison["vague_pct"] * 100).round(1)
comparison["has_code_pct"] = (comparison["has_code_pct"] * 100).round(1)
comparison["has_error_pct"] = (comparison["has_error_pct"] * 100).round(1)
display(comparison)

# %% [markdown]
# ---
# *End of Notebook 2.*
