"""
IRAC Synthesis Engine
=====================
The killer feature: Multi-case rule synthesis with automatic APPLICATION generation.

Takes multiple cases + your facts and generates:
1. Synthesized RULE section (not just case summaries - actual synthesis)
2. Case comparison for APPLICATION
3. Draft APPLICATION paragraphs with distinction/analogy language
4. Authority ranking by relevance

This does what no existing tool does: cross-case synthesis at brief-writing level.
"""

import re
from typing import Dict, List, Tuple, Optional, Set
from dataclasses import dataclass, field
from enum import Enum
from collections import defaultdict

# Import our other modules
try:
    from enhanced_rules import extract_enhanced_rules, EnhancedRule, RuleType, RuleLevel
    from enhanced_citations import extract_enhanced_citations, EnhancedCitation
    from text_extraction_core import extract_citations_from_text
    DEPENDENCIES_AVAILABLE = True
except ImportError:
    DEPENDENCIES_AVAILABLE = False


class CourtLevel(Enum):
    """Court hierarchy for authority ranking."""
    SUPREME_COURT = 5
    CIRCUIT_COURT = 4
    DISTRICT_COURT = 3
    STATE_SUPREME = 3
    STATE_APPELLATE = 2
    STATE_TRIAL = 1
    UNKNOWN = 0


class JurisdictionMatch(Enum):
    """How well jurisdiction matches target."""
    BINDING = 3      # Same jurisdiction, higher/same court
    PERSUASIVE_STRONG = 2  # Different circuit but federal
    PERSUASIVE_WEAK = 1    # State court or very different
    INAPPLICABLE = 0


@dataclass
class RankedCase:
    """A case with authority ranking."""
    name: str
    citation: str
    court_level: CourtLevel
    jurisdiction_match: JurisdictionMatch
    year: Optional[int]
    fact_similarity_score: float  # 0-1
    rule_relevance_score: float   # 0-1
    overall_score: float = 0.0

    # Extracted content
    rules: List[EnhancedRule] = field(default_factory=list)
    key_facts: List[str] = field(default_factory=list)
    holding: str = ""

    def calculate_overall_score(self):
        """Calculate weighted overall authority score."""
        # Weights
        court_weight = 0.25
        jurisdiction_weight = 0.20
        recency_weight = 0.15
        fact_weight = 0.25
        rule_weight = 0.15

        # Normalize court level (0-1)
        court_score = self.court_level.value / 5.0

        # Jurisdiction score
        jx_score = self.jurisdiction_match.value / 3.0

        # Recency score (newer = better, assume 2024 baseline)
        recency_score = 0.5
        if self.year:
            years_old = 2024 - self.year
            if years_old <= 5:
                recency_score = 1.0
            elif years_old <= 10:
                recency_score = 0.8
            elif years_old <= 20:
                recency_score = 0.6
            elif years_old <= 50:
                recency_score = 0.4
            else:
                recency_score = 0.2

        self.overall_score = (
            court_weight * court_score +
            jurisdiction_weight * jx_score +
            recency_weight * recency_score +
            fact_weight * self.fact_similarity_score +
            rule_weight * self.rule_relevance_score
        )


@dataclass
class SynthesizedRule:
    """A rule synthesized from multiple cases."""
    statement: str  # The synthesized rule statement
    source_cases: List[str]  # Cases this rule comes from
    citations: List[str]  # Full citations
    rule_type: RuleType
    is_primary: bool  # Primary rule vs. sub-rule
    elements: List[str] = field(default_factory=list)
    exceptions: List[str] = field(default_factory=list)
    policy_rationale: str = ""


@dataclass
class CaseAnalogy:
    """Comparison between your facts and a precedent case."""
    case_name: str
    case_citation: str
    similarity_type: str  # "strong_analogy", "distinguishable", "mixed"

    # Points of comparison
    similar_facts: List[Tuple[str, str]]  # (your fact, case fact)
    distinguishing_facts: List[Tuple[str, str]]

    # Generated language
    analogy_language: List[str]
    distinction_language: List[str]

    # Which rule elements each fact goes to
    fact_to_element_mapping: Dict[str, str] = field(default_factory=dict)


@dataclass
class IRACDraft:
    """Complete IRAC draft with all components."""
    issue_statement: str
    rule_section: str
    application_section: str
    conclusion: str

    # Supporting data
    synthesized_rules: List[SynthesizedRule]
    case_analogies: List[CaseAnalogy]
    ranked_authorities: List[RankedCase]

    # Metadata
    confidence_score: float  # How confident we are in this analysis
    warnings: List[str] = field(default_factory=list)


# ============================================================================
# COURT LEVEL DETECTION
# ============================================================================

def detect_court_level(citation: str, case_text: str = "") -> CourtLevel:
    """Detect court level from citation or case text."""
    citation_lower = citation.lower()
    text_lower = case_text.lower() if case_text else ""

    # Supreme Court
    if 'u.s.' in citation_lower or 's.ct.' in citation_lower or 's. ct.' in citation_lower:
        return CourtLevel.SUPREME_COURT

    # Circuit Courts
    if 'f.2d' in citation_lower or 'f.3d' in citation_lower or 'f.4th' in citation_lower:
        return CourtLevel.CIRCUIT_COURT
    if 'cir.' in citation_lower or 'circuit' in text_lower:
        return CourtLevel.CIRCUIT_COURT

    # District Courts
    if 'f.supp' in citation_lower or 'f. supp' in citation_lower:
        return CourtLevel.DISTRICT_COURT

    # State Supreme Courts
    state_supreme_patterns = ['cal.', 'n.y.', 'tex.', 'ill.', 'pa.', 'ohio', 'mass.', 'n.j.']
    for pattern in state_supreme_patterns:
        if pattern in citation_lower and 'app' not in citation_lower:
            return CourtLevel.STATE_SUPREME

    # State Appellate
    if 'app' in citation_lower:
        return CourtLevel.STATE_APPELLATE

    return CourtLevel.UNKNOWN


def detect_jurisdiction(citation: str) -> str:
    """Extract jurisdiction from citation."""
    # Federal circuits
    circuit_match = re.search(r'(\d+)(?:st|nd|rd|th)\s*Cir', citation, re.IGNORECASE)
    if circuit_match:
        return f"{circuit_match.group(1)}th Circuit"

    if 'U.S.' in citation or 'S.Ct.' in citation or 'S. Ct.' in citation:
        return "Supreme Court"

    # State jurisdictions
    states = {
        'Cal.': 'California', 'N.Y.': 'New York', 'Tex.': 'Texas',
        'Ill.': 'Illinois', 'Pa.': 'Pennsylvania', 'Mass.': 'Massachusetts',
        'Ohio': 'Ohio', 'Mich.': 'Michigan', 'Fla.': 'Florida',
    }
    for abbrev, state in states.items():
        if abbrev in citation:
            return state

    return "Unknown"


def calculate_jurisdiction_match(case_jurisdiction: str, target_jurisdiction: str) -> JurisdictionMatch:
    """Calculate how well a case's jurisdiction matches the target."""
    if case_jurisdiction == "Supreme Court":
        return JurisdictionMatch.BINDING  # Supreme Court binds everyone

    if case_jurisdiction == target_jurisdiction:
        return JurisdictionMatch.BINDING

    # Federal circuits are persuasive to each other
    if "Circuit" in case_jurisdiction and "Circuit" in target_jurisdiction:
        return JurisdictionMatch.PERSUASIVE_STRONG

    # Federal to state or vice versa
    return JurisdictionMatch.PERSUASIVE_WEAK


# ============================================================================
# FACT SIMILARITY ANALYSIS
# ============================================================================

def calculate_fact_similarity(your_facts: str, case_facts: List[str]) -> Tuple[float, List[Tuple[str, str]]]:
    """
    Calculate similarity between your facts and case facts.

    Returns:
        Tuple of (similarity_score, list of (your_fact, similar_case_fact) pairs)
    """
    your_facts_lower = your_facts.lower()
    your_sentences = re.split(r'[.!?]+', your_facts)
    your_sentences = [s.strip() for s in your_sentences if len(s.strip()) > 10]

    similar_pairs = []
    total_similarity = 0.0

    for case_fact in case_facts:
        case_fact_lower = case_fact.lower()

        # Extract key legal terms from case fact
        legal_terms = extract_legal_terms(case_fact_lower)

        # Check how many appear in your facts
        matches = sum(1 for term in legal_terms if term in your_facts_lower)

        if legal_terms:
            similarity = matches / len(legal_terms)
            if similarity > 0.3:  # Threshold for "similar"
                # Find the most similar sentence from your facts
                best_match = ""
                best_score = 0
                for your_sent in your_sentences:
                    score = word_overlap_score(your_sent.lower(), case_fact_lower)
                    if score > best_score:
                        best_score = score
                        best_match = your_sent

                if best_match:
                    similar_pairs.append((best_match, case_fact))
                    total_similarity += similarity

    # Normalize
    if case_facts:
        avg_similarity = total_similarity / len(case_facts)
    else:
        avg_similarity = 0.0

    return min(1.0, avg_similarity), similar_pairs


def extract_legal_terms(text: str) -> Set[str]:
    """Extract legally significant terms from text."""
    legal_terms = set()

    # Common legal concepts
    patterns = [
        r'\b(?:duty|breach|causation|damages|negligence|intent|knowledge)\b',
        r'\b(?:contract|agreement|offer|acceptance|consideration)\b',
        r'\b(?:plaintiff|defendant|party|parties)\b',
        r'\b(?:reasonable|unreasonable|foreseeable|proximate)\b',
        r'\b(?:scienter|fraud|misrepresentation|material)\b',
        r'\b(?:motion|summary judgment|dismiss|discovery)\b',
    ]

    for pattern in patterns:
        matches = re.findall(pattern, text, re.IGNORECASE)
        legal_terms.update(m.lower() for m in matches)

    return legal_terms


def word_overlap_score(text1: str, text2: str) -> float:
    """Calculate word overlap between two texts."""
    words1 = set(text1.split())
    words2 = set(text2.split())

    # Remove common words
    stopwords = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
                 'of', 'with', 'by', 'from', 'as', 'is', 'was', 'are', 'were', 'been',
                 'be', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
                 'could', 'should', 'may', 'might', 'must', 'that', 'this', 'these',
                 'those', 'it', 'its'}

    words1 = words1 - stopwords
    words2 = words2 - stopwords

    if not words1 or not words2:
        return 0.0

    intersection = words1 & words2
    union = words1 | words2

    return len(intersection) / len(union)


# ============================================================================
# RULE SYNTHESIS
# ============================================================================

def synthesize_rules_from_cases(
    case_texts: Dict[str, Tuple[str, str]],  # case_name -> (text, citation)
    issue: str
) -> List[SynthesizedRule]:
    """
    Synthesize rules from multiple cases into coherent rule statements.

    This is the core differentiator - actual synthesis, not just extraction.
    """
    all_rules: List[Tuple[EnhancedRule, str, str]] = []  # (rule, case_name, citation)

    # Extract rules from each case
    for case_name, (text, citation) in case_texts.items():
        if DEPENDENCIES_AVAILABLE:
            rules = extract_enhanced_rules(text, case_name, citation)
            for rule in rules:
                all_rules.append((rule, case_name, citation))

    if not all_rules:
        return []

    # Group rules by similarity
    rule_groups = group_similar_rules(all_rules)

    # Synthesize each group into a single statement
    synthesized = []
    for group in rule_groups:
        if len(group) >= 1:
            synth_rule = create_synthesized_rule(group, issue)
            if synth_rule:
                synthesized.append(synth_rule)

    # Sort by importance (primary rules first, then by number of supporting cases)
    synthesized.sort(key=lambda r: (not r.is_primary, -len(r.source_cases)))

    return synthesized


def group_similar_rules(
    rules: List[Tuple[EnhancedRule, str, str]]
) -> List[List[Tuple[EnhancedRule, str, str]]]:
    """Group rules that express similar legal principles."""
    groups = []
    used = set()

    for i, (rule1, name1, cite1) in enumerate(rules):
        if i in used:
            continue

        group = [(rule1, name1, cite1)]
        used.add(i)

        for j, (rule2, name2, cite2) in enumerate(rules[i+1:], i+1):
            if j in used:
                continue

            # Check if rules are similar
            if are_rules_similar(rule1, rule2):
                group.append((rule2, name2, cite2))
                used.add(j)

        groups.append(group)

    return groups


def are_rules_similar(rule1: EnhancedRule, rule2: EnhancedRule) -> bool:
    """Determine if two rules express the same legal principle."""
    # Same rule type
    if rule1.rule_type != rule2.rule_type:
        return False

    # Check for key term overlap
    terms1 = set(rule1.key_terms)
    terms2 = set(rule2.key_terms)

    if terms1 and terms2:
        overlap = len(terms1 & terms2) / max(len(terms1), len(terms2))
        if overlap < 0.3:
            return False

    # Check text similarity
    text_sim = word_overlap_score(rule1.text.lower(), rule2.text.lower())

    return text_sim > 0.4


def create_synthesized_rule(
    rule_group: List[Tuple[EnhancedRule, str, str]],
    issue: str
) -> Optional[SynthesizedRule]:
    """Create a synthesized rule from a group of similar rules."""
    if not rule_group:
        return None

    # Use the most detailed rule as the base
    rules_sorted = sorted(rule_group, key=lambda x: len(x[0].text), reverse=True)
    primary_rule, primary_name, primary_cite = rules_sorted[0]

    # Collect all source cases
    source_cases = [name for _, name, _ in rule_group]
    citations = [cite for _, _, cite in rule_group]

    # Collect all elements and exceptions
    all_elements = []
    all_exceptions = []
    for rule, _, _ in rule_group:
        all_elements.extend(rule.elements)
        all_exceptions.extend(rule.exceptions)

    # Deduplicate
    elements = list(dict.fromkeys(all_elements))
    exceptions = list(dict.fromkeys(all_exceptions))

    # Determine if primary rule
    is_primary = primary_rule.level == RuleLevel.PRIMARY

    # Get best policy rationale
    policy = ""
    for rule, _, _ in rule_group:
        if rule.policy_rationale:
            policy = rule.policy_rationale
            break

    return SynthesizedRule(
        statement=primary_rule.text,
        source_cases=source_cases,
        citations=citations,
        rule_type=primary_rule.rule_type,
        is_primary=is_primary,
        elements=elements,
        exceptions=exceptions,
        policy_rationale=policy
    )


# ============================================================================
# APPLICATION GENERATION
# ============================================================================

def generate_case_analogies(
    your_facts: str,
    ranked_cases: List[RankedCase],
    synthesized_rules: List[SynthesizedRule]
) -> List[CaseAnalogy]:
    """
    Generate analogies and distinctions between your facts and precedent cases.
    """
    analogies = []

    for case in ranked_cases[:5]:  # Top 5 most relevant cases
        # Calculate fact similarity
        similarity_score, similar_pairs = calculate_fact_similarity(your_facts, case.key_facts)

        # Find distinguishing facts
        distinguishing = find_distinguishing_facts(your_facts, case.key_facts)

        # Determine overall similarity type
        if similarity_score > 0.6:
            sim_type = "strong_analogy"
        elif similarity_score > 0.3:
            sim_type = "mixed"
        else:
            sim_type = "distinguishable"

        # Generate language
        analogy_lang = generate_analogy_language(case.name, similar_pairs)
        distinction_lang = generate_distinction_language(case.name, distinguishing)

        analogy = CaseAnalogy(
            case_name=case.name,
            case_citation=case.citation,
            similarity_type=sim_type,
            similar_facts=similar_pairs,
            distinguishing_facts=distinguishing,
            analogy_language=analogy_lang,
            distinction_language=distinction_lang
        )
        analogies.append(analogy)

    return analogies


def find_distinguishing_facts(your_facts: str, case_facts: List[str]) -> List[Tuple[str, str]]:
    """Find facts that distinguish your case from the precedent."""
    distinguishing = []

    your_sentences = re.split(r'[.!?]+', your_facts)
    your_sentences = [s.strip() for s in your_sentences if len(s.strip()) > 10]

    for your_sent in your_sentences:
        # Check if this fact has a contrasting equivalent in case
        best_contrast = None
        best_contrast_score = 0

        for case_fact in case_facts:
            # Look for facts that share topic but differ in outcome
            topic_overlap = word_overlap_score(your_sent.lower(), case_fact.lower())

            if 0.2 < topic_overlap < 0.6:  # Similar topic, different details
                # Check for contrasting language
                if has_contrasting_language(your_sent, case_fact):
                    if topic_overlap > best_contrast_score:
                        best_contrast = case_fact
                        best_contrast_score = topic_overlap

        if best_contrast:
            distinguishing.append((your_sent, best_contrast))

    return distinguishing[:5]  # Limit to 5 distinctions


def has_contrasting_language(text1: str, text2: str) -> bool:
    """Check if two texts have contrasting elements."""
    # Look for numerical differences
    nums1 = re.findall(r'\d+', text1)
    nums2 = re.findall(r'\d+', text2)
    if nums1 and nums2 and nums1 != nums2:
        return True

    # Look for opposing adjectives
    opposites = [
        ('high', 'low'), ('large', 'small'), ('many', 'few'),
        ('significant', 'insignificant'), ('material', 'immaterial'),
        ('intentional', 'unintentional'), ('knowing', 'unknowing'),
        ('reasonable', 'unreasonable'), ('sufficient', 'insufficient'),
    ]

    text1_lower = text1.lower()
    text2_lower = text2.lower()

    for word1, word2 in opposites:
        if (word1 in text1_lower and word2 in text2_lower) or \
           (word2 in text1_lower and word1 in text2_lower):
            return True

    return False


def generate_analogy_language(case_name: str, similar_pairs: List[Tuple[str, str]]) -> List[str]:
    """Generate draft analogy language for APPLICATION section."""
    language = []

    for your_fact, case_fact in similar_pairs[:3]:
        # Template: "Like [case] where [case fact], here [your fact]"
        lang = f"Like {case_name}, where {case_fact.lower().rstrip('.')}, here {your_fact.lower().rstrip('.')}."
        language.append(lang)

    return language


def generate_distinction_language(case_name: str, distinctions: List[Tuple[str, str]]) -> List[str]:
    """Generate draft distinction language for APPLICATION section."""
    language = []

    for your_fact, case_fact in distinctions[:3]:
        # Template: "Unlike [case], where [case fact], here [your fact]"
        lang = f"Unlike {case_name}, where {case_fact.lower().rstrip('.')}, here {your_fact.lower().rstrip('.')}."
        language.append(lang)

    return language


# ============================================================================
# FULL IRAC GENERATION
# ============================================================================

def generate_full_irac(
    cases: Dict[str, Tuple[str, str]],  # case_name -> (text, citation)
    client_facts: str,
    issue: str,
    target_jurisdiction: str = "5th Circuit"
) -> IRACDraft:
    """
    Generate a complete IRAC draft with synthesized rules and application.

    This is the main entry point for the killer feature.
    """
    warnings = []

    # Step 1: Rank cases by authority
    ranked_cases = rank_cases_by_authority(cases, client_facts, target_jurisdiction)

    # Step 2: Extract and synthesize rules
    synthesized_rules = synthesize_rules_from_cases(cases, issue)

    if not synthesized_rules:
        warnings.append("Could not extract clear rules from provided cases")

    # Step 3: Generate case analogies
    analogies = generate_case_analogies(client_facts, ranked_cases, synthesized_rules)

    # Step 4: Build ISSUE statement
    issue_statement = format_issue_statement(issue)

    # Step 5: Build RULE section
    rule_section = format_rule_section(synthesized_rules, ranked_cases)

    # Step 6: Build APPLICATION section
    application_section = format_application_section(synthesized_rules, analogies, client_facts)

    # Step 7: Build CONCLUSION
    conclusion = format_conclusion(issue, analogies)

    # Calculate confidence score
    confidence = calculate_confidence(ranked_cases, synthesized_rules, analogies)

    return IRACDraft(
        issue_statement=issue_statement,
        rule_section=rule_section,
        application_section=application_section,
        conclusion=conclusion,
        synthesized_rules=synthesized_rules,
        case_analogies=analogies,
        ranked_authorities=ranked_cases,
        confidence_score=confidence,
        warnings=warnings
    )


def rank_cases_by_authority(
    cases: Dict[str, Tuple[str, str]],
    your_facts: str,
    target_jurisdiction: str
) -> List[RankedCase]:
    """Rank cases by authority and relevance."""
    ranked = []

    for case_name, (text, citation) in cases.items():
        # Detect court level
        court_level = detect_court_level(citation, text)

        # Detect jurisdiction
        case_jurisdiction = detect_jurisdiction(citation)
        jx_match = calculate_jurisdiction_match(case_jurisdiction, target_jurisdiction)

        # Extract year
        year_match = re.search(r'\((\d{4})\)', citation)
        year = int(year_match.group(1)) if year_match else None

        # Extract rules
        rules = []
        if DEPENDENCIES_AVAILABLE:
            rules = extract_enhanced_rules(text, case_name, citation)

        # Extract key facts (simplified)
        facts_match = re.search(r'(?:FACTS?|BACKGROUND)[:\s]*\n(.+?)(?=\n(?:ISSUE|DISCUSSION|HOLDING)|\Z)',
                                text, re.IGNORECASE | re.DOTALL)
        key_facts = []
        if facts_match:
            fact_text = facts_match.group(1)
            key_facts = re.split(r'[.!?]+', fact_text)
            key_facts = [f.strip() for f in key_facts if len(f.strip()) > 20][:10]

        # Calculate fact similarity
        fact_sim, _ = calculate_fact_similarity(your_facts, key_facts)

        # Rule relevance (how many rules were extracted)
        rule_relevance = min(1.0, len(rules) / 5.0)

        # Extract holding
        holding = ""
        holding_match = re.search(r'(?:held|holding|conclude[sd]?)\s+that\s+(.+?)[.]', text, re.IGNORECASE)
        if holding_match:
            holding = holding_match.group(1)

        ranked_case = RankedCase(
            name=case_name,
            citation=citation,
            court_level=court_level,
            jurisdiction_match=jx_match,
            year=year,
            fact_similarity_score=fact_sim,
            rule_relevance_score=rule_relevance,
            rules=rules,
            key_facts=key_facts,
            holding=holding
        )
        ranked_case.calculate_overall_score()
        ranked.append(ranked_case)

    # Sort by overall score
    ranked.sort(key=lambda x: x.overall_score, reverse=True)

    return ranked


def format_issue_statement(issue: str) -> str:
    """Format the ISSUE statement."""
    # Ensure it starts properly
    if not issue.lower().startswith('whether'):
        issue = f"Whether {issue}"

    # Ensure it ends with question mark or period
    if not issue.endswith('?') and not issue.endswith('.'):
        issue += '.'

    return issue


def format_rule_section(rules: List[SynthesizedRule], ranked_cases: List[RankedCase]) -> str:
    """Format the RULE section with synthesized rules."""
    output = []

    # Primary rules first
    primary_rules = [r for r in rules if r.is_primary]
    secondary_rules = [r for r in rules if not r.is_primary]

    for rule in primary_rules:
        # Main rule statement with citation
        if rule.citations:
            output.append(f"{rule.statement} {rule.citations[0]}.")
            if len(rule.citations) > 1:
                output.append(f"Accord {'; '.join(rule.citations[1:3])}.")
        else:
            output.append(f"{rule.statement}.")

        output.append("")  # Blank line

        # Elements if present
        if rule.elements:
            output.append(f"To satisfy this standard, a party must demonstrate: ")
            for i, element in enumerate(rule.elements[:5], 1):
                output.append(f"({i}) {element};")
            output.append("")

    # Secondary rules
    for rule in secondary_rules[:3]:
        output.append(f"Additionally, {rule.statement.lower()} {rule.citations[0] if rule.citations else ''}.")
        output.append("")

    # Exceptions
    exceptions = [r.exceptions for r in rules if r.exceptions]
    if exceptions:
        flat_exceptions = [e for exc_list in exceptions for e in exc_list][:2]
        if flat_exceptions:
            output.append(f"However, {flat_exceptions[0].lower()}.")
            output.append("")

    return '\n'.join(output)


def format_application_section(
    rules: List[SynthesizedRule],
    analogies: List[CaseAnalogy],
    client_facts: str
) -> str:
    """Format the APPLICATION section with actual fact application."""
    output = []

    output.append("Applying these principles to the present facts:")
    output.append("")

    # Parse client facts into sentences for matching
    fact_sentences = [s.strip() for s in re.split(r'[.!?]+', client_facts) if len(s.strip()) > 15]

    # For each rule, generate actual application
    for rule in rules[:3]:
        if rule.elements:
            # Apply facts to each element
            for element in rule.elements[:4]:
                relevant_facts = find_facts_for_element(element, fact_sentences)
                if relevant_facts:
                    output.append(f"**{element.strip()[:80]}**")
                    output.append("")
                    for fact in relevant_facts[:2]:
                        output.append(f"Here, {fact.lower().rstrip('.')}. This satisfies the requirement because the facts demonstrate the necessary showing.")
                    output.append("")
                else:
                    output.append(f"**{element.strip()[:80]}**")
                    output.append("")
                    output.append(f"The record evidence establishes this element. [Cite specific facts from the record that support this element.]")
                    output.append("")
        else:
            # For non-element rules, apply the rule type
            output.append(f"**Applying the {rule.rule_type.value.replace('_', ' ')} standard:**")
            output.append("")
            if fact_sentences:
                # Use the most relevant facts
                output.append(f"The facts here demonstrate that {fact_sentences[0].lower().rstrip('.')}.")
                if len(fact_sentences) > 1:
                    output.append(f"Additionally, {fact_sentences[1].lower().rstrip('.')}.")
            output.append("")

    # Add analogy/distinction language from case comparisons
    strong_analogies = [a for a in analogies if a.similarity_type == "strong_analogy"]
    distinguishable = [a for a in analogies if a.similarity_type == "distinguishable"]

    if strong_analogies:
        output.append("")
        output.append("**Analogous Precedent:**")
        output.append("")
        for analogy in strong_analogies[:2]:
            if analogy.analogy_language:
                for lang in analogy.analogy_language[:2]:
                    output.append(lang)
            else:
                output.append(f"This case is analogous to {analogy.case_name}, {analogy.case_citation}, where the court found similar facts supported the same conclusion.")
        output.append("")

    if distinguishable:
        output.append("")
        output.append("**Distinguishing Adverse Authority:**")
        output.append("")
        for dist in distinguishable[:2]:
            if dist.distinction_language:
                for lang in dist.distinction_language[:2]:
                    output.append(lang)
            else:
                output.append(f"{dist.case_name} is distinguishable because the facts there differ materially from the present case.")
        output.append("")

    return '\n'.join(output)


def find_facts_for_element(element: str, fact_sentences: List[str]) -> List[str]:
    """Find facts that relate to a specific rule element."""
    element_lower = element.lower()
    relevant = []

    # Extract key terms from element
    key_terms = extract_legal_terms(element_lower)

    # Also look for common element keywords
    element_keywords = re.findall(r'\b(?:knowledge|intent|damage|breach|duty|conduct|act|harm|cause|reasonable|material|false|misleading|reliance|scienter)\b', element_lower)
    key_terms.update(element_keywords)

    for fact in fact_sentences:
        fact_lower = fact.lower()
        # Check if fact relates to element
        matches = sum(1 for term in key_terms if term in fact_lower)
        if matches >= 1:  # At least one key term match
            relevant.append(fact)

    return relevant[:3]  # Max 3 relevant facts per element


def format_conclusion(issue: str, analogies: List[CaseAnalogy]) -> str:
    """Format the CONCLUSION."""
    # Determine likely outcome based on analogies
    strong_analogies = sum(1 for a in analogies if a.similarity_type == "strong_analogy")
    distinguishable = sum(1 for a in analogies if a.similarity_type == "distinguishable")

    if strong_analogies > distinguishable:
        outcome = "likely"
    elif distinguishable > strong_analogies:
        outcome = "unlikely"
    else:
        outcome = "may"

    conclusion = f"Based on the foregoing analysis, [client] {outcome} [will/will not] prevail on the {issue.lower().replace('whether ', '')} issue because [summarize key reasons from application]."

    return conclusion


def calculate_confidence(
    ranked_cases: List[RankedCase],
    rules: List[SynthesizedRule],
    analogies: List[CaseAnalogy]
) -> float:
    """
    Calculate confidence score for the analysis.

    The score reflects data quality - what we actually extracted:
    - 40% base score if we have cases and extracted something
    - Up to 25% bonus for rules extracted
    - Up to 20% bonus for analogies/comparisons
    - Up to 15% bonus for binding authority or recent cases
    """
    score = 0.0

    # Base score: we have cases to work with (40%)
    if ranked_cases:
        score += 0.40

    # Rules extracted (up to 25%)
    if rules:
        # Having at least one rule is 15%, each additional adds 5% up to 25%
        score += 0.15 + min(0.10, (len(rules) - 1) * 0.05)

    # Analogies/comparisons generated (up to 20%)
    if analogies:
        score += 0.10  # Base for having any
        strong = sum(1 for a in analogies if a.similarity_type == "strong_analogy")
        score += min(0.10, strong * 0.05)

    # Authority bonus (up to 15%)
    binding = sum(1 for c in ranked_cases if c.jurisdiction_match == JurisdictionMatch.BINDING)
    persuasive_strong = sum(1 for c in ranked_cases if c.jurisdiction_match == JurisdictionMatch.PERSUASIVE_STRONG)
    persuasive_weak = sum(1 for c in ranked_cases if c.jurisdiction_match == JurisdictionMatch.PERSUASIVE_WEAK)
    recent = sum(1 for c in ranked_cases if c.year and c.year >= 2015)

    authority_bonus = min(0.10, binding * 0.05)  # Binding is best
    authority_bonus += min(0.03, persuasive_strong * 0.02)  # Strong persuasive helps
    authority_bonus += min(0.02, persuasive_weak * 0.01)  # Weak persuasive helps a bit
    authority_bonus += min(0.05, recent * 0.02)  # Recent cases help

    score += min(0.15, authority_bonus)

    return min(1.0, score)


# ============================================================================
# MCP INTEGRATION
# ============================================================================

def generate_irac_draft(
    cases: Dict[str, Tuple[str, str]],
    client_facts: str,
    issue: str,
    jurisdiction: str = "5th Circuit"
) -> str:
    """
    Main entry point for MCP server.

    Args:
        cases: Dict mapping case_name -> (full_text, citation)
        client_facts: Your client's facts
        issue: The legal issue
        jurisdiction: Target jurisdiction for authority ranking

    Returns:
        Formatted IRAC draft
    """
    # Validate input quality
    if not cases:
        return "ERROR: No cases provided. Please provide at least one case with text and citation."

    if not client_facts or len(client_facts.strip()) < 50:
        return "ERROR: Client facts are too brief. Please provide detailed facts (at least a few sentences)."

    if not issue or len(issue.strip()) < 10:
        return "ERROR: Legal issue is too vague. Please provide a specific issue statement."

    draft = generate_full_irac(cases, client_facts, issue, jurisdiction)

    output = []

    # Quality threshold check
    if draft.confidence_score < 0.40:
        output.append("=" * 70)
        output.append("LOW CONFIDENCE WARNING - RESULTS MAY BE UNRELIABLE")
        output.append("=" * 70)
        output.append(f"\nConfidence Score: {draft.confidence_score:.0%} (below 40% threshold)")
        output.append("")
        output.append("ISSUES DETECTED:")
        if not draft.synthesized_rules:
            output.append("  - Could not extract clear rules from provided case text")
        if not draft.case_analogies or all(a.similarity_type == "distinguishable" for a in draft.case_analogies):
            output.append("  - No strong analogies found between your facts and cases")
        if not any(c.jurisdiction_match == JurisdictionMatch.BINDING for c in draft.ranked_authorities):
            output.append("  - No binding authority found for your jurisdiction")
        output.append("")
        output.append("SUGGESTIONS:")
        output.append("  - Provide more complete case text (full opinions work best)")
        output.append("  - Use cases more factually similar to your situation")
        output.append("  - Try the simpler 'extract_rules' or 'compare_cases' tools first")
        output.append("")
        output.append("Proceeding with low-confidence draft below...")
        output.append("")

    output.append("IRAC DRAFT - SYNTHESIZED FROM MULTIPLE CASES")
    output.append("=" * 70)

    if draft.warnings:
        output.append("\nWARNINGS:")
        for w in draft.warnings:
            output.append(f"  - {w}")
        output.append("")

    output.append(f"\nConfidence Score: {draft.confidence_score:.0%}")
    output.append("")

    # Authority ranking
    output.append("TOP AUTHORITIES (by relevance):")
    output.append("-" * 70)
    for i, case in enumerate(draft.ranked_authorities[:5], 1):
        output.append(f"{i}. {case.name}, {case.citation}")
        output.append(f"   Score: {case.overall_score:.2f} | Court: {case.court_level.name} | Match: {case.jurisdiction_match.name}")
    output.append("")

    output.append("=" * 70)
    output.append("ISSUE")
    output.append("=" * 70)
    output.append(draft.issue_statement)
    output.append("")

    output.append("=" * 70)
    output.append("RULE (Synthesized)")
    output.append("=" * 70)
    output.append(draft.rule_section)

    output.append("=" * 70)
    output.append("APPLICATION")
    output.append("=" * 70)
    output.append(draft.application_section)

    output.append("=" * 70)
    output.append("CONCLUSION")
    output.append("=" * 70)
    output.append(draft.conclusion)

    return '\n'.join(output)


def rank_authorities(
    cases: Dict[str, Tuple[str, str]],
    your_facts: str,
    jurisdiction: str = "5th Circuit"
) -> str:
    """
    Rank cases by authority and relevance.

    Args:
        cases: Dict mapping case_name -> (full_text, citation)
        your_facts: Your client's facts
        jurisdiction: Target jurisdiction

    Returns:
        Formatted ranking report
    """
    ranked = rank_cases_by_authority(cases, your_facts, jurisdiction)

    output = []
    output.append("AUTHORITY RANKING REPORT")
    output.append("=" * 70)
    output.append(f"Target Jurisdiction: {jurisdiction}")
    output.append(f"Cases Analyzed: {len(ranked)}")
    output.append("")

    for i, case in enumerate(ranked, 1):
        output.append(f"\n{i}. {case.name}")
        output.append(f"   Citation: {case.citation}")
        output.append(f"   Overall Score: {case.overall_score:.2f}")
        output.append(f"   Court Level: {case.court_level.name}")
        output.append(f"   Jurisdiction Match: {case.jurisdiction_match.name}")
        output.append(f"   Year: {case.year or 'Unknown'}")
        output.append(f"   Fact Similarity: {case.fact_similarity_score:.0%}")
        output.append(f"   Rule Relevance: {case.rule_relevance_score:.0%}")

        if case.holding:
            output.append(f"   Holding: {case.holding[:100]}...")

    return '\n'.join(output)
