"""
Core-layer proposal parsing utilities.

Pure functions for parsing proposal markdown into actionable directives.
No I/O operations - all file operations are handled by Shell layer.
"""

import re
from dataclasses import dataclass

from deal import pre, post

from lattice.core.types.enums import ProposalAction


@dataclass(frozen=True)
class ProposalDirective:
    """A parsed directive from a proposal.

    >>> d = ProposalDirective(
    ...     action=ProposalAction.ADD,
    ...     title="Always use type hints",
    ...     content="Type hints are required for all functions.",
    ...     target_file="conventions.md",
    ... )
    >>> d.action
    <ProposalAction.ADD: 'add'>
    """

    action: ProposalAction
    title: str
    content: str
    target_file: str = "conventions.md"


@pre(lambda content: isinstance(content, str))
@post(lambda result: isinstance(result, list))
@post(lambda result: all(isinstance(d, ProposalDirective) for d in result))
def parse_proposal_markdown(content: str) -> list[ProposalDirective]:
    """Parse proposal markdown into list of actionable directives.

    Parses the format generated by _format_proposal_content:
        ## Proposal N: [Title]

        **Action**: [add|merge|remove|reword|update]
        **Pattern**: [convention|...]

        ### Content

        [Rule content]

    Args:
        content: Full markdown content of proposal file.

    Returns:
        List of ProposalDirective objects. May be empty.

    >>> md = '''
    ... # Evolution Proposals
    ...
    ... ## Proposal 1: Test Rule
    ...
    ... **Action**: add
    ... **Pattern**: convention
    ...
    ... ### Content
    ...
    ... The rule content here.
    ... '''
    >>> directives = parse_proposal_markdown(md)
    >>> len(directives)
    1
    >>> directives[0].action
    <ProposalAction.ADD: 'add'>
    >>> directives[0].title
    'Test Rule'
    >>> # Test UPDATE action (not just reword)
    >>> md_update = '''
    ... ## Proposal 2: Update Rule
    ...
    ... **Action**: update
    ... **Pattern**: preference
    ...
    ... ### Content
    ...
    ... Updated content.
    ... '''
    >>> directives_update = parse_proposal_markdown(md_update)
    >>> len(directives_update)
    1
    >>> directives_update[0].action
    <ProposalAction.UPDATE: 'update'>
    >>> # Test MERGE action
    >>> md_merge = '''
    ... ## Proposal 3: Merge Rule
    ...
    ... **Action**: merge
    ... **Pattern**: convention
    ...
    ... ### Content
    ...
    ... Additional content to merge.
    ... '''
    >>> directives_merge = parse_proposal_markdown(md_merge)
    >>> len(directives_merge)
    1
    >>> directives_merge[0].action
    <ProposalAction.MERGE: 'merge'>
    >>> # Test REMOVE action
    >>> md_remove = '''
    ... ## Proposal 4: Remove Rule
    ...
    ... **Action**: remove
    ... **Pattern**: preference
    ...
    ... ### Content
    ...
    ... Rule to remove.
    ... '''
    >>> directives_remove = parse_proposal_markdown(md_remove)
    >>> len(directives_remove)
    1
    >>> directives_remove[0].action
    <ProposalAction.REMOVE: 'remove'>
    >>> # Test REWORD action (maps to UPDATE)
    >>> md_reword = '''
    ... ## Proposal 5: Reword Rule
    ...
    ... **Action**: reword
    ... **Pattern**: convention
    ...
    ... ### Content
    ...
    ... Reworded content.
    ... '''
    >>> directives_reword = parse_proposal_markdown(md_reword)
    >>> len(directives_reword)
    1
    >>> directives_reword[0].action
    <ProposalAction.UPDATE: 'update'>
    """
    directives: list[ProposalDirective] = []

    # Pattern for each proposal section
    # Matches: ## Proposal N: Title ... **Action**: action ... ### Content ... content
    proposal_pattern = re.compile(
        r"##\s*Proposal\s+\d+:\s*(.+?)\n"
        r".*?"
        r"\*\*Action\*\*:\s*(add|merge|remove|reword|update)\s*\n"
        r".*?"
        r"\*\*Pattern\*\*:\s*\w+\s*\n"
        r".*?"
        r"###\s*Content\s*\n"
        r"\n?"
        r"(.*?)(?=\n###|\n##|\Z)",
        re.DOTALL | re.IGNORECASE,
    )

    for match in proposal_pattern.finditer(content):
        title = match.group(1).strip()
        action_str = match.group(2).strip().lower()
        rule_content = match.group(3).strip()

        # Map action string to enum
        # Note: "reword" maps to UPDATE (they are synonyms in RFC-002)
        action_map = {
            "add": ProposalAction.ADD,
            "merge": ProposalAction.MERGE,
            "remove": ProposalAction.REMOVE,
            "reword": ProposalAction.UPDATE,  # RFC-002 uses REWORD/UPDATE interchangeably
            "update": ProposalAction.UPDATE,
        }
        action = action_map.get(action_str, ProposalAction.ADD)

        # Derive target filename from title (kebab-case)
        target_file = _title_to_filename(title)

        directives.append(
            ProposalDirective(
                action=action,
                title=title,
                content=rule_content,
                target_file=target_file,
            )
        )

    return directives


@pre(lambda title: isinstance(title, str))
@post(lambda result: isinstance(result, str))
@post(lambda result: result.endswith(".md"))
def _title_to_filename(title: str) -> str:
    """Convert proposal title to kebab-case filename.

    >>> _title_to_filename("Always use type hints")
    'always_use_type_hints.md'
    >>> _title_to_filename("Test Rule!")
    'test_rule.md'
    """
    # Lowercase, replace spaces/special chars with underscores
    filename = re.sub(r"[^a-zA-Z0-9]+", "_", title.lower())
    # Remove leading/trailing underscores
    filename = filename.strip("_")
    # Ensure non-empty
    if not filename:
        filename = "rule"
    return f"{filename}.md"


__all__ = ["ProposalDirective", "parse_proposal_markdown"]
