"""
Prompting Module - LLM prompt building and response parsing.

Contains:
- build_consolidation_prompt: Build prompt for Claude
- parse_consolidation_response: Parse XML response
"""

import re
from typing import Any, Dict, List

from .models import Cluster


def build_consolidation_prompt(clusters: List[Cluster]) -> str:
    """
    Build the prompt for Claude to consolidate clusters.

    Includes file pattern analysis to detect plan->implementation patterns
    and guide merge decisions for same-feature lifecycle clusters.
    """
    formatted_clusters = []

    for cluster in clusters:
        # Analyze files across all memories in cluster
        all_files = []
        plan_files = []
        code_files = []

        memories_text = ""
        for i, mem in enumerate(cluster.memories, 1):
            files = mem.get('files', []) or []
            all_files.extend(files)

            for f in files:
                if f.endswith('.md') or 'plan' in f.lower() or 'future' in f.lower() or 'docs/' in f.lower():
                    plan_files.append(f)
                elif f.endswith(('.py', '.js', '.ts', '.html', '.css', '.jsx', '.tsx')):
                    code_files.append(f)

            memories_text += f"""  <memory id="{mem['id']}">
    <type>{mem.get('type', 'unknown')}</type>
    <title>{mem.get('title', 'N/A')}</title>
    <fact>{mem.get('fact', 'N/A')}</fact>
    <files>{', '.join(files) or 'None'}</files>
  </memory>
"""

        # Determine file pattern
        has_plan_files = len(plan_files) > 0
        has_code_files = len(code_files) > 0
        unique_files = len(set(all_files))

        if has_plan_files and has_code_files:
            file_pattern = "PLAN+IMPL (likely same feature lifecycle)"
        elif unique_files == 0:
            file_pattern = "NO FILES"
        elif len(set(all_files)) == len(all_files):
            file_pattern = f"ALL DIFFERENT ({unique_files} unique files)"
        else:
            file_pattern = f"MIXED ({unique_files} unique files)"

        formatted_clusters.append(f"""<cluster id="{cluster.id}" size="{cluster.size}" file_pattern="{file_pattern}">
{memories_text}</cluster>""")

    clusters_text = "\n\n".join(formatted_clusters)

    prompt = f"""IMPORTANT: Output ONLY valid XML. No explanations, no markdown, no insight blocks.

You are consolidating clusters of similar memories in a coding knowledge base.
Each cluster contains 2+ memories that are semantically related.

========================================
FILE PATTERN ANALYSIS (check FIRST)
========================================

Each cluster has a "file_pattern" attribute:

**PLAN+IMPL** = Cluster contains both plan/design docs (.md) AND code files (.py, .html, etc.)
  -> Strong signal for **merge** (same feature at different lifecycle stages)
  -> Combine planning context with implementation details
  -> This is NOT a false positive - it's the same feature evolving

**ALL DIFFERENT** = Each memory references completely different files
  -> Could be **keep_all** (truly different topics)
  -> OR could be **merge** (same feature touching multiple files)
  -> Check if memories describe the SAME concept/feature

**MIXED** = Some file overlap
  -> Likely **merge** (complementary details about same code)
  -> Or **keep_one** if one is clearly superior

========================================
LIFECYCLE PROGRESSION
========================================

When a cluster contains:
- Memory about "planning/designing/proposing" something (often .md files)
- Memory about "implementing/building/adding" the same thing (code files)
- Memory about related changes (CSS, tests, etc.)

These are the SAME FEATURE at different stages -> **merge** them into one comprehensive memory.

Example cluster:
  1. "Planning debug mode toggle" (future-plans/cli-session-management.md)
  2. "Implemented debug mode UI" (templates/index.html)
  3. "Added debug mode styles" (static/style.css)

-> merge: "Implemented debug mode toggle (per cli-session-management.md plan) with UI
         checkbox in index.html and styling in style.css"

========================================
DECISION CRITERIA
========================================

**merge** - Choose when (PREFERRED for same-feature clusters):
- File pattern is PLAN+IMPL (same feature lifecycle)
- Memories describe the SAME feature/concept from different angles
- Each memory has unique valuable details
- Combining creates ONE comprehensive memory
- Memories are about the same code area with complementary info

**keep_all** - Choose when:
- Memories are about genuinely DIFFERENT topics (false positive cluster)
- They mention similar keywords but describe unrelated things
- Example: "database caching" vs "API response caching" - different systems
- Keeping separate is the only way to preserve distinct information

**keep_one** - Choose when (USE SPARINGLY):
- ONE memory is clearly superior AND covers everything others say
- Others are truly redundant subsets with no unique information
- Be careful: Don't choose just because one is longer or more verbose

========================================
COMMON MISTAKES TO AVOID
========================================

DON'T choose keep_one just because:
- One memory is longer than others
- One memory has more file paths listed
- One memory seems "more detailed" but others have different details

DON'T choose keep_all when:
- Memories describe the same feature from planning to implementation
- Files are different but the TOPIC is the same

DO prefer merge when:
- Cluster has plan .md + implementation code files
- Memories describe same feature at different stages
- Each memory adds something the others don't have

========================================
MERGED TYPE SELECTION
========================================

When merging memories with DIFFERENT types, choose the type that best
represents the FINAL OUTCOME of the combined knowledge.

**Priority order** (use the highest applicable):

1. **bugfix** - If the outcome was fixing a bug or resolving an issue
2. **feature** - If the outcome was implementing new functionality
3. **refactor** - If the outcome was restructuring/improving existing code
4. **optimization** - If the outcome was performance improvement
5. **decision** - If the outcome was an architectural or design choice
6. **discovery** - If the outcome was learning how something works
7. **gotcha** - If the outcome was identifying a pitfall to avoid

**Rules**:
- Plan (decision) + Implementation (feature) -> use `feature`
- Discovery + Fix -> use `bugfix` (the fix is the outcome)
- Multiple discoveries about same topic -> use `discovery`
- Design doc + code changes -> use the code change type (feature/bugfix/refactor)

**Example**:
  Cluster types: [decision, feature, bugfix]
  - decision: "Decided to add debug mode toggle"
  - feature: "Implemented debug mode UI"
  - bugfix: "Fixed toggle not persisting state"
  -> merged_type: `bugfix` (highest priority, represents final state)

**Example**:
  Cluster types: [decision, feature]
  - decision: "Planning session injection system"
  - feature: "Implemented session hooks"
  -> merged_type: `feature` (implementation is the outcome)

========================================
EXAMPLES
========================================

**Example 1: Plan+Implementation = merge**
Cluster (file_pattern="PLAN+IMPL"):
  Memory 1 [decision]: "Designing session summary injection" (summary-injection-design.md)
  Memory 2 [feature]: "Implemented session hooks" (hooks/session_start.py)
  Memory 3 [feature]: "Added /summaries UI page" (templates/summaries.html)
-> merge:
    merged_type: feature (implementation outcome > planning decision)
    merged_title: "Implemented session summary injection system"
    merged_fact: "Implemented session summary injection (per summary-injection-design.md)
                 with session_start.py hook and /summaries UI page"

**Example 2: True false positive = keep_all**
Cluster (file_pattern="ALL DIFFERENT"):
  Memory 1 [feature]: "Database query caching with Redis" (db/cache.py)
  Memory 2 [feature]: "Browser localStorage caching for UI state" (static/app.js)
-> keep_all (genuinely different caching systems, not the same feature)

**Example 3: Complementary details = merge**
Cluster (file_pattern="MIXED"):
  Memory 1 [discovery]: "Use CODECONTEXT_MODEL env var"
  Memory 2 [discovery]: "Set env var before server start"
  Memory 3 [discovery]: "PowerShell syntax: $env:CODECONTEXT_MODEL = 'value'"
-> merge:
    merged_type: discovery (all same type)
    merged_title: "CODECONTEXT_MODEL environment variable configuration"
    merged_fact: "Set CODECONTEXT_MODEL environment variable before starting server.py
                 to override the default embedding model. PowerShell: $env:CODECONTEXT_MODEL = 'value'"

**Example 4: Clear superior = keep_one**
Cluster:
  Memory 1 [bugfix]: "Fixed the API bug"
  Memory 2 [bugfix]: "Fixed null pointer in getUserById() when user ID is undefined, added validation"
  Memory 3 [bugfix]: "API fix deployed"
-> keep_one: Memory 2 (specific details, others are vague)

**Example 5: Mixed types with bugfix = merge**
Cluster (file_pattern="PLAN+IMPL"):
  Memory 1 [decision]: "Decided to add debug mode toggle"
  Memory 2 [feature]: "Implemented debug mode UI checkbox"
  Memory 3 [bugfix]: "Fixed toggle state not persisting after refresh"
-> merge:
    merged_type: bugfix (highest priority: bugfix > feature > decision)
    merged_title: "Debug mode toggle with persistence fix"
    merged_fact: "Implemented debug mode toggle UI checkbox (per design decision) and fixed
                 state persistence issue where toggle would reset after page refresh"

========================================
OUTPUT FORMAT (strict XML only)
========================================

<consolidations>
  <consolidation cluster_id="cluster_1">
    <decision>merge</decision>
    <merged_type>feature</merged_type>
    <merged_title>Implemented session summary injection system</merged_title>
    <merged_fact>Implemented session summary injection (per summary-injection-design.md) with session hooks and /summaries UI</merged_fact>
    <reasoning>PLAN+IMPL: decision + feature types, use feature as implementation outcome</reasoning>
  </consolidation>
  <consolidation cluster_id="cluster_2">
    <decision>merge</decision>
    <merged_type>bugfix</merged_type>
    <merged_title>Debug mode toggle with persistence fix</merged_title>
    <merged_fact>Implemented debug mode toggle and fixed state persistence issue</merged_fact>
    <reasoning>Mixed types [decision, feature, bugfix]: bugfix is highest priority</reasoning>
  </consolidation>
  <consolidation cluster_id="cluster_3">
    <decision>keep_all</decision>
    <reasoning>False positive: database caching vs browser caching are different systems</reasoning>
  </consolidation>
  <consolidation cluster_id="cluster_4">
    <decision>keep_one</decision>
    <keep_id>mem_abc123</keep_id>
    <reasoning>Memory abc123 has specific details, others are vague summaries</reasoning>
  </consolidation>
</consolidations>

========================================
RULES
========================================
1. decision MUST be: merge | keep_all | keep_one
2. For "merge", ALL THREE are REQUIRED:
   - merged_type (use type priority: bugfix > feature > refactor > optimization > decision > discovery > gotcha)
   - merged_title (clear, concise title)
   - merged_fact (combined fact with all important details)
3. keep_id REQUIRED for "keep_one" (exact ID from input)
4. reasoning: 1 brief sentence explaining decision AND type choice for merges
5. cluster_id must match EXACTLY
6. Consolidate EVERY cluster
7. Output ONLY the XML block
8. When file_pattern is PLAN+IMPL, strongly prefer merge
9. When uncertain between merge and keep_one, prefer merge
10. When selecting merged_type, use the highest priority type present in the cluster

========================================
CLUSTERS TO CONSOLIDATE
========================================

{clusters_text}"""

    return prompt


def parse_consolidation_response(response: str) -> List[Dict[str, Any]]:
    """Parse the XML response from Claude into consolidation decisions."""

    consolidations = []

    # Strip markdown code blocks if present
    cleaned = response
    code_block_match = re.search(r'```(?:xml)?\s*(.*?)```', response, re.DOTALL)
    if code_block_match:
        cleaned = code_block_match.group(1)

    # Extract the <consolidations>...</consolidations> block
    match = re.search(r'<consolidations>(.*?)</consolidations>', cleaned, re.DOTALL)
    if not match:
        # Try to find individual consolidation tags
        consolidation_matches = re.findall(r'<consolidation[^>]*>(.*?)</consolidation>', cleaned, re.DOTALL)
        if not consolidation_matches:
            raise ValueError("No valid consolidation XML found in response")
        # Also try to get cluster_id from the tag attributes
        full_matches = re.findall(r'<consolidation\s+cluster_id="([^"]+)">(.*?)</consolidation>', cleaned, re.DOTALL)
        if full_matches:
            for cluster_id, content in full_matches:
                consolidation = {"cluster_id": cluster_id}
                _parse_consolidation_content(content, consolidation)
                if consolidation.get("decision"):
                    consolidations.append(consolidation)
            return consolidations
    else:
        # Extract from within consolidations block
        inner = match.group(1)
        full_matches = re.findall(r'<consolidation\s+cluster_id="([^"]+)">(.*?)</consolidation>', inner, re.DOTALL)
        for cluster_id, content in full_matches:
            consolidation = {"cluster_id": cluster_id}
            _parse_consolidation_content(content, consolidation)
            if consolidation.get("decision"):
                consolidations.append(consolidation)

    return consolidations


def _parse_consolidation_content(content: str, consolidation: Dict) -> None:
    """Parse the content within a consolidation tag."""

    # Extract decision
    decision_match = re.search(r'<decision>(.*?)</decision>', content, re.DOTALL)
    if decision_match:
        consolidation['decision'] = decision_match.group(1).strip()

    # Extract keep_id (for keep_one)
    keep_id_match = re.search(r'<keep_id>(.*?)</keep_id>', content, re.DOTALL)
    if keep_id_match:
        consolidation['keep_id'] = keep_id_match.group(1).strip()

    # Extract merged fields (for merge)
    merged_type_match = re.search(r'<merged_type>(.*?)</merged_type>', content, re.DOTALL)
    if merged_type_match:
        consolidation['merged_type'] = merged_type_match.group(1).strip()

    merged_title_match = re.search(r'<merged_title>(.*?)</merged_title>', content, re.DOTALL)
    if merged_title_match:
        consolidation['merged_title'] = merged_title_match.group(1).strip()

    merged_fact_match = re.search(r'<merged_fact>(.*?)</merged_fact>', content, re.DOTALL)
    if merged_fact_match:
        consolidation['merged_fact'] = merged_fact_match.group(1).strip()

    # Extract reasoning
    reasoning_match = re.search(r'<reasoning>(.*?)</reasoning>', content, re.DOTALL)
    if reasoning_match:
        consolidation['reasoning'] = reasoning_match.group(1).strip()

    # Validate decision value
    if consolidation.get('decision') not in ('keep_one', 'merge', 'keep_all'):
        consolidation.pop('decision', None)  # Invalid, will be filtered out
