"""
Core layer for Global Compiler parsing functions.

Pure functions for parsing LLM output into structured data.
These functions have no I/O and can be tested independently.

Reference: RFC-002 §4.4 Global Evolution
"""

from __future__ import annotations

import re
from dataclasses import dataclass

import deal


@dataclass(frozen=True)
class GlobalCandidatePattern:
    """A candidate global pattern identified in Phase 1.

    Attributes:
        pattern_name: Name/title of the pattern.
        description: Clear description of the pattern.
        source_projects: List of project names where this pattern appears.
        confidence: HIGH, MEDIUM, or LOW.
        evidence_type: adopted_rule, trace_signal, or both.
        rationale: Why this might be a global pattern.
        convergence_count: Number of projects supporting this pattern.

    >>> candidate = GlobalCandidatePattern(
    ...     pattern_name="Type Hints",
    ...     description="Always use type hints",
    ...     source_projects=["proj-a", "proj-b", "proj-c"],
    ...     confidence="HIGH",
    ...     evidence_type="adopted_rule",
    ...     rationale="Appears across multiple projects",
    ...     convergence_count=3,
    ... )
    >>> candidate.convergence_count
    3
    """

    pattern_name: str
    description: str
    source_projects: list[str]
    confidence: str
    evidence_type: str
    rationale: str
    convergence_count: int


@dataclass(frozen=True)
class GlobalProposal:
    """A global rule proposal from Phase 2.

    Attributes:
        title: Rule title.
        action: ADD, MERGE, REMOVE, or REWORD.
        target: Target rule file path.
        content: Rule content text.
        source_projects: Projects that contributed evidence.
        evidence_sessions: Session IDs from evidence.
        rationale: Why this should be a global rule.

    >>> proposal = GlobalProposal(
    ...     title="Type Hints",
    ...     action="ADD",
    ...     target="conventions.md",
    ...     content="Always use type hints",
    ...     source_projects=["proj-a", "proj-b"],
    ...     evidence_sessions=["s1", "s2"],
    ...     rationale="Convergent pattern",
    ... )
    >>> proposal.action
    'ADD'
    """

    title: str
    action: str
    target: str
    content: str
    source_projects: list[str]
    evidence_sessions: list[str]
    rationale: str


@deal.pre(lambda cot_output: isinstance(cot_output, str))
@deal.post(lambda result: isinstance(result, list))
def parse_candidate_patterns(cot_output: str) -> list[GlobalCandidatePattern]:
    """Parse candidate patterns from LLM Phase 1 output.

    Args:
        cot_output: Full CoT output from LLM.

    Returns:
        List of GlobalCandidatePattern objects.

    >>> cot = "<candidate_patterns>\\n## CANDIDATE: Test\\nPattern: desc\\nSource Projects: a, b\\nConfidence: HIGH\\nEvidence Type: rule\\nRationale: test\\nConvergence: 2\\n</candidate_patterns>"
    >>> patterns = parse_candidate_patterns(cot)
    >>> len(patterns)
    1
    >>> patterns[0].pattern_name
    'Test'
    """
    patterns: list[GlobalCandidatePattern] = []

    # Extract candidate_patterns section
    match = re.search(
        r"<candidate_patterns>(.*?)</candidate_patterns>",
        cot_output,
        re.DOTALL | re.IGNORECASE,
    )
    if not match:
        return patterns

    candidates_section = match.group(1)

    # Parse each candidate
    candidate_pattern = re.compile(
        r"##\s*CANDIDATE:\s*(.+?)\n"
        r"Pattern:\s*(.+?)\n"
        r"Source Projects:\s*(.+?)\n"
        r"Confidence:\s*(.+?)\n"
        r"Evidence Type:\s*(.+?)\n"
        r"Rationale:\s*(.+?)\n"
        r"Convergence:\s*(\d+)",
        re.DOTALL,
    )

    for m in candidate_pattern.finditer(candidates_section):
        projects_str = m.group(3).strip()
        # Parse comma-separated project list
        projects = [p.strip() for p in projects_str.split(",") if p.strip()]

        patterns.append(
            GlobalCandidatePattern(
                pattern_name=m.group(1).strip(),
                description=m.group(2).strip(),
                source_projects=projects,
                confidence=m.group(4).strip().upper(),
                evidence_type=m.group(5).strip(),
                rationale=m.group(6).strip(),
                convergence_count=int(m.group(7)),
            )
        )

    return patterns


@deal.pre(lambda cot_output: isinstance(cot_output, str))
@deal.post(lambda result: isinstance(result, list))
def parse_global_proposals(cot_output: str) -> list[GlobalProposal]:
    """Parse global rule proposals from LLM Phase 2 output.

    Args:
        cot_output: Full CoT output from LLM.

    Returns:
        List of GlobalProposal objects.

    >>> cot = "<synthesis>\\n## GLOBAL PROPOSAL: Test\\nAction: ADD\\nTarget: conv.md\\nContent: rule\\nSource Projects: a\\nEvidence Sessions: s1\\nRationale: test\\n</synthesis>"
    >>> proposals = parse_global_proposals(cot)
    >>> len(proposals)
    1
    """
    proposals: list[GlobalProposal] = []

    # Extract synthesis section
    match = re.search(
        r"<synthesis>(.*?)</synthesis>",
        cot_output,
        re.DOTALL | re.IGNORECASE,
    )
    if not match:
        return proposals

    synthesis_section = match.group(1)

    # Parse each proposal
    proposal_pattern = re.compile(
        r"##\s*GLOBAL PROPOSAL:\s*(.+?)\n"
        r"Action:\s*(.+?)\n"
        r"Target:\s*(.+?)\n"
        r"Content:\s*(.+?)\n"
        r"Source Projects:\s*(.+?)\n"
        r"Evidence Sessions:\s*(.+?)\n"
        r"Rationale:\s*(.+?)(?=\n##|\Z)",
        re.DOTALL,
    )

    for m in proposal_pattern.finditer(synthesis_section):
        # Parse comma-separated lists
        projects = [p.strip() for p in m.group(5).split(",") if p.strip()]
        sessions = [s.strip() for s in m.group(6).split(",") if s.strip()]

        proposals.append(
            GlobalProposal(
                title=m.group(1).strip(),
                action=m.group(2).strip().upper(),
                target=m.group(3).strip(),
                content=m.group(4).strip(),
                source_projects=projects,
                evidence_sessions=sessions,
                rationale=m.group(7).strip(),
            )
        )

    return proposals


@deal.post(lambda result: isinstance(result, str))
def extract_triage_content(cot_output: str) -> str:
    """Extract triage section from CoT output.

    Args:
        cot_output: Full CoT output.

    Returns:
        Triage section content or empty string.

    >>> extract_triage_content("<triage>Hello</triage>")
    'Hello'
    >>> extract_triage_content("No triage here")
    ''
    """
    match = re.search(r"<triage>(.*?)</triage>", cot_output, re.DOTALL | re.IGNORECASE)
    if match:
        return match.group(1).strip()
    return ""


@deal.post(lambda result: isinstance(result, str))
def extract_cross_ref_content(cot_output: str) -> str:
    """Extract cross_ref section from CoT output.

    Args:
        cot_output: Full CoT output.

    Returns:
        Cross-ref section content or empty string.

    >>> extract_cross_ref_content("<cross_ref>Verification</cross_ref>")
    'Verification'
    >>> extract_cross_ref_content("No cross_ref here")
    ''
    """
    match = re.search(
        r"<cross_ref>(.*?)</cross_ref>", cot_output, re.DOTALL | re.IGNORECASE
    )
    if match:
        return match.group(1).strip()
    return ""


@deal.post(lambda result: isinstance(result, str))
def extract_synthesis_content(cot_output: str) -> str:
    """Extract synthesis section from CoT output.

    Args:
        cot_output: Full CoT output.

    Returns:
        Synthesis section content or empty string.

    >>> extract_synthesis_content("<synthesis>Proposals</synthesis>")
    'Proposals'
    >>> extract_synthesis_content("No synthesis here")
    ''
    """
    match = re.search(
        r"<synthesis>(.*?)</synthesis>", cot_output, re.DOTALL | re.IGNORECASE
    )
    if match:
        return match.group(1).strip()
    return ""


__all__ = [
    "GlobalCandidatePattern",
    "GlobalProposal",
    "parse_candidate_patterns",
    "parse_global_proposals",
    "extract_triage_content",
    "extract_cross_ref_content",
    "extract_synthesis_content",
]
