"""
Argument Weakness Detector
==========================
Analyzes legal arguments to identify vulnerabilities, weak citations,
logical gaps, and counter-argument opportunities.

For both:
- Self-review before filing
- Analyzing opposing counsel's arguments
"""

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


# ============================================================================
# ENUMS AND DATA CLASSES
# ============================================================================

class WeaknessType(Enum):
    """Categories of argument weaknesses."""
    LOGICAL_FALLACY = "logical_fallacy"
    WEAK_CITATION = "weak_citation"
    FACTUAL_GAP = "factual_gap"
    OVERREACH = "overreach"
    ASSUMPTION = "unsupported_assumption"
    MISSING_ELEMENT = "missing_element"
    COUNTER_VULNERABLE = "counter_argument_vulnerable"
    HEDGING = "excessive_hedging"
    CONCLUSORY = "conclusory_statement"
    UNPUBLISHED_CITE = "unpublished_citation"
    DICTA_AS_HOLDING = "dicta_as_holding"
    UNADDRESSED_DISTINCTION = "unaddressed_distinction"
    MISSING_COUNTER = "missing_counter_argument"


class LitigationStage(Enum):
    """Stage of litigation - affects severity weighting."""
    PLEADING = "pleading"           # Complaint, Answer
    MOTION_TO_DISMISS = "mtd"       # 12(b)(6)
    DISCOVERY = "discovery"         # Interrogatories, depositions
    SUMMARY_JUDGMENT = "msj"        # Most critical for fact/law analysis
    TRIAL = "trial"                 # Jury instructions, testimony
    APPEAL = "appeal"               # Record-based, law-focused


class Severity(Enum):
    """Severity of the weakness."""
    CRITICAL = "critical"      # Could lose the argument
    MAJOR = "major"            # Significant vulnerability
    MODERATE = "moderate"      # Should address
    MINOR = "minor"            # Polish issue


@dataclass
class ArgumentWeakness:
    """A detected weakness in an argument."""
    weakness_type: WeaknessType
    severity: Severity
    description: str
    text_excerpt: str
    suggestion: str
    location: int = 0  # Character position in text
    stage_relevance: Optional[List[str]] = None  # Which stages this matters most


@dataclass
class WeaknessReport:
    """Complete weakness analysis report."""
    weaknesses: List[ArgumentWeakness] = field(default_factory=list)
    strength_score: float = 0.0  # 0-100
    critical_count: int = 0
    major_count: int = 0
    summary: str = ""
    stage: Optional[LitigationStage] = None
    stage_adjusted_score: float = 0.0  # Score adjusted for litigation stage
    missing_counter_arguments: List[str] = field(default_factory=list)


# ============================================================================
# LOGICAL FALLACY PATTERNS
# ============================================================================

LOGICAL_FALLACIES = {
    # Slippery slope
    r'\b(?:will\s+(?:inevitably|necessarily)|must\s+lead\s+to|opens\s+the\s+(?:door|floodgates)|if\s+we\s+allow.*then)\b': {
        'name': 'Slippery Slope',
        'severity': Severity.MAJOR,
        'suggestion': 'Establish causal connection with evidence rather than speculation'
    },

    # Appeal to authority (without substance)
    r'\b(?:courts\s+have\s+(?:always|traditionally|uniformly)|it\s+is\s+(?:well\s+)?(?:settled|established)\s+that)\b(?![^.]*\d+\s+(?:U\.?S\.|F\.\d|S\.?\s*Ct))': {
        'name': 'Unsupported Appeal to Authority',
        'severity': Severity.MODERATE,
        'suggestion': 'Add specific citation to support the claim'
    },

    # False dichotomy
    r'\b(?:either.*or|the\s+only\s+(?:two\s+)?(?:options?|choices?|alternatives?)|must\s+(?:either|choose\s+between))\b': {
        'name': 'False Dichotomy',
        'severity': Severity.MODERATE,
        'suggestion': 'Acknowledge other possibilities or justify why only two options exist'
    },

    # Ad hominem (attacking party credibility inappropriately)
    r'\b(?:opposing\s+counsel\s+(?:ignores|fails|misunderstands|cannot)|plaintiff\'?s?\s+(?:bad\s+faith|dishonest)|defendant\'?s?\s+(?:bad\s+faith|dishonest))\b': {
        'name': 'Ad Hominem Risk',
        'severity': Severity.MINOR,
        'suggestion': 'Focus on the legal argument rather than opposing party\'s character'
    },

    # Straw man indicators
    r'\b(?:apparently\s+(?:believes|argues|contends)|seems?\s+to\s+(?:suggest|argue)|would\s+have\s+(?:us|this\s+court)\s+believe)\b': {
        'name': 'Potential Straw Man',
        'severity': Severity.MODERATE,
        'suggestion': 'Quote opposing argument directly rather than characterizing it'
    },

    # Circular reasoning
    r'\b(?:because\s+it\s+is|is\s+(?:\w+\s+)?because\s+it\s+is|by\s+definition)\b': {
        'name': 'Circular Reasoning Risk',
        'severity': Severity.MAJOR,
        'suggestion': 'Provide independent evidence or reasoning'
    },

    # Post hoc fallacy
    r'\b(?:after.*therefore|since.*then.*must\s+have|following.*(?:caused|resulted))\b': {
        'name': 'Post Hoc Fallacy Risk',
        'severity': Severity.MODERATE,
        'suggestion': 'Establish actual causation, not just temporal sequence'
    },
}


# ============================================================================
# WEAK CITATION PATTERNS
# ============================================================================

def detect_weak_citations(text: str) -> List[ArgumentWeakness]:
    """Detect citations that may be weak authority."""
    weaknesses = []

    # Pattern for citations with years
    citation_year_pattern = r'(\d{1,3}\s+(?:U\.?S\.|F\.\s*(?:2d|3d|4th)?|S\.\s*Ct\.|F\.\s*Supp\.?(?:\s*2d|3d)?)\s+\d+).*?\(.*?((?:19|20)\d{2})\)'

    current_year = 2026  # Update as needed

    for match in re.finditer(citation_year_pattern, text):
        citation = match.group(1)
        year = int(match.group(2))
        age = current_year - year

        # Very old cases (30+ years)
        if age >= 30:
            weaknesses.append(ArgumentWeakness(
                weakness_type=WeaknessType.WEAK_CITATION,
                severity=Severity.MODERATE,
                description=f"Citation is {age} years old - may be outdated",
                text_excerpt=match.group(0)[:100],
                suggestion="Verify case is still good law and consider citing more recent authority",
                location=match.start()
            ))

        # Old cases (15-30 years) - just note
        elif age >= 15:
            weaknesses.append(ArgumentWeakness(
                weakness_type=WeaknessType.WEAK_CITATION,
                severity=Severity.MINOR,
                description=f"Citation is {age} years old",
                text_excerpt=match.group(0)[:100],
                suggestion="Consider supplementing with more recent authority if available",
                location=match.start()
            ))

    # Detect dicta signals
    dicta_patterns = [
        (r'(?:dictum|dicta|in\s+passing|by\s+way\s+of\s+(?:dicta|illustration)|noted\s+in\s+dicta)', 'Dicta - not binding'),
        (r'(?:see\s+also|cf\.|compare|but\s+see|contra)', 'Weak citation signal'),
        (r'(?:unpublished|not\s+for\s+publication|non-?precedential)', 'Unpublished/non-precedential'),
        (r'(?:concurr(?:ing|ence)|dissent(?:ing)?)', 'Concurrence/dissent - not majority holding'),
    ]

    for pattern, desc in dicta_patterns:
        for match in re.finditer(pattern, text, re.IGNORECASE):
            # Check if near a citation
            context_start = max(0, match.start() - 50)
            context_end = min(len(text), match.end() + 100)
            context = text[context_start:context_end]

            if re.search(r'\d+\s+(?:U\.?S\.|F\.|S\.\s*Ct)', context):
                weaknesses.append(ArgumentWeakness(
                    weakness_type=WeaknessType.WEAK_CITATION,
                    severity=Severity.MODERATE if 'dicta' in desc.lower() or 'unpublished' in desc.lower() else Severity.MINOR,
                    description=desc,
                    text_excerpt=context.strip(),
                    suggestion="Consider whether stronger authority is available for this point",
                    location=match.start()
                ))

    return weaknesses


# ============================================================================
# FACTUAL GAP DETECTION
# ============================================================================

def detect_factual_gaps(text: str) -> List[ArgumentWeakness]:
    """Detect unsupported factual assertions."""
    weaknesses = []

    # Factual claims without record citations
    factual_claim_patterns = [
        # Definite factual statements
        (r'(?:The\s+)?(?:plaintiff|defendant|petitioner|respondent|appellant|appellee)\s+(?:did|knew|intended|was\s+aware|understood|believed)',
         'Factual claim about party knowledge/intent'),

        # Quantitative claims
        (r'\b(?:many|most|all|every|numerous|substantial|significant)\s+(?:\w+\s+){0,3}(?:cases?|courts?|jurisdictions?)',
         'Quantitative claim about legal landscape'),

        # Temporal claims
        (r'\b(?:always|never|at\s+all\s+times|consistently|repeatedly)\b',
         'Absolute temporal claim'),
    ]

    # Check for record citations nearby
    record_cite_pattern = r'(?:\([Rr](?:ec)?\.?\s*(?:at\s*)?\d+|[Dd]kt\.\s*\d+|Ex\.\s*[A-Z\d]+|\d+\s*[Aa]pp\.\s*\d+)'

    for pattern, desc in factual_claim_patterns:
        for match in re.finditer(pattern, text):
            # Check surrounding context for record citation
            context_start = max(0, match.start() - 50)
            context_end = min(len(text), match.end() + 100)
            context = text[context_start:context_end]

            if not re.search(record_cite_pattern, context):
                weaknesses.append(ArgumentWeakness(
                    weakness_type=WeaknessType.FACTUAL_GAP,
                    severity=Severity.MAJOR,
                    description=f"{desc} - no record citation",
                    text_excerpt=match.group(0),
                    suggestion="Add record citation to support this factual claim",
                    location=match.start()
                ))

    return weaknesses


# ============================================================================
# OVERREACH DETECTION
# ============================================================================

def detect_overreach(text: str) -> List[ArgumentWeakness]:
    """Detect overreaching claims and conclusions."""
    weaknesses = []

    overreach_patterns = [
        # Absolute language
        (r'\b(?:clearly|obviously|undoubtedly|unquestionably|certainly|beyond\s+(?:any\s+)?(?:doubt|question))\b',
         Severity.MODERATE,
         'Absolute claim',
         'Consider softening unless truly undisputed'),

        # Extreme characterizations
        (r'\b(?:completely|entirely|totally|utterly|absolutely|wholly)\s+(?:without|lacking|devoid|meritless|frivolous)',
         Severity.MODERATE,
         'Extreme characterization',
         'Strong language may undermine credibility - let facts speak'),

        # Conclusory legal claims
        (r'\b(?:is\s+(?:clearly\s+)?(?:arbitrary|unreasonable|irrational|absurd)|(?:clearly|obviously)\s+(?:violates|fails))\b',
         Severity.MAJOR,
         'Conclusory legal claim',
         'Support with specific facts and legal analysis'),

        # Sweeping generalizations
        (r'\b(?:in\s+every\s+(?:case|instance)|without\s+exception|in\s+all\s+(?:cases|circumstances))\b',
         Severity.MODERATE,
         'Sweeping generalization',
         'Verify accuracy or qualify the statement'),

        # Unfounded certainty about outcome
        (r'\b(?:must\s+(?:necessarily\s+)?(?:find|conclude|hold)|can\s+only\s+(?:find|conclude|reach\s+one\s+conclusion))\b',
         Severity.MODERATE,
         'Commanding the court',
         'Rephrase as respectful advocacy rather than directive'),
    ]

    for pattern, severity, desc, suggestion in overreach_patterns:
        for match in re.finditer(pattern, text, re.IGNORECASE):
            weaknesses.append(ArgumentWeakness(
                weakness_type=WeaknessType.OVERREACH,
                severity=severity,
                description=desc,
                text_excerpt=match.group(0),
                suggestion=suggestion,
                location=match.start()
            ))

    return weaknesses


# ============================================================================
# ASSUMPTION DETECTION
# ============================================================================

def detect_assumptions(text: str) -> List[ArgumentWeakness]:
    """Detect unsupported assumptions."""
    weaknesses = []

    assumption_patterns = [
        # Implicit assumptions
        (r'\b(?:assuming|presumes?|takes?\s+for\s+granted|given\s+that)\b',
         Severity.MODERATE,
         'Explicit assumption',
         'Ensure assumption is supported or acknowledged'),

        # Hidden premises
        (r'\b(?:necessarily\s+(?:means|implies|follows)|it\s+follows\s+that|therefore|thus|hence|accordingly)\b',
         Severity.MINOR,
         'Logical connector - verify premise',
         'Verify the logical step is supported'),

        # Begging the question
        (r'\b(?:since\s+\w+\s+is\s+\w+,?\s+(?:it\s+)?(?:must|should|cannot))\b',
         Severity.MODERATE,
         'Potential circular reasoning',
         'Ensure premise is independently established'),
    ]

    for pattern, severity, desc, suggestion in assumption_patterns:
        for match in re.finditer(pattern, text, re.IGNORECASE):
            weaknesses.append(ArgumentWeakness(
                weakness_type=WeaknessType.ASSUMPTION,
                severity=severity,
                description=desc,
                text_excerpt=match.group(0),
                suggestion=suggestion,
                location=match.start()
            ))

    return weaknesses


# ============================================================================
# HEDGING DETECTION
# ============================================================================

def detect_hedging(text: str) -> List[ArgumentWeakness]:
    """Detect excessive hedging that weakens arguments."""
    weaknesses = []
    hedge_count = 0

    hedge_patterns = [
        r'\b(?:may|might|could|possibly|perhaps|arguably|potentially|appears?\s+to|seems?\s+to)\b',
        r'\b(?:it\s+(?:is\s+)?(?:possible|conceivable|arguable)\s+that)\b',
        r'\b(?:to\s+(?:some|a\s+certain)\s+(?:extent|degree))\b',
    ]

    for pattern in hedge_patterns:
        matches = list(re.finditer(pattern, text, re.IGNORECASE))
        hedge_count += len(matches)

    # Calculate hedge density
    word_count = len(text.split())
    if word_count > 0:
        hedge_density = (hedge_count / word_count) * 100

        if hedge_density > 3.0:  # More than 3% hedging words
            weaknesses.append(ArgumentWeakness(
                weakness_type=WeaknessType.HEDGING,
                severity=Severity.MODERATE,
                description=f"Excessive hedging ({hedge_count} hedging phrases in {word_count} words)",
                text_excerpt="[Document-wide issue]",
                suggestion="Consider strengthening key assertions where facts support certainty",
                location=0
            ))
        elif hedge_density > 2.0:
            weaknesses.append(ArgumentWeakness(
                weakness_type=WeaknessType.HEDGING,
                severity=Severity.MINOR,
                description=f"Moderate hedging ({hedge_count} hedging phrases)",
                text_excerpt="[Document-wide issue]",
                suggestion="Review hedging language - some may be unnecessary",
                location=0
            ))

    return weaknesses


# ============================================================================
# CONCLUSORY STATEMENT DETECTION
# ============================================================================

def detect_conclusory(text: str) -> List[ArgumentWeakness]:
    """Detect conclusory statements lacking analysis."""
    weaknesses = []

    # Split into sentences
    sentences = re.split(r'(?<=[.!?])\s+', text)

    conclusory_patterns = [
        r'^(?:Thus|Therefore|Accordingly|Hence|Consequently|As\s+(?:such|a\s+result)),?\s+(?:the\s+)?(?:plaintiff|defendant|court)\s+(?:must|should|cannot|fails?)',
        r'^(?:It\s+is\s+(?:clear|obvious|evident|apparent)\s+that)',
        r'^(?:There\s+(?:is|can\s+be)\s+no\s+(?:question|doubt)\s+that)',
    ]

    for sentence in sentences:
        sentence = sentence.strip()
        if len(sentence) < 150:  # Short conclusory sentences
            for pattern in conclusory_patterns:
                if re.match(pattern, sentence, re.IGNORECASE):
                    # Check if next sentence provides support
                    weaknesses.append(ArgumentWeakness(
                        weakness_type=WeaknessType.CONCLUSORY,
                        severity=Severity.MODERATE,
                        description="Conclusory statement - may need supporting analysis",
                        text_excerpt=sentence[:100],
                        suggestion="Add specific reasoning or facts before the conclusion",
                        location=text.find(sentence)
                    ))
                    break

    return weaknesses


# ============================================================================
# COUNTER-ARGUMENT VULNERABILITY DETECTION
# ============================================================================

def detect_counter_vulnerabilities(text: str) -> List[ArgumentWeakness]:
    """Detect areas vulnerable to counter-argument."""
    weaknesses = []

    # Issues not addressed (common legal issues)
    standard_issues = [
        ('standing', r'\bstanding\b'),
        ('jurisdiction', r'\bjurisdiction(?:al)?\b'),
        ('statute of limitations', r'\b(?:statute\s+of\s+)?limitations?\b'),
        ('ripeness', r'\bripeness\b'),
        ('mootness', r'\bmoot(?:ness)?\b'),
        ('exhaustion', r'\bexhaust(?:ion|ed)?\b'),
        ('damages', r'\bdamages?\b'),
        ('causation', r'\bcaus(?:ation|ed?|al)\b'),
        ('proximate cause', r'\bproximate\s+cause\b'),
    ]

    # Check if document discusses affirmative claims but misses standard defenses
    has_affirmative_claim = bool(re.search(r'\b(?:claims?|alleges?|asserts?|contends?)\b', text, re.IGNORECASE))

    if has_affirmative_claim:
        for issue_name, pattern in standard_issues:
            if not re.search(pattern, text, re.IGNORECASE):
                # This might be a gap - but only flag if relevant context suggests it
                pass  # Don't over-flag

    # Detect counter-argument acknowledgment gaps
    counter_signals = [
        r'\b(?:opposing|defendant\'?s?|plaintiff\'?s?)\s+(?:argu(?:es?|ment)|contends?|claims?)\b',
        r'\b(?:may\s+argue|will\s+(?:likely\s+)?argue|could\s+contend)\b',
        r'\b(?:anticipated|foreseeable)\s+(?:counter)?argument\b',
    ]

    has_counter_acknowledgment = any(
        re.search(p, text, re.IGNORECASE) for p in counter_signals
    )

    # Check for "distinguish" or "inapposite" - signs of addressing counter
    addresses_counter = bool(re.search(
        r'\b(?:distinguish(?:able|ed)?|inapposite|inapplicable|does\s+not\s+apply)\b',
        text, re.IGNORECASE
    ))

    if not has_counter_acknowledgment and not addresses_counter:
        word_count = len(text.split())
        if word_count > 500:  # Only flag for substantial documents
            weaknesses.append(ArgumentWeakness(
                weakness_type=WeaknessType.COUNTER_VULNERABLE,
                severity=Severity.MODERATE,
                description="No apparent acknowledgment of counter-arguments",
                text_excerpt="[Document-wide issue]",
                suggestion="Consider addressing anticipated counter-arguments to strengthen position",
                location=0
            ))

    return weaknesses


# ============================================================================
# MISSING ELEMENT DETECTION (for common legal standards)
# ============================================================================

def detect_missing_elements(text: str) -> List[ArgumentWeakness]:
    """Detect potentially missing elements for common legal standards."""
    weaknesses = []

    # Common multi-element tests and their elements
    legal_tests = {
        'negligence': {
            'trigger': r'\bnegligen(?:ce|t)\b',
            'elements': ['duty', 'breach', 'causation', 'damages'],
            'patterns': [r'\bduty\b', r'\bbreach(?:ed)?\b', r'\bcaus(?:ed?|ation)\b', r'\bdamages?\b']
        },
        'fraud': {
            'trigger': r'\bfraud(?:ulent)?\b',
            'elements': ['misrepresentation', 'materiality', 'scienter', 'reliance', 'damages'],
            'patterns': [r'\bmisrepresent', r'\bmaterial', r'\bscienter|intent|knowledge\b', r'\breli(?:ed|ance)\b', r'\bdamages?\b']
        },
        'contract_breach': {
            'trigger': r'\bbreach\s+of\s+contract\b',
            'elements': ['valid contract', 'performance', 'breach', 'damages'],
            'patterns': [r'\bvalid\s+(?:contract|agreement)\b', r'\bperform(?:ed|ance)\b', r'\bbreach(?:ed)?\b', r'\bdamages?\b']
        },
        'preliminary_injunction': {
            'trigger': r'\bpreliminary\s+injunction\b',
            'elements': ['likelihood of success', 'irreparable harm', 'balance of hardships', 'public interest'],
            'patterns': [r'\blikelihood\s+of\s+success\b', r'\birreparable\s+harm\b', r'\bbalance\s+of\s+(?:hardships?|equities)\b', r'\bpublic\s+interest\b']
        },
        'summary_judgment': {
            'trigger': r'\bsummary\s+judgment\b',
            'elements': ['no genuine dispute', 'material fact', 'entitled to judgment'],
            'patterns': [r'\bno\s+genuine\s+(?:dispute|issue)\b', r'\bmaterial\s+fact\b', r'\bentitled\s+to\s+judgment\b']
        },
    }

    for test_name, test_info in legal_tests.items():
        if re.search(test_info['trigger'], text, re.IGNORECASE):
            missing = []
            for element, pattern in zip(test_info['elements'], test_info['patterns']):
                if not re.search(pattern, text, re.IGNORECASE):
                    missing.append(element)

            if missing and len(missing) <= len(test_info['elements']) - 1:  # At least one element present
                weaknesses.append(ArgumentWeakness(
                    weakness_type=WeaknessType.MISSING_ELEMENT,
                    severity=Severity.MAJOR if len(missing) > 1 else Severity.MODERATE,
                    description=f"{test_name.replace('_', ' ').title()}: potentially missing elements: {', '.join(missing)}",
                    text_excerpt=f"[Analysis of {test_name.replace('_', ' ')} claim]",
                    suggestion=f"Verify all elements are addressed: {', '.join(test_info['elements'])}",
                    location=0
                ))

    return weaknesses


# ============================================================================
# UNPUBLISHED OPINION DETECTION
# ============================================================================

def detect_unpublished_citations(text: str) -> List[ArgumentWeakness]:
    """Detect citations to unpublished opinions without proper notation."""
    weaknesses = []

    # Patterns that indicate unpublished opinions
    unpublished_indicators = [
        r'\d{4}\s+WL\s+\d+',           # Westlaw citations (often unpublished)
        r'\d{4}\s+U\.S\.\s+Dist\.\s+LEXIS',  # District court LEXIS
        r'\d{4}\s+U\.S\.\s+App\.\s+LEXIS',   # Appellate LEXIS
        r'slip\s+op\.',                 # Slip opinions
        r'No\.\s+\d+-\w+-\d+',          # Docket numbers without reporter
    ]

    # Check if "unpublished" or "non-precedential" notation exists
    has_unpublished_notation = bool(re.search(
        r'\b(?:unpublished|non-?precedential|not\s+for\s+publication|table)\b',
        text, re.IGNORECASE
    ))

    for pattern in unpublished_indicators:
        for match in re.finditer(pattern, text):
            # Check context for unpublished notation
            context_start = max(0, match.start() - 100)
            context_end = min(len(text), match.end() + 100)
            context = text[context_start:context_end]

            if not re.search(r'\b(?:unpublished|non-?precedential)\b', context, re.IGNORECASE):
                weaknesses.append(ArgumentWeakness(
                    weakness_type=WeaknessType.UNPUBLISHED_CITE,
                    severity=Severity.MAJOR,
                    description="Possible citation to unpublished opinion without notation",
                    text_excerpt=match.group(0),
                    suggestion="Verify publication status; if unpublished, note 'unpublished' and check local rules on citability",
                    location=match.start(),
                    stage_relevance=["appeal", "msj"]
                ))

    return weaknesses


# ============================================================================
# DICTA VS. HOLDING DETECTION
# ============================================================================

def detect_dicta_as_holding(text: str) -> List[ArgumentWeakness]:
    """Detect when dicta may be cited as holding."""
    weaknesses = []

    # Patterns suggesting dicta being treated as holding
    dicta_risk_patterns = [
        # "Court noted" or "Court observed" (often dicta)
        (r'(?:court|judge)\s+(?:noted|observed|remarked|commented|suggested)\s+(?:that\s+)?([^.]+\.)',
         'Court "noting/observing" language often indicates dicta'),

        # "In dictum" acknowledgment
        (r'\b(?:in\s+)?dict(?:um|a)\b',
         'Explicit dicta reference - ensure not relied upon as holding'),

        # Hypothetical language
        (r'(?:court|opinion)\s+(?:hypothesized|speculated|imagined|wondered)\s+(?:that\s+)?([^.]+\.)',
         'Hypothetical language suggests dicta'),

        # "By the way" or "incidentally"
        (r'\b(?:incidentally|parenthetically|in\s+passing|by\s+the\s+way)\b',
         'Parenthetical language suggests dicta'),

        # Footnote citations for key propositions
        (r'([^.]+\.\s*)(?:See\s+)?[A-Z][a-z]+\s+v\.\s+[A-Z][a-z]+[^.]+n\.\s*\d+',
         'Footnote citation may indicate dicta or secondary point'),
    ]

    for pattern, desc in dicta_risk_patterns:
        for match in re.finditer(pattern, text, re.IGNORECASE):
            # Check if this is supporting a key argument
            context_start = max(0, match.start() - 200)
            context = text[context_start:match.start()]

            # Key argument indicators
            key_arg_indicators = ['therefore', 'thus', 'accordingly', 'establishes', 'proves', 'demonstrates']
            is_key_support = any(ind in context.lower() for ind in key_arg_indicators)

            if is_key_support:
                weaknesses.append(ArgumentWeakness(
                    weakness_type=WeaknessType.DICTA_AS_HOLDING,
                    severity=Severity.MAJOR,
                    description=desc,
                    text_excerpt=match.group(0)[:100],
                    suggestion="Verify this is binding holding, not dicta. If dicta, find stronger authority.",
                    location=match.start(),
                    stage_relevance=["appeal", "msj", "mtd"]
                ))

    return weaknesses


# ============================================================================
# UNADDRESSED DISTINCTION DETECTION
# ============================================================================

def detect_unaddressed_distinctions(text: str) -> List[ArgumentWeakness]:
    """Detect cases that may be factually distinguishable but aren't addressed."""
    weaknesses = []

    # Find case citations
    case_pattern = r'([A-Z][a-z]+\s+v\.\s+[A-Z][a-z]+),?\s*\d+\s+(?:U\.S\.|F\.\d+d?|S\.\s*Ct\.)'

    # Find all cited cases
    cases = list(re.finditer(case_pattern, text))

    for match in cases:
        case_name = match.group(1)
        context_start = max(0, match.start() - 300)
        context_end = min(len(text), match.end() + 300)
        context = text[context_start:context_end]

        # Check if comparison language exists
        comparison_language = [
            'like in', 'similar to', 'as in', 'just as',
            'unlike', 'distinguishable', 'different from',
            'analogous', 'on all fours', 'indistinguishable'
        ]

        has_comparison = any(comp in context.lower() for comp in comparison_language)

        # Check for factual description of cited case
        factual_description = bool(re.search(
            r'(?:in\s+that\s+case|where\s+the|there,?\s+the|involved|concerned)\s+(?:[^.]+\.)',
            context, re.IGNORECASE
        ))

        # If case is cited without comparison to present facts
        if not has_comparison and not factual_description:
            # Check if this appears to be a key authority (not just string cite)
            is_key_cite = bool(re.search(r'(?:see|held|established|set\s+forth)', context.lower()))

            if is_key_cite:
                weaknesses.append(ArgumentWeakness(
                    weakness_type=WeaknessType.UNADDRESSED_DISTINCTION,
                    severity=Severity.MODERATE,
                    description=f"Case cited without factual comparison: {case_name}",
                    text_excerpt=context[150:250] if len(context) > 250 else context,
                    suggestion=f"Add factual comparison: 'Like in {case_name}, where [precedent fact], here [your fact].'",
                    location=match.start(),
                    stage_relevance=["msj", "appeal"]
                ))

    return weaknesses[:5]  # Limit to avoid over-flagging


# ============================================================================
# CONCLUSORY TRANSITION DETECTION
# ============================================================================

def detect_conclusory_transitions(text: str) -> List[ArgumentWeakness]:
    """Detect conclusory transitions without supporting logic."""
    weaknesses = []

    # Conclusory transition patterns
    conclusory_patterns = [
        (r'\b(clearly|obviously|undoubtedly|plainly|manifestly)\s+(?:shows?|demonstrates?|establishes?|proves?)',
         'Conclusory "clearly shows" without specific reasoning'),

        (r'\b(it\s+follows\s+that|it\s+is\s+(?:therefore\s+)?(?:clear|obvious|evident)\s+that)',
         'Conclusory transition - add specific logical step'),

        (r'\b(as\s+(?:is\s+)?(?:clearly|plainly)\s+(?:shown|demonstrated|established))',
         'Conclusory reference to prior argument'),

        (r'\b(no\s+(?:reasonable|rational)\s+(?:person|juror|factfinder)\s+could)',
         'Conclusory reasonableness claim'),

        (r'\b(the\s+(?:only|inescapable|inevitable)\s+conclusion\s+is)',
         'Conclusory "only conclusion" without showing why'),

        (r'\b((?:this|which)\s+(?:clearly|obviously|necessarily)\s+means)',
         'Conclusory connector without explanation'),
    ]

    for pattern, desc in conclusory_patterns:
        for match in re.finditer(pattern, text, re.IGNORECASE):
            # Check if followed by specific reasoning
            following_text = text[match.end():match.end() + 200]

            # Look for specific facts or citations following
            has_specifics = bool(re.search(
                r'(?:because|since|given\s+that|as\s+evidenced\s+by|\(R\.\s*\d+|testified|stated)',
                following_text, re.IGNORECASE
            ))

            if not has_specifics:
                weaknesses.append(ArgumentWeakness(
                    weakness_type=WeaknessType.CONCLUSORY,
                    severity=Severity.MODERATE,
                    description=desc,
                    text_excerpt=match.group(0),
                    suggestion="Replace conclusory language with specific reasoning: 'X because Y, as shown by Z.'",
                    location=match.start(),
                    stage_relevance=["msj", "appeal", "mtd"]
                ))

    return weaknesses


# ============================================================================
# MISSING COUNTER-ARGUMENT DETECTION
# ============================================================================

# Common counter-arguments by claim type
COMMON_COUNTER_ARGUMENTS = {
    'negligence': [
        ('comparative fault', r'\b(?:comparative|contributory)\s+(?:fault|negligence)\b'),
        ('assumption of risk', r'\bassumption\s+of\s+(?:the\s+)?risk\b'),
        ('no duty', r'\b(?:no|owed\s+no)\s+duty\b'),
        ('superseding cause', r'\bsuperseding\s+(?:cause|intervening)\b'),
    ],
    'contract': [
        ('statute of frauds', r'\bstatute\s+of\s+frauds\b'),
        ('lack of consideration', r'\b(?:no|lack\s+of)\s+consideration\b'),
        ('impossibility', r'\b(?:impossibility|impracticability)\b'),
        ('waiver', r'\bwaiv(?:er|ed)\b'),
        ('estoppel', r'\bestoppel\b'),
    ],
    'discrimination': [
        ('legitimate business reason', r'\b(?:legitimate|non-?discriminatory)\s+(?:business\s+)?reason\b'),
        ('similarly situated', r'\bsimilarly\s+situated\b'),
        ('pretext', r'\bpretext(?:ual)?\b'),
        ('stray remarks', r'\bstray\s+remarks?\b'),
    ],
    'constitutional': [
        ('qualified immunity', r'\bqualified\s+immunity\b'),
        ('rational basis', r'\brational\s+basis\b'),
        ('standing', r'\bstanding\b'),
        ('ripeness', r'\b(?:ripe(?:ness)?|not\s+(?:yet\s+)?ripe)\b'),
    ],
    'summary_judgment': [
        ('genuine dispute', r'\bgenuine\s+(?:dispute|issue)\s+of\s+(?:material\s+)?fact\b'),
        ('credibility', r'\bcredibility\s+(?:determination|issue)\b'),
        ('inference', r'\b(?:reasonable\s+)?inference(?:s)?\s+(?:in\s+favor|to\s+(?:non-?)?moving)\b'),
    ],
}


def detect_missing_counter_arguments(text: str) -> Tuple[List[ArgumentWeakness], List[str]]:
    """Detect likely counter-arguments that aren't addressed."""
    weaknesses = []
    missing_counters = []

    text_lower = text.lower()

    # Determine claim types present
    claim_indicators = {
        'negligence': r'\bnegligen(?:ce|t)\b',
        'contract': r'\b(?:breach\s+of\s+)?contract|agreement\b',
        'discrimination': r'\bdiscriminat(?:ion|ed|ory)\b',
        'constitutional': r'\b(?:constitutional|due\s+process|equal\s+protection|first\s+amendment)\b',
        'summary_judgment': r'\bsummary\s+judgment\b',
    }

    active_claims = []
    for claim, pattern in claim_indicators.items():
        if re.search(pattern, text_lower):
            active_claims.append(claim)

    # Check for missing counter-arguments
    for claim in active_claims:
        if claim in COMMON_COUNTER_ARGUMENTS:
            for counter_name, counter_pattern in COMMON_COUNTER_ARGUMENTS[claim]:
                if not re.search(counter_pattern, text_lower):
                    # Check if this is a likely counter-argument
                    # (we're being conservative - only flag obvious ones)
                    missing_counters.append(f"{claim}: {counter_name}")

    # Generate weaknesses for the most important missing counters
    if missing_counters and len(text.split()) > 500:
        # Group by claim type
        counter_summary = "; ".join(missing_counters[:5])
        weaknesses.append(ArgumentWeakness(
            weakness_type=WeaknessType.MISSING_COUNTER,
            severity=Severity.MODERATE,
            description=f"Potential counter-arguments not addressed",
            text_excerpt=f"Consider: {counter_summary}",
            suggestion="Address anticipated defenses/counter-arguments to strengthen position",
            location=0,
            stage_relevance=["msj", "trial", "appeal"]
        ))

    return weaknesses, missing_counters


# ============================================================================
# STAGE-BASED SEVERITY ADJUSTMENT
# ============================================================================

def adjust_severity_for_stage(
    weaknesses: List[ArgumentWeakness],
    stage: LitigationStage
) -> List[ArgumentWeakness]:
    """Adjust weakness severity based on litigation stage."""

    # Severity multipliers by stage
    stage_multipliers = {
        LitigationStage.PLEADING: {
            WeaknessType.FACTUAL_GAP: 0.5,  # Less critical at pleading stage
            WeaknessType.WEAK_CITATION: 0.7,
            WeaknessType.CONCLUSORY: 0.5,
        },
        LitigationStage.MOTION_TO_DISMISS: {
            WeaknessType.FACTUAL_GAP: 0.7,
            WeaknessType.MISSING_ELEMENT: 1.5,  # Very important
            WeaknessType.CONCLUSORY: 0.8,
        },
        LitigationStage.SUMMARY_JUDGMENT: {
            WeaknessType.FACTUAL_GAP: 1.5,  # Critical - need record support
            WeaknessType.WEAK_CITATION: 1.3,
            WeaknessType.UNADDRESSED_DISTINCTION: 1.3,
            WeaknessType.DICTA_AS_HOLDING: 1.3,
            WeaknessType.MISSING_COUNTER: 1.2,
        },
        LitigationStage.APPEAL: {
            WeaknessType.UNPUBLISHED_CITE: 1.5,  # Rules on unpublished vary
            WeaknessType.DICTA_AS_HOLDING: 1.5,
            WeaknessType.WEAK_CITATION: 1.3,
            WeaknessType.OVERREACH: 1.2,
        },
    }

    adjusted = []
    for w in weaknesses:
        if stage in stage_multipliers:
            multiplier = stage_multipliers[stage].get(w.weakness_type, 1.0)

            # Upgrade severity if multiplier > 1
            if multiplier >= 1.5 and w.severity == Severity.MODERATE:
                w = ArgumentWeakness(
                    weakness_type=w.weakness_type,
                    severity=Severity.MAJOR,
                    description=w.description + f" [Critical at {stage.value} stage]",
                    text_excerpt=w.text_excerpt,
                    suggestion=w.suggestion,
                    location=w.location,
                    stage_relevance=w.stage_relevance
                )
            elif multiplier <= 0.5 and w.severity == Severity.MAJOR:
                w = ArgumentWeakness(
                    weakness_type=w.weakness_type,
                    severity=Severity.MODERATE,
                    description=w.description + f" [Less critical at {stage.value} stage]",
                    text_excerpt=w.text_excerpt,
                    suggestion=w.suggestion,
                    location=w.location,
                    stage_relevance=w.stage_relevance
                )

        adjusted.append(w)

    return adjusted


# ============================================================================
# MAIN ANALYSIS FUNCTION
# ============================================================================

def analyze_argument_weaknesses(
    text: str,
    strict: bool = False,
    stage: Optional[LitigationStage] = None
) -> WeaknessReport:
    """
    Comprehensive argument weakness analysis.

    Enhanced features:
    - Detects unpublished citations without notation
    - Identifies dicta cited as holding
    - Flags unaddressed factual distinctions
    - Detects conclusory transitions
    - Identifies missing counter-arguments
    - Adjusts severity based on litigation stage

    Args:
        text: Legal argument text to analyze
        strict: If True, flag more potential issues
        stage: Litigation stage for severity adjustment

    Returns:
        WeaknessReport with all detected issues
    """
    report = WeaknessReport()
    report.stage = stage

    # Run all detectors
    report.weaknesses.extend(detect_weak_citations(text))
    report.weaknesses.extend(detect_factual_gaps(text))
    report.weaknesses.extend(detect_overreach(text))
    report.weaknesses.extend(detect_assumptions(text))
    report.weaknesses.extend(detect_hedging(text))
    report.weaknesses.extend(detect_conclusory(text))
    report.weaknesses.extend(detect_counter_vulnerabilities(text))
    report.weaknesses.extend(detect_missing_elements(text))

    # NEW: Enhanced detectors
    report.weaknesses.extend(detect_unpublished_citations(text))
    report.weaknesses.extend(detect_dicta_as_holding(text))
    report.weaknesses.extend(detect_unaddressed_distinctions(text))
    report.weaknesses.extend(detect_conclusory_transitions(text))

    # NEW: Missing counter-argument detection
    counter_weaknesses, missing_counters = detect_missing_counter_arguments(text)
    report.weaknesses.extend(counter_weaknesses)
    report.missing_counter_arguments = missing_counters

    # Check logical fallacies
    for pattern, fallacy_info in LOGICAL_FALLACIES.items():
        for match in re.finditer(pattern, text, re.IGNORECASE):
            report.weaknesses.append(ArgumentWeakness(
                weakness_type=WeaknessType.LOGICAL_FALLACY,
                severity=fallacy_info['severity'],
                description=f"Potential {fallacy_info['name']}",
                text_excerpt=match.group(0),
                suggestion=fallacy_info['suggestion'],
                location=match.start()
            ))

    # NEW: Adjust severity based on litigation stage
    if stage:
        report.weaknesses = adjust_severity_for_stage(report.weaknesses, stage)

    # Sort by severity
    severity_order = {Severity.CRITICAL: 0, Severity.MAJOR: 1, Severity.MODERATE: 2, Severity.MINOR: 3}
    report.weaknesses.sort(key=lambda w: severity_order[w.severity])

    # Count by severity
    report.critical_count = sum(1 for w in report.weaknesses if w.severity == Severity.CRITICAL)
    report.major_count = sum(1 for w in report.weaknesses if w.severity == Severity.MAJOR)

    # Calculate strength score (100 = no weaknesses, 0 = critical issues)
    penalty = (
        report.critical_count * 25 +
        report.major_count * 10 +
        sum(1 for w in report.weaknesses if w.severity == Severity.MODERATE) * 5 +
        sum(1 for w in report.weaknesses if w.severity == Severity.MINOR) * 2
    )
    report.strength_score = max(0, 100 - penalty)

    # Calculate stage-adjusted score
    if stage:
        # Additional penalty for stage-critical issues
        stage_penalty = sum(
            5 for w in report.weaknesses
            if w.stage_relevance and stage.value in w.stage_relevance
        )
        report.stage_adjusted_score = max(0, report.strength_score - stage_penalty)
    else:
        report.stage_adjusted_score = report.strength_score

    # Generate summary
    if report.critical_count > 0:
        report.summary = f"CRITICAL ISSUES FOUND: {report.critical_count} critical, {report.major_count} major weaknesses"
    elif report.major_count > 0:
        report.summary = f"Major weaknesses detected: {report.major_count} issues require attention"
    elif report.weaknesses:
        report.summary = f"Argument generally sound with {len(report.weaknesses)} minor/moderate issues"
    else:
        report.summary = "No significant weaknesses detected"

    # Add missing counter-arguments to summary
    if report.missing_counter_arguments:
        report.summary += f"\n  Potential counter-arguments to address: {len(report.missing_counter_arguments)}"

    return report


# ============================================================================
# OUTPUT FORMATTING
# ============================================================================

def format_weakness_report(report: WeaknessReport) -> str:
    """Format weakness report for display."""
    output = []

    output.append("=" * 70)
    output.append("ARGUMENT WEAKNESS ANALYSIS")
    output.append("=" * 70)
    output.append("")
    output.append(f"STRENGTH SCORE: {report.strength_score:.0f}/100")
    if report.stage:
        output.append(f"STAGE-ADJUSTED SCORE: {report.stage_adjusted_score:.0f}/100 ({report.stage.value})")
    output.append(f"SUMMARY: {report.summary}")
    output.append("")

    if not report.weaknesses:
        output.append("No significant weaknesses detected.")
        return '\n'.join(output)

    output.append(f"Total issues found: {len(report.weaknesses)}")
    output.append(f"  - Critical: {report.critical_count}")
    output.append(f"  - Major: {report.major_count}")
    output.append(f"  - Moderate: {sum(1 for w in report.weaknesses if w.severity == Severity.MODERATE)}")
    output.append(f"  - Minor: {sum(1 for w in report.weaknesses if w.severity == Severity.MINOR)}")
    output.append("")

    # Show missing counter-arguments if any
    if report.missing_counter_arguments:
        output.append("-" * 50)
        output.append("POTENTIAL COUNTER-ARGUMENTS TO ADDRESS:")
        output.append("-" * 50)
        for counter in report.missing_counter_arguments[:7]:
            output.append(f"  [!] {counter}")
        output.append("")

    # Group by severity
    current_severity = None
    for weakness in report.weaknesses:
        if weakness.severity != current_severity:
            current_severity = weakness.severity
            output.append("-" * 50)
            output.append(f"{current_severity.value.upper()} ISSUES")
            output.append("-" * 50)

        output.append(f"\n[{weakness.weakness_type.value.upper()}]")
        output.append(f"Issue: {weakness.description}")
        output.append(f"Text: \"{weakness.text_excerpt[:100]}\"")
        output.append(f"Suggestion: {weakness.suggestion}")
        if weakness.stage_relevance and report.stage:
            if report.stage.value in weakness.stage_relevance:
                output.append(f"  ** Especially important at {report.stage.value} stage **")

    return '\n'.join(output)


def analyze_weaknesses_formatted(text: str) -> str:
    """Main entry point - analyze and return formatted report."""
    report = analyze_argument_weaknesses(text)
    return format_weakness_report(report)


# ============================================================================
# EXPORT FOR MCP
# ============================================================================

def get_weakness_data(text: str) -> Dict:
    """Get weakness analysis as dictionary for programmatic use."""
    report = analyze_argument_weaknesses(text)

    return {
        'strength_score': report.strength_score,
        'summary': report.summary,
        'critical_count': report.critical_count,
        'major_count': report.major_count,
        'total_issues': len(report.weaknesses),
        'weaknesses': [
            {
                'type': w.weakness_type.value,
                'severity': w.severity.value,
                'description': w.description,
                'excerpt': w.text_excerpt,
                'suggestion': w.suggestion
            }
            for w in report.weaknesses
        ]
    }
