"""Claim extraction module for battle card generation.

Parses narrative documents and data modules to extract
claim/evidence/implication triples suitable for FIA card assembly.
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import Optional


class ClaimExtractor:
    """Extract quantified claims from narratives and data modules.

    Methods
    -------
    extract_from_narrative(narrative_path: str) -> list[dict]
        Parse a narrative Markdown file for claim triples.
    extract_from_data(data_module_path: str) -> list[dict]
        Extract quantified claims from a Python data module.
    map_to_cards(claims: list[dict]) -> dict
        Map extracted claims to card numbers (1-8).

    Claim dict format
    -----------------
    {
        "card_number": int,
        "section": "fact" | "impact" | "act",
        "claim": str,
        "evidence": str,
        "source": str,
    }
    """

    # Card number to topic mapping for heuristic assignment
    _CARD_TOPICS = {
        1: ("valuation", "cape", "market cap", "shiller", "p/e"),
        2: ("infrastructure", "data center", "hyperscaler", "capex"),
        3: ("gpu", "utilization", "tensor", "compute"),
        4: ("startup", "funding", "venture", "valuation disconnect"),
        5: ("enterprise", "deployment", "adoption", "production"),
        6: ("developer", "coding", "programming", "ide"),
        7: ("quality", "security", "vulnerability", "bug"),
        8: ("productivity", "long-term", "trajectory", "efficiency"),
    }

    def extract_from_narrative(
        self, narrative_path: str
    ) -> list[dict]:
        """Parse a Markdown narrative for claim/evidence/implication triples.

        Parameters
        ----------
        narrative_path : str
            Path to the Markdown narrative file.

        Returns
        -------
        list[dict]
            List of extracted claim dicts.
        """
        claims: list[dict] = []
        path = Path(narrative_path)

        if not path.exists():
            return claims

        text = path.read_text(encoding="utf-8")

        # Pattern: bullet points that contain quantitative data
        # Matches lines starting with "- " that contain numbers
        bullet_pattern = re.compile(
            r"^[-*]\s+(.+?[\d%]+\S.*?)$",
            re.MULTILINE,
        )

        for match in bullet_pattern.finditer(text):
            bullet_text = match.group(1).strip()

            # Extract evidence (numeric data points)
            numbers = re.findall(
                r"\d+(?:,\d{3})*(?:\.\d+)?[%$]?",
                bullet_text,
            )
            evidence = ", ".join(numbers) if numbers else "qualitative"

            # Determine section by context keywords
            section = self._classify_section(bullet_text)

            # Map to card number by topic
            card_number = self._match_topic(bullet_text)

            claim = {
                "card_number": card_number,
                "section": section,
                "claim": bullet_text,
                "evidence": evidence,
                "source": "case_narrative",
            }
            claims.append(claim)

        return claims

    def extract_from_data(
        self, data_module_path: str
    ) -> list[dict]:
        """Extract quantified claims from a Python data module.

        Reads module-level list[dict] or dict constants and
        extracts notable data points as claims.

        Parameters
        ----------
        data_module_path : str
            Path to the Python data module file.

        Returns
        -------
        list[dict]
            List of extracted claim dicts.
        """
        claims: list[dict] = []
        path = Path(data_module_path)

        if not path.exists():
            return claims

        text = path.read_text(encoding="utf-8")

        # Extract module-level variable names (list[dict] or dict)
        var_pattern = re.compile(
            r"^(\w+):\s*list\[dict\].*?=(\[.*?\])",
            re.MULTILINE | re.DOTALL,
        )

        module_name = path.stem

        for match in var_pattern.finditer(text):
            var_name = match.group(1)
            data_str = match.group(2)

            # Extract representative values from the data
            numbers = re.findall(
                r"[\d]+(?:,[\d]{3})*(?:\.[\d]+)?",
                data_str,
            )

            if numbers:
                # Take first and last significant values
                sample = f"Range: {numbers[0]} to {numbers[-1]}"
                card_number = self._match_topic(var_name)

                claim = {
                    "card_number": card_number,
                    "section": "fact",
                    "claim": f"{var_name}: {sample}",
                    "evidence": sample,
                    "source": module_name,
                }
                claims.append(claim)

        return claims

    def map_to_cards(self, claims: list[dict]) -> dict:
        """Map a list of claims to card numbers (1-8).

        Parameters
        ----------
        claims : list[dict]
            List of claim dicts to organize.

        Returns
        -------
        dict
            Mapping of card_number -> list of claims for that card.
        """
        card_map: dict[int, list[dict]] = {i: [] for i in range(1, 9)}

        for claim in claims:
            card_num = claim.get("card_number", 1)
            # Clamp to valid range
            card_num = max(1, min(8, card_num))
            card_map[card_num].append(claim)

        return card_map

    # -----------------------------------------------------------------------
    # Private helpers
    # -----------------------------------------------------------------------

    @staticmethod
    def _classify_section(text: str) -> str:
        """Classify a text snippet into fact, impact, or act section."""
        lower = text.lower()
        if any(
            kw in lower
            for kw in [
                "risk", "impact", "threat", "consequence", "could",
                "would", "may lead", "potential",
            ]
        ):
            return "impact"
        if any(
            kw in lower
            for kw in [
                "should", "recommend", "act", "take action",
                "consider", "monitor", "hedge",
            ]
        ):
            return "act"
        return "fact"

    @staticmethod
    def _match_topic(text: str) -> int:
        """Match text to the closest card number by topic keywords."""
        lower = text.lower()
        for card_num, keywords in ClaimExtractor._CARD_TOPICS.items():
            if any(kw in lower for kw in keywords):
                return card_num
        return 1  # Default to card 1
