"""
Case Structuring Tool - Convert raw case text into structured format for Claude's context.

This module preprocesses legal opinions into a structured markdown format with:
- Labeled sections (holding, rule framework, reasoning, facts, etc.)
- Navigation anchors for quick reference
- Key quotes with usage tags
- Full text preservation for complete reference

The output is optimized for Claude's context window - structured enough to navigate
like code, complete enough to answer any question about the source.
"""

import json
import re
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple, Any
from datetime import datetime

# Import existing extraction tools where helpful
try:
    from extraction_validation import (
        extract_procedural_context,
        ExtractionMeta,
        build_extraction_meta
    )
    EXTRACTION_VALIDATION_AVAILABLE = True
except ImportError:
    EXTRACTION_VALIDATION_AVAILABLE = False

try:
    from enhanced_rules import extract_enhanced_rules, RuleType
    ENHANCED_RULES_AVAILABLE = True
except ImportError:
    ENHANCED_RULES_AVAILABLE = False

try:
    from brief_synthesis import read_case_file
    FILE_READING_AVAILABLE = True
except ImportError:
    FILE_READING_AVAILABLE = False


# ============================================================================
# DATA STRUCTURES
# ============================================================================

@dataclass
class CaseMetadata:
    """Extracted case metadata."""
    case_name: str = ""
    citation: str = ""
    court: str = ""
    date: str = ""
    author: str = ""
    disposition: str = ""
    case_type: str = ""  # civil, criminal, bankruptcy, etc.


@dataclass
class SectionContent:
    """Content for a structured section."""
    detected: bool = False
    content: str = ""
    source_location: str = ""  # Where in original text this came from
    confidence: float = 0.0


@dataclass
class KeyQuote:
    """A significant quote from the case."""
    text: str
    location: str = ""
    use_for: str = ""  # holding, standard, policy, etc.


@dataclass
class StructuredCase:
    """Complete structured case output."""
    metadata: CaseMetadata = field(default_factory=CaseMetadata)
    procedural_posture: SectionContent = field(default_factory=SectionContent)
    issue: SectionContent = field(default_factory=SectionContent)
    holding: SectionContent = field(default_factory=SectionContent)
    rule_framework: SectionContent = field(default_factory=SectionContent)
    reasoning: SectionContent = field(default_factory=SectionContent)
    key_quotes: List[KeyQuote] = field(default_factory=list)
    facts: SectionContent = field(default_factory=SectionContent)
    authority_cited: SectionContent = field(default_factory=SectionContent)
    dissent: SectionContent = field(default_factory=SectionContent)
    concurrence: SectionContent = field(default_factory=SectionContent)
    practical_notes: SectionContent = field(default_factory=SectionContent)
    full_text: str = ""
    navigation: Dict[str, int] = field(default_factory=dict)


# ============================================================================
# METADATA EXTRACTION
# ============================================================================

def extract_metadata(text: str, provided_name: Optional[str] = None,
                    provided_citation: Optional[str] = None) -> CaseMetadata:
    """Extract case metadata from text."""
    metadata = CaseMetadata()

    # Use provided values if available
    if provided_name:
        metadata.case_name = provided_name
    if provided_citation:
        metadata.citation = provided_citation

    # Extract case name if not provided
    if not metadata.case_name:
        metadata.case_name = _extract_case_name(text)

    # Extract citation if not provided
    if not metadata.citation:
        metadata.citation = _extract_citation(text)

    # Extract court from citation or text
    metadata.court = _extract_court(text, metadata.citation)

    # Extract date
    metadata.date = _extract_date(text)

    # Extract author
    metadata.author = _extract_author(text)

    # Extract disposition
    metadata.disposition = _extract_disposition(text)

    return metadata


def _extract_case_name(text: str) -> str:
    """Extract case name from text."""
    # Skip Westlaw/Lexis header noise
    start_pos = 0
    westlaw_markers = ['KeyCite', 'Westlaw', 'Thomson Reuters', 'Superseded by', 'LexisNexis']
    if any(marker in text[:500] for marker in westlaw_markers):
        # Find actual caption - look for party designation pattern
        match = re.search(r'\n([A-Z][A-Z\s,\.&]+,?\s*(?:Petitioner|Plaintiff|Appellant))', text[:2500])
        if match:
            start_pos = match.start()

    search_text = text[start_pos:start_pos+3000]

    # Pattern 1: PARTY, Petitioner(s) v. PARTY, Respondent(s) (formal caption)
    formal_pattern = r'([A-Z][A-Z\s,\.&\']+?),?\s*(?:et al\.?,?\s*)?(?:Petitioner|Plaintiff|Appellant)s?,?\s*[\n\s]*v\.?\s*[\n\s]*([A-Z][A-Z\s,\.&\']+?),?\s*(?:et al\.?,?\s*)?(?:Respondent|Defendant|Appellee)'
    match = re.search(formal_pattern, search_text, re.MULTILINE | re.IGNORECASE)
    if match:
        p1 = _clean_party_name(match.group(1))
        p2 = _clean_party_name(match.group(2))
        return f"{p1} v. {p2}"

    # Pattern 2: Simple "X v. Y" on same line or with newline
    simple_pattern = r'([A-Z][A-Za-z\-\.,\s&\']+?)\s*[\n\s]*v\.?\s*[\n\s]*([A-Z][A-Za-z\-\.,\s&\']+?)(?:\n|,\s*\d|\s+\d+\s+[A-Z])'
    match = re.search(simple_pattern, search_text)
    if match:
        p1 = _clean_party_name(match.group(1))
        p2 = _clean_party_name(match.group(2))
        if len(p1) > 2 and len(p2) > 2:
            return f"{p1} v. {p2}"

    # Pattern 3: In re / Ex parte
    in_re_match = re.search(r'(?:In re|In the Matter of|Ex parte)\s+([A-Z][a-zA-Z\s,\.\']+)', search_text)
    if in_re_match:
        return in_re_match.group(0).strip()

    # Fallback: look for any "X v. Y" pattern
    fallback = re.search(r'([A-Z][A-Za-z\-]+(?:\s+[A-Z][A-Za-z\-]+)*)\s+v\.\s+([A-Z][A-Za-z\-]+(?:\s+[A-Z][A-Za-z\-]+)*)', search_text)
    if fallback:
        return f"{fallback.group(1)} v. {fallback.group(2)}"

    return "[Case Name Not Detected]"


def _clean_party_name(name: str) -> str:
    """Clean up party name by removing extra whitespace, commas, procedural designations."""
    name = re.sub(r'\s+', ' ', name)  # Normalize whitespace
    name = name.strip().rstrip(',').rstrip('.')

    # Remove procedural designations
    procedural = [
        r',?\s*et\s+al\.?',
        r',?\s*Petitioner[s]?',
        r',?\s*Respondent[s]?',
        r',?\s*Plaintiff[s]?',
        r',?\s*Defendant[s]?',
        r',?\s*Appellant[s]?',
        r',?\s*Appellee[s]?',
    ]
    for pattern in procedural:
        name = re.sub(pattern, '', name, flags=re.IGNORECASE)

    # Clean up trailing punctuation
    name = name.strip().rstrip(',').rstrip('.')

    return name.strip()


def _extract_citation(text: str) -> str:
    """Extract primary citation from text."""
    # Look in first portion of text
    first_chunk = text[:5000]

    # Citation patterns in order of preference
    patterns = [
        # U.S. Reports (Supreme Court)
        r'(\d+)\s+U\.S\.\s+(\d+)',
        # S.Ct. (Supreme Court)
        r'(\d+)\s+S\.\s*Ct\.\s+(\d+)',
        # F.4th (Circuit Courts)
        r'(\d+)\s+F\.4th\s+(\d+)',
        # F.3d
        r'(\d+)\s+F\.3d\s+(\d+)',
        # F.2d
        r'(\d+)\s+F\.2d\s+(\d+)',
        # F.Supp.3d (District Courts)
        r'(\d+)\s+F\.\s*Supp\.\s*3d\s+(\d+)',
        # F.Supp.2d
        r'(\d+)\s+F\.\s*Supp\.\s*2d\s+(\d+)',
        # F.Supp.
        r'(\d+)\s+F\.\s*Supp\.\s+(\d+)',
        # State reporters - generic pattern
        r'(\d+)\s+([A-Z][a-z]+\.?\s*(?:2d|3d|4th)?)\s+(\d+)',
    ]

    for pattern in patterns:
        match = re.search(pattern, first_chunk)
        if match:
            return match.group(0).strip()

    return "[Citation Not Detected]"


def _extract_court(text: str, citation: str) -> str:
    """Extract court from citation or text patterns."""
    # Infer from citation
    if "U.S." in citation and "F." not in citation:
        return "Supreme Court of the United States"
    if "S.Ct." in citation or "S. Ct." in citation:
        return "Supreme Court of the United States"
    if "F.4th" in citation or "F.3d" in citation or "F.2d" in citation:
        # Try to find circuit
        circuit_match = re.search(r'(\d+)(?:st|nd|rd|th)\s+Cir(?:cuit|\.)', text[:5000], re.IGNORECASE)
        if circuit_match:
            return f"United States Court of Appeals for the {circuit_match.group(1)}th Circuit"
        return "United States Court of Appeals"
    if "F.Supp" in citation:
        # Try to find district
        district_match = re.search(r'([SNEW]\.[DN]\.\s*[A-Za-z\.]+)', text[:5000])
        if district_match:
            return f"United States District Court, {district_match.group(1)}"
        return "United States District Court"

    # Look for explicit court references
    court_patterns = [
        (r'Supreme Court of the United States', "Supreme Court of the United States"),
        (r'United States Court of Appeals for the (\w+) Circuit', None),
        (r'(\d+)(?:st|nd|rd|th) Circuit', None),
        (r'United States District Court', "United States District Court"),
        (r'Supreme Court of ([A-Z][a-z]+)', None),
        (r'Court of Appeals of ([A-Z][a-z]+)', None),
    ]

    for pattern, default_name in court_patterns:
        match = re.search(pattern, text[:5000], re.IGNORECASE)
        if match:
            if default_name:
                return default_name
            return match.group(0)

    return "[Court Not Detected]"


def _extract_date(text: str) -> str:
    """Extract decision date from text."""
    first_chunk = text[:5000]

    # Common date patterns in legal opinions
    patterns = [
        # "Decided January 15, 2024"
        r'Decided\s+([A-Z][a-z]+\s+\d{1,2},?\s+\d{4})',
        # "January 15, 2024"
        r'([A-Z][a-z]+\s+\d{1,2},?\s+\d{4})',
        # "01/15/2024" or "1/15/2024"
        r'(\d{1,2}/\d{1,2}/\d{4})',
        # "2024-01-15"
        r'(\d{4}-\d{2}-\d{2})',
    ]

    for pattern in patterns:
        match = re.search(pattern, first_chunk)
        if match:
            return match.group(1).strip()

    return "[Date Not Detected]"


def _extract_author(text: str) -> str:
    """Extract opinion author from text."""
    first_chunk = text[:10000]

    # Author patterns - expanded to catch more variations
    patterns = [
        # "Justice [NAME] delivered/wrote/held/announced/filed the opinion"
        r'(?:Justice|Judge)\s+([A-Z][A-Za-z\']+)[,\s]+(?:delivered|wrote|held|writing|announcing|filed|opinion)',
        # "Justice [NAME], writing for the Court"
        r'(?:Justice|Judge)\s+([A-Z][A-Za-z\']+),?\s+writing\s+for',
        # "[NAME], J." or "[NAME], Circuit Judge"
        r'([A-Z][A-Za-z\']+),\s+(?:J\.|C\.J\.|Circuit Judge|District Judge|Chief Judge)',
        # "Opinion by [NAME]"
        r'Opinion\s+(?:of|by)\s+(?:Justice|Judge)?\s*([A-Z][A-Za-z\']+)',
        # "[NAME], J., delivered"
        r'([A-Z][A-Za-z\']+),\s+J\.,\s+delivered',
        # "Chief Justice [NAME]"
        r'Chief\s+Justice\s+([A-Z][A-Za-z\']+)',
        # "Before: [NAME], Circuit Judge" (often in circuit court opinions)
        r'Before:?\s*[^\.]*?([A-Z][A-Za-z\']+),\s+Circuit\s+Judge',
    ]

    for pattern in patterns:
        match = re.search(pattern, first_chunk, re.IGNORECASE)
        if match:
            author = match.group(1).strip()
            # Normalize to title case
            return author.title() if author.isupper() else author

    # Check for per curiam
    if re.search(r'per\s+curiam', first_chunk, re.IGNORECASE):
        return "Per Curiam"

    return "[Author Not Detected]"


def _extract_disposition(text: str) -> str:
    """Extract case disposition from text."""
    # Look in last 20% of document for actual disposition
    search_start = int(len(text) * 0.7)
    last_chunk = text[search_start:]

    # Priority order: compound dispositions first, then simple ones
    # More specific patterns checked first
    patterns = [
        # Compound dispositions (most specific)
        (r'(?:judgment\s+)?(?:is\s+)?(?:hereby\s+)?affirmed\s+in\s+part\s+and\s+reversed\s+in\s+part', "Affirmed in Part, Reversed in Part"),
        (r'(?:judgment\s+)?(?:is\s+)?(?:hereby\s+)?reversed\s+in\s+part\s+and\s+affirmed\s+in\s+part', "Affirmed in Part, Reversed in Part"),
        (r'(?:judgment\s+)?(?:is\s+)?(?:hereby\s+)?vacated\s+and\s+remanded', "Vacated and Remanded"),
        (r'(?:judgment\s+)?(?:is\s+)?(?:hereby\s+)?reversed\s+and\s+remanded', "Reversed and Remanded"),
        (r'(?:judgment\s+)?(?:is\s+)?(?:hereby\s+)?affirmed\s+in\s+part', "Affirmed in Part"),
        (r'(?:judgment\s+)?(?:is\s+)?(?:hereby\s+)?reversed\s+in\s+part', "Reversed in Part"),
        # Judgment language (authoritative)
        (r'judgment\s+(?:of\s+the\s+\w+\s+(?:court\s+)?)?(?:is\s+)?(?:hereby\s+)?(affirmed|reversed|vacated|remanded)', None),
        # "We" language
        (r'we\s+(affirm|reverse|vacate|remand)', None),
        # Simple "is" patterns
        (r'(?:is\s+)?(?:hereby\s+)?affirmed', "Affirmed"),
        (r'(?:is\s+)?(?:hereby\s+)?reversed', "Reversed"),
        (r'(?:is\s+)?(?:hereby\s+)?vacated', "Vacated"),
        (r'(?:is\s+)?(?:hereby\s+)?remanded', "Remanded"),
        (r'(?:is\s+)?(?:hereby\s+)?dismissed', "Dismissed"),
        (r'(?:is\s+)?(?:hereby\s+)?denied', "Denied"),
        (r'(?:is\s+)?(?:hereby\s+)?granted', "Granted"),
    ]

    for pattern, disposition in patterns:
        match = re.search(pattern, last_chunk, re.IGNORECASE)
        if match:
            if disposition is None:
                # Extract from capture group and capitalize
                return match.group(1).capitalize() if match.lastindex else match.group(0).capitalize()
            return disposition

    return "[Disposition Not Detected]"


# ============================================================================
# SECTION DETECTION & EXTRACTION
# ============================================================================

def extract_procedural_posture(text: str) -> SectionContent:
    """Extract procedural posture section."""
    content = SectionContent()

    # Signals for procedural posture
    signals = [
        r'(?:This\s+)?appeal\s+(?:comes|arises)\s+from',
        r'petition\s+for\s+(?:certiorari|review)',
        r'(?:The\s+)?district\s+court\s+(?:granted|denied)',
        r'(?:We|This Court)\s+review',
        r'procedural\s+(?:history|background|posture)',
        r'(?:I|II|III)?\.?\s*(?:Background|Procedural History)',
        r'(?:comes|came)\s+before\s+(?:us|this\s+Court)',
    ]

    # Find section containing procedural information
    for signal in signals:
        match = re.search(signal, text, re.IGNORECASE)
        if match:
            # Extract surrounding context
            start = max(0, match.start() - 100)
            end = min(len(text), match.end() + 2000)
            chunk = text[start:end]

            # Find paragraph boundaries
            paragraphs = chunk.split('\n\n')
            relevant_paras = []

            for para in paragraphs:
                if any(re.search(s, para, re.IGNORECASE) for s in signals):
                    relevant_paras.append(para.strip())
                elif relevant_paras and len(para.strip()) > 50:
                    # Include following paragraph if it continues the context
                    relevant_paras.append(para.strip())
                    if len(relevant_paras) >= 3:
                        break

            if relevant_paras:
                content.detected = True
                content.confidence = 0.8

                # Format the extracted content
                lines = []
                lines.append(_extract_procedural_details(text))
                lines.append("")
                lines.append("Procedural Summary:")
                for para in relevant_paras[:2]:
                    lines.append(para[:500] + ("..." if len(para) > 500 else ""))

                content.content = "\n".join(lines)
                content.source_location = f"Near position {match.start()}"
                return content

    content.detected = False
    content.content = "[NOT DETECTED - No explicit procedural history found. See FULL_TEXT and search for 'appeal from' or 'district court' language.]"
    return content


def _extract_procedural_details(text: str) -> str:
    """Extract specific procedural details."""
    details = []

    # Lower court
    lower_court_patterns = [
        r'(?:the\s+)?district\s+court\s+(?:for\s+the\s+)?([^,\.]+)',
        r'(?:the\s+)?(?:trial|lower)\s+court',
        r'(?:the\s+)?(?:bankruptcy|tax)\s+court',
    ]
    for pattern in lower_court_patterns:
        match = re.search(pattern, text[:10000], re.IGNORECASE)
        if match:
            details.append(f"- Lower court: {match.group(0).strip()}")
            break

    # Lower court decision
    decision_patterns = [
        r'(?:district|trial|lower)\s+court\s+(?:granted|denied|dismissed|entered\s+judgment)',
        r'(?:granted|denied)\s+(?:summary\s+judgment|motion\s+to\s+dismiss)',
    ]
    for pattern in decision_patterns:
        match = re.search(pattern, text[:10000], re.IGNORECASE)
        if match:
            details.append(f"- Lower court decision: {match.group(0).strip()}")
            break

    # Standard of review
    review_patterns = [
        (r'de\s+novo', "de novo"),
        (r'abuse\s+of\s+discretion', "abuse of discretion"),
        (r'clear\s+error', "clear error"),
        (r'substantial\s+evidence', "substantial evidence"),
        (r'plain\s+error', "plain error"),
        (r'arbitrary\s+and\s+capricious', "arbitrary and capricious"),
    ]
    for pattern, standard in review_patterns:
        if re.search(pattern, text[:15000], re.IGNORECASE):
            details.append(f"- Standard of review: {standard}")
            break

    return "\n".join(details) if details else "- [Procedural details not clearly stated]"


def extract_issue(text: str) -> SectionContent:
    """Extract the legal issue presented."""
    content = SectionContent()

    # Explicit issue signals
    signals = [
        r'(?:The\s+)?question\s+(?:presented|before\s+us)\s+is',
        r'(?:The\s+)?issue\s+(?:is|before\s+us)\s+(?:is\s+)?whether',
        r'(?:We|This\s+Court)\s+must\s+(?:decide|determine)\s+(?:whether|if)',
        r'(?:This\s+)?case\s+(?:requires|asks)\s+us\s+to\s+(?:decide|determine)',
        r'(?:The\s+)?(?:sole|primary|central)\s+(?:issue|question)',
        r'(?:We|The Court)\s+(?:granted\s+certiorari|took\s+this\s+appeal)\s+to\s+(?:decide|determine|resolve)',
    ]

    for signal in signals:
        match = re.search(signal, text, re.IGNORECASE)
        if match:
            # Extract the question
            start = match.start()
            end = min(len(text), start + 500)
            chunk = text[start:end]

            # Find sentence boundary
            sentence_end = re.search(r'[\.?](?:\s|$)', chunk)
            if sentence_end:
                question = chunk[:sentence_end.end()].strip()
                content.detected = True
                content.confidence = 0.9
                content.content = question
                content.source_location = f"Near position {start}"
                return content

    # If no explicit issue found, try to synthesize from holding
    holding_match = re.search(r'(?:we|this\s+court)\s+hold(?:s)?\s+that\s+([^\.]+\.)', text, re.IGNORECASE)
    if holding_match:
        holding = holding_match.group(1)
        # Invert to question form
        content.detected = True
        content.confidence = 0.6
        content.content = f"[Synthesized from holding]: Whether {holding.lower().rstrip('.')}"
        content.source_location = "Derived from holding"
        return content

    content.detected = False
    content.content = "[NOT DETECTED - No explicit issue statement found. See FULL_TEXT and search for 'question presented' or 'we must decide' language.]"
    return content


def extract_holding(text: str) -> SectionContent:
    """Extract the court's holding."""
    content = SectionContent()

    # Holding signals - explicit
    holding_patterns = [
        r'(?:We|This\s+Court|The\s+Court)\s+hold(?:s)?\s+that\s+([^\.]+(?:\.[^\.]+)?\.)',
        r'(?:We|This\s+Court)\s+conclude(?:s)?\s+that\s+([^\.]+(?:\.[^\.]+)?\.)',
        r'(?:We|This\s+Court)\s+(?:hereby\s+)?(?:hold|rule|decide)\s*:\s*([^\.]+\.)',
        r'(?:Accordingly|Therefore|Thus),?\s+(?:we|the\s+Court)\s+(?:hold|conclude|find)\s+(?:that\s+)?([^\.]+\.)',
    ]

    for pattern in holding_patterns:
        match = re.search(pattern, text, re.IGNORECASE)
        if match:
            holding_text = match.group(1).strip() if match.lastindex else match.group(0).strip()
            content.detected = True
            content.confidence = 0.9

            # Get disposition
            disposition = _extract_disposition(text)

            content.content = f"{holding_text}\n\nDisposition: {disposition}"
            content.source_location = f"Near position {match.start()}"
            return content

    # Try to find judgment language near the end
    last_section = text[-8000:]
    judgment_patterns = [
        r'(?:The\s+)?judgment\s+(?:of\s+the\s+[^\.]+\s+)?is\s+(\w+)',
        r'(?:We|The Court)\s+(affirm|reverse|vacate|remand)',
    ]

    for pattern in judgment_patterns:
        match = re.search(pattern, last_section, re.IGNORECASE)
        if match:
            # Look for surrounding context
            start = max(0, match.start() - 200)
            end = min(len(last_section), match.end() + 100)
            context = last_section[start:end].strip()

            content.detected = True
            content.confidence = 0.7
            content.content = f"[Extracted from conclusion]:\n{context}"
            content.source_location = "End of opinion"
            return content

    content.detected = False
    content.content = "[NOT DETECTED - No explicit holding found. See FULL_TEXT and search for 'we hold that' or judgment language near the end.]"
    return content


def extract_rule_framework(text: str) -> SectionContent:
    """Extract the legal rule/standard framework."""
    content = SectionContent()

    # Try to use enhanced_rules if available
    if ENHANCED_RULES_AVAILABLE:
        try:
            rules = extract_enhanced_rules(text, "", "")
            if rules:
                lines = []

                # Find primary rule
                primary_rules = [r for r in rules if r.level.value in ("primary", "supreme_court")]
                if primary_rules:
                    lines.append("PRIMARY RULE:")
                    lines.append(f"> {primary_rules[0].text}")
                    lines.append("")

                # Collect elements and factors
                elements = []
                factors = []
                for r in rules:
                    if r.elements:
                        elements.extend(r.elements)
                    if r.factors:
                        factors.extend(r.factors)

                if elements:
                    lines.append("ELEMENTS:")
                    for i, elem in enumerate(elements[:10], 1):
                        lines.append(f"{i}. {elem}")
                    lines.append("")

                if factors:
                    lines.append("FACTORS:")
                    for i, factor in enumerate(factors[:10], 1):
                        lines.append(f"{i}. {factor}")
                    lines.append("")

                # Determine test type
                test_types = [r.rule_type.value for r in rules if hasattr(r.rule_type, 'value')]
                if "elements" in test_types:
                    lines.append("STANDARD:")
                    lines.append("- Type: Elements test")
                elif "factors" in test_types or "multi_factor" in test_types:
                    lines.append("STANDARD:")
                    lines.append("- Type: Multi-factor test")
                elif "balancing" in test_types:
                    lines.append("STANDARD:")
                    lines.append("- Type: Balancing test")
                elif "totality" in test_types:
                    lines.append("STANDARD:")
                    lines.append("- Type: Totality of circumstances")
                elif "bright_line" in test_types:
                    lines.append("STANDARD:")
                    lines.append("- Type: Bright-line rule")

                if lines:
                    content.detected = True
                    content.confidence = 0.85
                    content.content = "\n".join(lines)
                    return content
        except Exception:
            pass

    # Manual extraction fallback
    lines = []

    # Look for rule/standard language
    rule_patterns = [
        r'(?:the|a)\s+(?:applicable|governing|relevant)\s+(?:standard|test|rule)\s+(?:is|requires)',
        r'(?:to\s+(?:establish|prove|show)|(?:plaintiff|defendant)\s+must\s+(?:demonstrate|show|prove))',
        r'(?:requires?\s+(?:a\s+)?showing\s+(?:of|that))',
        r'(?:under|pursuant\s+to)\s+[^,]+,\s+(?:a\s+)?(?:plaintiff|party|claimant)\s+must',
        r'(?:the\s+)?elements?\s+(?:of|for)\s+[^:]+(?:are|is|include)',
    ]

    for pattern in rule_patterns:
        match = re.search(pattern, text, re.IGNORECASE)
        if match:
            start = match.start()
            end = min(len(text), start + 1500)
            chunk = text[start:end]

            # Extract the rule statement
            lines.append("PRIMARY RULE:")

            # Find complete sentence(s)
            sentences = re.split(r'(?<=[\.!?])\s+', chunk)
            rule_sentences = []
            for sent in sentences[:3]:
                if len(sent) > 30:
                    rule_sentences.append(sent)

            if rule_sentences:
                lines.append(f"> {' '.join(rule_sentences)}")

            break

    # Look for numbered elements
    element_pattern = r'(?:\(\d\)|\d\.)\s*([A-Za-z][^;\.]+)[;\.]'
    element_matches = re.findall(element_pattern, text[:15000])
    if element_matches and len(element_matches) >= 2:
        lines.append("")
        lines.append("SUB-RULES/ELEMENTS:")
        for i, elem in enumerate(element_matches[:8], 1):
            lines.append(f"{i}. {elem.strip()}")

    # Look for burden of proof
    burden_patterns = [
        (r'preponderance\s+of\s+(?:the\s+)?evidence', "Preponderance of the evidence"),
        (r'clear\s+and\s+convincing', "Clear and convincing evidence"),
        (r'beyond\s+(?:a\s+)?reasonable\s+doubt', "Beyond reasonable doubt"),
        (r'(?:plaintiff|defendant|movant)\s+bears\s+the\s+burden', None),
    ]

    for pattern, burden_name in burden_patterns:
        if re.search(pattern, text, re.IGNORECASE):
            lines.append("")
            lines.append("STANDARD:")
            if burden_name:
                lines.append(f"- Burden: {burden_name}")
            else:
                match = re.search(pattern, text, re.IGNORECASE)
                if match:
                    lines.append(f"- Burden: {match.group(0)}")
            break

    if lines:
        content.detected = True
        content.confidence = 0.7
        content.content = "\n".join(lines)
    else:
        content.detected = False
        content.content = "[NOT DETECTED - No clear legal standard found. See FULL_TEXT and search for 'the standard is' or 'must establish' language.]"

    return content


def extract_reasoning(text: str) -> SectionContent:
    """Extract the court's reasoning structure."""
    content = SectionContent()

    # Look for section markers
    section_patterns = [
        r'(?:^|\n)\s*(I{1,3}|IV|V|VI|VII|VIII|IX|X)\.?\s+([A-Z][^\n]+)',
        r'(?:^|\n)\s*([A-D])\.?\s+([A-Z][^\n]+)',
        r'(?:^|\n)\s*(?:First|Second|Third|Fourth|Fifth),?\s+',
        r'(?:^|\n)\s*(?:We\s+(?:begin|first|next|now|turn|finally))',
        r'(?:^|\n)\s*(?:The\s+(?:first|second|third|next)\s+(?:issue|question|argument))',
    ]

    sections = []

    # Find Roman numeral sections
    roman_matches = re.finditer(r'(?:^|\n)\s*(I{1,3}|IV|V|VI|VII|VIII|IX|X)\.?\s*\n?\s*([A-Z][^\n]+)', text)
    for match in roman_matches:
        numeral = match.group(1)
        title = match.group(2).strip()[:80]
        position = match.start()

        # Extract summary of this section
        section_text = text[position:position+3000]
        summary = _summarize_section(section_text)

        sections.append({
            "label": f"SECTION {numeral}",
            "topic": title,
            "summary": summary,
            "position": position
        })

    # If no Roman numerals, look for transitional phrases
    if not sections:
        transition_patterns = [
            (r'We\s+(?:begin|start)\s+(?:by|with|our\s+analysis)', "Introduction/Framework"),
            (r'We\s+first\s+(?:consider|address|examine)', "First Issue"),
            (r'(?:We\s+)?(?:next|then)\s+(?:consider|turn\s+to|address)', "Second Issue"),
            (r'(?:We\s+)?(?:finally|lastly)\s+(?:consider|address|turn)', "Final Issue"),
            (r'(?:The\s+)?(?:plaintiff|defendant|appellant|appellee)\s+argues', "Party's Argument"),
        ]

        for pattern, label in transition_patterns:
            for match in re.finditer(pattern, text, re.IGNORECASE):
                position = match.start()
                section_text = text[position:position+2000]
                summary = _summarize_section(section_text)
                sections.append({
                    "label": label,
                    "topic": match.group(0).strip(),
                    "summary": summary,
                    "position": position
                })

    if sections:
        lines = []
        for section in sections[:10]:  # Limit to 10 sections
            lines.append(f"{section['label']}: {section['topic']}")
            lines.append(f"- Court's argument: {section['summary']}")
            lines.append("")

        # Look for rejected arguments
        rejected = _find_rejected_arguments(text)
        if rejected:
            lines.append("REJECTED ARGUMENTS:")
            for arg in rejected[:5]:
                lines.append(f"- {arg}")

        content.detected = True
        content.confidence = 0.75
        content.content = "\n".join(lines)
    else:
        content.detected = False
        content.content = "[NOT DETECTED - No clear section structure found. See FULL_TEXT for full analysis.]"

    return content


def _summarize_section(section_text: str) -> str:
    """Generate a brief summary of a section."""
    # Look for conclusion-like sentences
    conclusion_patterns = [
        r'(?:We|The Court)\s+(?:conclude|hold|find|determine)(?:s)?\s+that\s+([^\.]+\.)',
        r'(?:Accordingly|Therefore|Thus),\s+([^\.]+\.)',
        r'(?:We|The Court)\s+(?:agree|disagree)(?:s)?\s+(?:with|that)\s+([^\.]+\.)',
    ]

    for pattern in conclusion_patterns:
        match = re.search(pattern, section_text, re.IGNORECASE)
        if match:
            return match.group(1).strip()[:200]

    # Fallback: first substantive sentence
    sentences = re.split(r'(?<=[\.!?])\s+', section_text)
    for sent in sentences:
        if len(sent) > 50 and not re.match(r'^[IVX]+\.?\s*$', sent):
            return sent[:200].strip() + ("..." if len(sent) > 200 else "")

    return "[Summary not extractable]"


def _find_rejected_arguments(text: str) -> List[str]:
    """Find arguments the court rejected."""
    rejected = []

    patterns = [
        r'(?:We|The Court)\s+reject(?:s)?\s+(?:the\s+)?(?:argument|contention|claim)\s+that\s+([^\.]+\.)',
        r'(?:We|This Court)\s+(?:disagree|are not persuaded)(?:s)?\s+(?:with|that|by)\s+([^\.]+\.)',
        r'(?:This|That)\s+argument\s+(?:fails|is\s+without\s+merit|is\s+unavailing)',
        r'(?:We|The Court)\s+(?:find|conclude)(?:s)?\s+(?:this|that)\s+argument\s+(?:unpersuasive|meritless)',
    ]

    for pattern in patterns:
        for match in re.finditer(pattern, text, re.IGNORECASE):
            rejected.append(match.group(0).strip()[:150])

    return rejected


def extract_key_quotes(text: str, metadata: CaseMetadata) -> List[KeyQuote]:
    """Extract significant quotes from the case."""
    quotes = []

    # Priority quote patterns
    quote_patterns = [
        # Holding language
        (r'(?:We|This\s+Court)\s+hold(?:s)?\s+that\s+[^\.]+\.', "holding"),
        # Standard articulation
        (r'(?:The|A)\s+(?:applicable|governing)\s+(?:standard|test|rule)\s+(?:is|requires)\s+[^\.]+\.', "standard"),
        # Policy rationale
        (r'(?:The\s+purpose|This\s+rule\s+serves|Congress\s+intended)\s+[^\.]+\.', "policy"),
        # Key definitions
        (r'(?:We|The Court)\s+define(?:s)?\s+[^\.]+\.', "definition"),
        # Important clarifications
        (r'(?:This\s+means|In\s+other\s+words|Put\s+differently)\s+[^\.]+\.', "clarification"),
    ]

    seen_quotes = set()

    for pattern, use_for in quote_patterns:
        for match in re.finditer(pattern, text, re.IGNORECASE):
            quote_text = match.group(0).strip()

            # Skip duplicates or very short quotes
            if quote_text in seen_quotes or len(quote_text) < 40:
                continue

            seen_quotes.add(quote_text)

            # Try to find page reference
            location = _estimate_location(text, match.start())

            quotes.append(KeyQuote(
                text=quote_text,
                location=location,
                use_for=use_for
            ))

            if len(quotes) >= 10:
                break

        if len(quotes) >= 10:
            break

    return quotes


def _estimate_location(text: str, position: int) -> str:
    """Estimate location in text for a quote."""
    # Calculate approximate percentage through document
    pct = int((position / len(text)) * 100)

    # Try to find nearby section marker
    preceding = text[max(0, position-500):position]
    section_match = re.search(r'(I{1,3}|IV|V|VI|VII|VIII|IX|X)\.', preceding)
    if section_match:
        return f"Section {section_match.group(1)}"

    return f"~{pct}% through opinion"


def extract_facts(text: str) -> SectionContent:
    """Extract facts section."""
    content = SectionContent()

    lines = []

    # Look for parties
    parties = _extract_parties(text)
    if parties:
        lines.append("PARTIES:")
        for party in parties:
            lines.append(f"- {party}")
        lines.append("")

    # Look for factual background section
    fact_patterns = [
        r'(?:I\.?\s*)?(?:Background|Facts|Factual\s+Background|Statement\s+of\s+Facts)',
        r'(?:The\s+)?(?:relevant|material|underlying)\s+facts\s+(?:are|follow)',
    ]

    for pattern in fact_patterns:
        match = re.search(pattern, text, re.IGNORECASE)
        if match:
            start = match.start()
            # Find end of facts section (next major section or ~3000 chars)
            end_match = re.search(r'\n\s*(II\.?|Discussion|Analysis|Standard|Legal\s+Framework)', text[start+100:start+5000], re.IGNORECASE)
            end = start + 100 + end_match.start() if end_match else start + 3000

            fact_text = text[start:end].strip()

            lines.append("FACTUAL BACKGROUND:")
            lines.append(fact_text[:2000] + ("..." if len(fact_text) > 2000 else ""))
            break

    # Try to identify key facts
    key_facts = _extract_key_facts(text)
    if key_facts:
        lines.append("")
        lines.append("KEY FACTS (for analogy/distinction):")
        for fact in key_facts[:8]:
            lines.append(f"- {fact}")

    if lines:
        content.detected = True
        content.confidence = 0.7
        content.content = "\n".join(lines)
    else:
        content.detected = False
        content.content = "[NOT DETECTED - No clear facts section found. See FULL_TEXT.]"

    return content


def _extract_parties(text: str) -> List[str]:
    """Extract party information."""
    parties = []

    # Look for party labels
    patterns = [
        r'(?:Plaintiff|Appellant)(?:-(?:Appellee|Appellant))?\s*[,:]?\s*([A-Z][^,\n]+)',
        r'(?:Defendant|Appellee)(?:-(?:Appellant|Appellee))?\s*[,:]?\s*([A-Z][^,\n]+)',
        r'(?:Petitioner)\s*[,:]?\s*([A-Z][^,\n]+)',
        r'(?:Respondent)\s*[,:]?\s*([A-Z][^,\n]+)',
    ]

    for pattern in patterns:
        match = re.search(pattern, text[:5000])
        if match:
            parties.append(match.group(0).strip()[:100])

    return parties


def _extract_key_facts(text: str) -> List[str]:
    """Extract facts that appear material to the outcome."""
    key_facts = []

    # Look for facts mentioned near holding/conclusion
    conclusion_area = text[-10000:]

    # Facts often referenced in conclusion
    fact_signals = [
        r'(?:here|in\s+this\s+case),?\s+([^\.]+\.)',
        r'(?:the\s+)?(?:undisputed|record)\s+(?:facts?\s+)?(?:show|indicate|establish)\s+that\s+([^\.]+\.)',
        r'(?:because|since)\s+([^,\.]+),',
    ]

    for pattern in fact_signals:
        for match in re.finditer(pattern, conclusion_area, re.IGNORECASE):
            fact = match.group(1).strip()
            if 20 < len(fact) < 200:
                key_facts.append(fact)

    return key_facts[:10]


def extract_authority_cited(text: str) -> SectionContent:
    """Extract key authorities cited."""
    content = SectionContent()

    lines = []

    # Find frequently cited cases
    case_citations = re.findall(r'([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\s+v\.?\s+[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)', text)

    # Count occurrences
    from collections import Counter
    case_counts = Counter(case_citations)
    top_cases = case_counts.most_common(10)

    if top_cases:
        lines.append("RELIED UPON:")
        for case, count in top_cases:
            if count >= 2:
                # Try to find what the case was used for
                context = _find_citation_context(text, case)
                lines.append(f"- {case}: {context}")

    # Look for distinguished cases
    distinguished = _find_distinguished_cases(text)
    if distinguished:
        lines.append("")
        lines.append("DISTINGUISHED:")
        for case, reason in distinguished[:5]:
            lines.append(f"- {case}: {reason}")

    # Find statutes
    statutes = _find_statutes(text)
    if statutes:
        lines.append("")
        lines.append("STATUTES:")
        for statute in statutes[:5]:
            lines.append(f"- {statute}")

    if lines:
        content.detected = True
        content.confidence = 0.8
        content.content = "\n".join(lines)
    else:
        content.detected = False
        content.content = "[No significant authorities detected]"

    return content


def _find_citation_context(text: str, case_name: str) -> str:
    """Find what a case was cited for."""
    pattern = rf'{re.escape(case_name)}[^\.]*\([^)]*\)[^\.]*\.?'
    match = re.search(pattern, text)
    if match:
        context = match.group(0)
        # Extract the proposition
        if "holding" in context.lower():
            return "holding"
        if "standard" in context.lower():
            return "standard"
        if "established" in context.lower():
            return "established rule"
    return "[purpose unclear]"


def _find_distinguished_cases(text: str) -> List[Tuple[str, str]]:
    """Find cases that were distinguished."""
    distinguished = []

    patterns = [
        r'([A-Z][a-z]+\s+v\.?\s+[A-Z][a-z]+)\s+(?:is\s+)?distinguishable\s+because\s+([^\.]+\.)',
        r'(?:Unlike|In\s+contrast\s+to)\s+([A-Z][a-z]+\s+v\.?\s+[A-Z][a-z]+)[,\s]+([^\.]+\.)',
    ]

    for pattern in patterns:
        for match in re.finditer(pattern, text, re.IGNORECASE):
            case = match.group(1)
            reason = match.group(2)[:100]
            distinguished.append((case, reason))

    return distinguished


def _find_statutes(text: str) -> List[str]:
    """Find statutes cited in the text."""
    statutes = set()

    patterns = [
        r'\d+\s+U\.S\.C\.?\s+§?\s*\d+',
        r'\d+\s+C\.F\.R\.?\s+§?\s*[\d\.]+',
        r'Fed\.\s*R\.\s*(?:Civ|Crim|App|Evid)\.\s*P\.\s*\d+',
        r'Rule\s+\d+(?:\([a-z]\))?',
    ]

    for pattern in patterns:
        for match in re.finditer(pattern, text):
            statutes.add(match.group(0))

    return list(statutes)[:10]


def extract_dissent(text: str) -> SectionContent:
    """Extract dissent if present."""
    content = SectionContent()

    # Look for dissent markers
    dissent_patterns = [
        r'([A-Z][A-Za-z\']+),?\s+(?:J\.|Justice),?\s+(?:dissenting|filed\s+a\s+dissent)',
        r'(?:I\s+)?(?:respectfully\s+)?dissent',
        r'(?:DISSENT|Dissenting\s+Opinion)',
    ]

    for pattern in dissent_patterns:
        match = re.search(pattern, text)
        if match:
            # Find dissent section
            start = match.start()
            dissent_text = text[start:start+5000]

            lines = []

            # Extract author
            author_match = re.search(r'([A-Z][A-Za-z\']+),?\s+(?:J\.|Justice)', dissent_text)
            if author_match:
                lines.append(f"AUTHOR: {author_match.group(1)}")

            # Look for joined by
            joined_match = re.search(r'(?:joined\s+by|with\s+whom)\s+([^,\.]+)', dissent_text, re.IGNORECASE)
            if joined_match:
                lines.append(f"JOINED BY: {joined_match.group(1)}")

            lines.append("")
            lines.append("CORE DISAGREEMENT:")

            # Extract key point of dissent
            disagree_patterns = [
                r'(?:I\s+would|The\s+majority\s+errs|I\s+disagree)\s+([^\.]+\.)',
                r'(?:The\s+)?(?:correct|proper)\s+(?:rule|standard|approach)\s+(?:is|should\s+be)\s+([^\.]+\.)',
            ]

            for dp in disagree_patterns:
                dm = re.search(dp, dissent_text, re.IGNORECASE)
                if dm:
                    lines.append(dm.group(0).strip())
                    break

            # Extract key quote
            lines.append("")
            lines.append("KEY DISSENT QUOTES:")
            quote_match = re.search(r'(?:I\s+would|The\s+majority)[^\.]+\.', dissent_text, re.IGNORECASE)
            if quote_match:
                lines.append(f"> \"{quote_match.group(0).strip()}\"")

            content.detected = True
            content.confidence = 0.85
            content.content = "\n".join(lines)
            return content

    content.detected = False
    content.content = "None"
    return content


def extract_concurrence(text: str) -> SectionContent:
    """Extract concurrence if present."""
    content = SectionContent()

    # Look for concurrence markers
    concur_patterns = [
        r'([A-Z][A-Za-z\']+),?\s+(?:J\.|Justice),?\s+(?:concurring)',
        r'(?:I\s+)?concur(?:\s+in\s+(?:the\s+)?(?:judgment|result))?',
        r'(?:CONCURRENCE|Concurring\s+Opinion)',
    ]

    for pattern in concur_patterns:
        match = re.search(pattern, text)
        if match:
            start = match.start()
            concur_text = text[start:start+3000]

            lines = []

            # Extract author
            author_match = re.search(r'([A-Z][A-Za-z\']+),?\s+(?:J\.|Justice)', concur_text)
            if author_match:
                lines.append(f"AUTHOR: {author_match.group(1)}")

            # Determine type
            if re.search(r'concur(?:ring)?\s+in\s+(?:the\s+)?(?:judgment|result)\s+only', concur_text, re.IGNORECASE):
                lines.append("TYPE: Concurring in judgment only")
            else:
                lines.append("TYPE: Concurring")

            lines.append("")
            lines.append("ADDITIONAL POINTS:")

            # Extract key point
            point_match = re.search(r'(?:I\s+write\s+(?:separately\s+)?to|I\s+would\s+(?:add|note))\s+([^\.]+\.)', concur_text, re.IGNORECASE)
            if point_match:
                lines.append(point_match.group(0).strip())

            content.detected = True
            content.confidence = 0.8
            content.content = "\n".join(lines)
            return content

    content.detected = False
    content.content = "None"
    return content


def generate_practical_notes(metadata: CaseMetadata, holding: SectionContent) -> SectionContent:
    """Generate practical notes for using the case."""
    content = SectionContent()
    lines = []

    # Binding/persuasive analysis
    court = metadata.court.lower()
    if "supreme court of the united states" in court:
        lines.append("BINDING IN: All federal courts, persuasive in state courts")
    elif "court of appeals" in court:
        # Try to extract circuit
        circuit_match = re.search(r'(\d+)(?:st|nd|rd|th)', metadata.court)
        if circuit_match:
            circuit = circuit_match.group(1)
            lines.append(f"BINDING IN: {circuit}th Circuit district courts")
            lines.append("PERSUASIVE IN: Other circuits, state courts")
        else:
            lines.append("BINDING IN: District courts within this circuit")
            lines.append("PERSUASIVE IN: Other circuits")
    elif "district court" in court:
        lines.append("BINDING IN: Not binding precedent (trial court decision)")
        lines.append("PERSUASIVE IN: Same district, other districts")

    lines.append("")
    lines.append("USE THIS CASE WHEN:")
    lines.append("- [Facts align with your client's situation]")
    lines.append("- [You need the rule/standard articulated here]")
    lines.append("- [Jurisdiction where this is binding precedent]")

    lines.append("")
    lines.append("DISTINGUISH THIS CASE WHEN:")
    lines.append("- [Key facts differ materially]")
    lines.append("- [Different procedural posture]")
    lines.append("- [Different legal issue being addressed]")

    lines.append("")
    lines.append("POTENTIAL WEAKNESSES:")
    lines.append("- [Note any limitations in holding]")
    lines.append("- [Circuit splits or contrary authority]")
    lines.append("- [Narrow scope of ruling]")

    content.detected = True
    content.confidence = 0.5  # Lower confidence - these are templates
    content.content = "\n".join(lines)
    return content


# ============================================================================
# MAIN STRUCTURING FUNCTION
# ============================================================================

def structure_case_for_context(
    case_text: Optional[str] = None,
    case_name: Optional[str] = None,
    citation: Optional[str] = None,
    include_full_text: bool = True,
    file_path: Optional[str] = None
) -> Dict[str, Any]:
    """
    Convert raw case text into structured format optimized for Claude's context.

    Args:
        case_text: Full text of the case opinion (required if file_path not provided)
        case_name: Case name if known (will detect if not provided)
        citation: Full citation if known (will detect if not provided)
        include_full_text: Whether to append complete original text
        file_path: Path to case file (PDF, DOCX, TXT) - alternative to case_text

    Returns:
        Dict with _meta and structured case output
    """
    # Handle file path input
    file_type = None
    if file_path:
        if not FILE_READING_AVAILABLE:
            return {
                "_meta": {"error": "File reading not available - brief_synthesis module not found"},
                "structured_output": "",
                "metadata": {}
            }
        try:
            case_text, file_type = read_case_file(file_path)
            if not case_text or len(case_text.strip()) < 100:
                return {
                    "_meta": {"error": f"Could not read file or file too short: {file_path}"},
                    "structured_output": "",
                    "metadata": {}
                }
        except Exception as e:
            return {
                "_meta": {"error": f"Error reading file {file_path}: {str(e)}"},
                "structured_output": "",
                "metadata": {}
            }

    if not case_text:
        return {
            "_meta": {"error": "Either case_text or file_path must be provided"},
            "structured_output": "",
            "metadata": {}
        }

    targets_found = []
    targets_missing = []
    confidence_scores = {}

    # Step 1: Extract metadata
    metadata = extract_metadata(case_text, case_name, citation)

    if metadata.case_name and "[" not in metadata.case_name:
        targets_found.append("case_name")
        confidence_scores["case_name"] = 0.9
    else:
        targets_missing.append("case_name")

    if metadata.citation and "[" not in metadata.citation:
        targets_found.append("citation")
        confidence_scores["citation"] = 0.9
    else:
        targets_missing.append("citation")

    # Step 2: Extract sections
    structured = StructuredCase()
    structured.metadata = metadata
    structured.full_text = case_text if include_full_text else ""

    # Procedural posture
    structured.procedural_posture = extract_procedural_posture(case_text)
    if structured.procedural_posture.detected:
        targets_found.append("procedural_posture")
        confidence_scores["procedural_posture"] = structured.procedural_posture.confidence
    else:
        targets_missing.append("procedural_posture")

    # Issue
    structured.issue = extract_issue(case_text)
    if structured.issue.detected:
        targets_found.append("issue")
        confidence_scores["issue"] = structured.issue.confidence
    else:
        targets_missing.append("issue")

    # Holding
    structured.holding = extract_holding(case_text)
    if structured.holding.detected:
        targets_found.append("holding")
        confidence_scores["holding"] = structured.holding.confidence
    else:
        targets_missing.append("holding")

    # Rule framework
    structured.rule_framework = extract_rule_framework(case_text)
    if structured.rule_framework.detected:
        targets_found.append("rule_framework")
        confidence_scores["rule_framework"] = structured.rule_framework.confidence
    else:
        targets_missing.append("rule_framework")

    # Reasoning
    structured.reasoning = extract_reasoning(case_text)
    if structured.reasoning.detected:
        targets_found.append("reasoning")
        confidence_scores["reasoning"] = structured.reasoning.confidence
    else:
        targets_missing.append("reasoning")

    # Key quotes
    structured.key_quotes = extract_key_quotes(case_text, metadata)
    if structured.key_quotes:
        targets_found.append(f"key_quotes ({len(structured.key_quotes)})")
        confidence_scores["key_quotes"] = 0.8
    else:
        targets_missing.append("key_quotes")

    # Facts
    structured.facts = extract_facts(case_text)
    if structured.facts.detected:
        targets_found.append("facts")
        confidence_scores["facts"] = structured.facts.confidence
    else:
        targets_missing.append("facts")

    # Authority cited
    structured.authority_cited = extract_authority_cited(case_text)
    if structured.authority_cited.detected:
        targets_found.append("authority_cited")
        confidence_scores["authority_cited"] = structured.authority_cited.confidence

    # Dissent
    structured.dissent = extract_dissent(case_text)
    if structured.dissent.detected and structured.dissent.content != "None":
        targets_found.append("dissent")
        confidence_scores["dissent"] = structured.dissent.confidence

    # Concurrence
    structured.concurrence = extract_concurrence(case_text)
    if structured.concurrence.detected and structured.concurrence.content != "None":
        targets_found.append("concurrence")
        confidence_scores["concurrence"] = structured.concurrence.confidence

    # Practical notes
    structured.practical_notes = generate_practical_notes(metadata, structured.holding)

    # Step 3: Generate output
    output = _format_structured_output(structured, include_full_text)

    # Step 4: Generate navigation (line numbers)
    structured.navigation = _generate_navigation(output)

    # Re-format with navigation
    output = _format_structured_output(structured, include_full_text)

    # Build meta
    overall_confidence = sum(confidence_scores.values()) / len(confidence_scores) if confidence_scores else 0.0

    meta = build_extraction_meta(
        found=targets_found,
        missing=targets_missing,
        confidence=confidence_scores,
        suggestions=_generate_suggestions(targets_missing)
    ) if EXTRACTION_VALIDATION_AVAILABLE else {
        "extraction_complete": len(targets_missing) == 0,
        "targets_found": targets_found,
        "targets_missing": targets_missing,
        "confidence_scores": confidence_scores,
        "overall_confidence": overall_confidence
    }

    return {
        "_meta": meta.to_dict() if hasattr(meta, 'to_dict') else meta,
        "structured_output": output,
        "metadata": {
            "case_name": metadata.case_name,
            "citation": metadata.citation,
            "court": metadata.court,
            "date": metadata.date,
            "author": metadata.author,
            "disposition": metadata.disposition
        },
        "sections_detected": targets_found,
        "sections_missing": targets_missing
    }


def _generate_suggestions(missing: List[str]) -> List[str]:
    """Generate suggestions for missing sections."""
    suggestions = []

    if "holding" in missing:
        suggestions.append("Search FULL_TEXT for 'we hold that' or judgment language near end")
    if "issue" in missing:
        suggestions.append("Search FULL_TEXT for 'question presented' or 'we must decide'")
    if "rule_framework" in missing:
        suggestions.append("Search FULL_TEXT for 'the standard is' or 'must establish'")
    if "procedural_posture" in missing:
        suggestions.append("Search FULL_TEXT for 'appeal from' or 'district court'")

    return suggestions


def _generate_navigation(output: str) -> Dict[str, int]:
    """Generate line numbers for navigation."""
    navigation = {}
    lines = output.split('\n')

    section_markers = [
        "### PROCEDURAL_POSTURE",
        "### ISSUE",
        "### HOLDING",
        "### RULE_FRAMEWORK",
        "### REASONING",
        "### KEY_QUOTES",
        "### FACTS",
        "### AUTHORITY_CITED",
        "### DISSENT",
        "### CONCURRENCE",
        "### PRACTICAL_NOTES",
        "### FULL_TEXT",
    ]

    for i, line in enumerate(lines, 1):
        for marker in section_markers:
            if marker in line:
                section_name = marker.replace("### ", "")
                navigation[section_name] = i

    return navigation


def _format_structured_output(structured: StructuredCase, include_full_text: bool) -> str:
    """Format the structured case into markdown output."""
    lines = []

    # Header
    lines.append("═" * 71)
    lines.append(f"CASE: {structured.metadata.case_name}")
    lines.append(f"CITE: {structured.metadata.citation}")
    lines.append(f"COURT: {structured.metadata.court}")
    lines.append(f"DATE: {structured.metadata.date}")
    lines.append(f"AUTHOR: {structured.metadata.author}")
    lines.append(f"DISPOSITION: {structured.metadata.disposition}")
    lines.append("═" * 71)

    # Navigation
    lines.append("")
    lines.append("### NAVIGATION")
    for section, line_num in structured.navigation.items():
        lines.append(f"- {section}: Line {line_num}")

    # Procedural Posture
    lines.append("")
    lines.append("═" * 71)
    lines.append("### PROCEDURAL_POSTURE")
    lines.append("═" * 71)
    lines.append("")
    lines.append(structured.procedural_posture.content)

    # Issue
    lines.append("")
    lines.append("═" * 71)
    lines.append("### ISSUE")
    lines.append("═" * 71)
    lines.append("")
    lines.append(structured.issue.content)

    # Holding
    lines.append("")
    lines.append("═" * 71)
    lines.append("### HOLDING")
    lines.append("═" * 71)
    lines.append("")
    lines.append(structured.holding.content)

    # Rule Framework
    lines.append("")
    lines.append("═" * 71)
    lines.append("### RULE_FRAMEWORK")
    lines.append("═" * 71)
    lines.append("")
    lines.append(structured.rule_framework.content)

    # Reasoning
    lines.append("")
    lines.append("═" * 71)
    lines.append("### REASONING")
    lines.append("═" * 71)
    lines.append("")
    lines.append(structured.reasoning.content)

    # Key Quotes
    lines.append("")
    lines.append("═" * 71)
    lines.append("### KEY_QUOTES")
    lines.append("═" * 71)
    lines.append("")
    if structured.key_quotes:
        for i, quote in enumerate(structured.key_quotes, 1):
            lines.append(f"[Quote {i}]")
            lines.append(f"> \"{quote.text}\"")
            lines.append(f"> — Location: {quote.location}")
            lines.append(f"> — Use for: {quote.use_for}")
            lines.append("")
    else:
        lines.append("[No key quotes extracted - see FULL_TEXT]")

    # Facts
    lines.append("")
    lines.append("═" * 71)
    lines.append("### FACTS")
    lines.append("═" * 71)
    lines.append("")
    lines.append(structured.facts.content)

    # Authority Cited
    lines.append("")
    lines.append("═" * 71)
    lines.append("### AUTHORITY_CITED")
    lines.append("═" * 71)
    lines.append("")
    lines.append(structured.authority_cited.content)

    # Dissent
    lines.append("")
    lines.append("═" * 71)
    lines.append("### DISSENT")
    lines.append("═" * 71)
    lines.append("")
    lines.append(structured.dissent.content)

    # Concurrence
    lines.append("")
    lines.append("═" * 71)
    lines.append("### CONCURRENCE")
    lines.append("═" * 71)
    lines.append("")
    lines.append(structured.concurrence.content)

    # Practical Notes
    lines.append("")
    lines.append("═" * 71)
    lines.append("### PRACTICAL_NOTES")
    lines.append("═" * 71)
    lines.append("")
    lines.append(structured.practical_notes.content)

    # Full Text
    if include_full_text and structured.full_text:
        lines.append("")
        lines.append("═" * 71)
        lines.append("### FULL_TEXT")
        lines.append("═" * 71)
        lines.append("")
        lines.append(structured.full_text)

    return "\n".join(lines)


# ============================================================================
# MCP WRAPPER
# ============================================================================

def structure_case_for_context_mcp(
    case_text: Optional[str] = None,
    case_name: Optional[str] = None,
    citation: Optional[str] = None,
    include_full_text: bool = True,
    file_path: Optional[str] = None
) -> str:
    """MCP wrapper for structure_case_for_context with formatted output."""
    result = structure_case_for_context(
        case_text=case_text,
        case_name=case_name,
        citation=citation,
        include_full_text=include_full_text,
        file_path=file_path
    )

    output = []
    output.append("=" * 70)
    output.append("CASE STRUCTURING COMPLETE")
    output.append("=" * 70)

    # Extraction report
    meta = result.get("_meta", {})
    output.append("\nEXTRACTION REPORT")
    output.append("-" * 40)

    for item in meta.get("targets_found", []):
        output.append(f"  [OK] {item}")
    for item in meta.get("targets_missing", []):
        output.append(f"  [X] {item}")

    confidence = meta.get("confidence_scores", {})
    if confidence:
        avg_conf = sum(confidence.values()) / len(confidence)
        output.append(f"\n  Overall confidence: {avg_conf:.0%}")

    for suggestion in meta.get("suggestions", []):
        output.append(f"  >> {suggestion}")

    # Metadata summary
    metadata = result.get("metadata", {})
    output.append(f"\nCase: {metadata.get('case_name', 'Unknown')}")
    output.append(f"Citation: {metadata.get('citation', 'Unknown')}")
    output.append(f"Court: {metadata.get('court', 'Unknown')}")
    output.append(f"Disposition: {metadata.get('disposition', 'Unknown')}")

    # Main structured output
    output.append("\n" + "=" * 70)
    output.append("STRUCTURED OUTPUT")
    output.append("=" * 70)
    output.append("")
    output.append(result.get("structured_output", ""))

    # JSON data at end
    output.append("\n" + "=" * 70)
    output.append("JSON_DATA:")

    # Create JSON-safe version (without full text for readability)
    json_result = {
        "_meta": result.get("_meta"),
        "metadata": result.get("metadata"),
        "sections_detected": result.get("sections_detected"),
        "sections_missing": result.get("sections_missing")
    }
    output.append(json.dumps(json_result, indent=2, default=str))

    return "\n".join(output)


# ============================================================================
# TEST
# ============================================================================

if __name__ == "__main__":
    # Test with sample text
    sample_case = """
    TELLABS, INC. v. MAKOR ISSUES & RIGHTS, LTD.

    551 U.S. 308

    Supreme Court of the United States

    Decided June 21, 2007

    Justice GINSBURG delivered the opinion of the Court.

    This case concerns the pleading standard for securities fraud actions.
    The question presented is whether the Private Securities Litigation Reform Act
    requires a plaintiff to plead facts giving rise to a "strong inference" of
    scienter.

    I. Background

    Tellabs, Inc., manufactures specialized equipment for fiber optic networks.
    During the relevant period, petitioner Richard Notebaert was Tellabs's CEO.
    Respondents purchased Tellabs stock and claim they were defrauded.

    The district court granted defendants' motion to dismiss. The Seventh Circuit
    reversed, holding that plaintiffs adequately pleaded scienter.

    II. Analysis

    We hold that to qualify as "strong," an inference of scienter must be more
    than merely plausible or reasonable—it must be cogent and at least as compelling
    as any opposing inference of nonfraudulent intent.

    The standard is demanding. The inference of scienter must be more than merely
    "reasonable" or "permissible"—it must be cogent and compelling.

    We affirm in part and reverse in part.
    """

    print("Testing structure_case_for_context...")
    result = structure_case_for_context_mcp(sample_case, include_full_text=False)
    print(result[:3000])
