# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Graph Parser: Text-to-graph extraction primitives.

Provides edge extraction, markdown parsing, and node ID generation
used by knowledge_graph_build.py to construct the knowledge graph.

Public API:
  - CAUSAL_PATTERNS          Raw (pattern, edge_type) pairs
  - COMPILED_CAUSAL          Pre-compiled regex versions
  - now_iso()                UTC timestamp helper
  - node_id(text)            Stable hash-based node ID
  - extract_causal_edges()   Regex-based causal relationship extraction
  - parse_md_sections()      Markdown -> structured entry list
"""

import hashlib
import re
from datetime import datetime, timezone
from pathlib import Path

# === CAUSAL PATTERNS (for extracting edges from text) ===

CAUSAL_PATTERNS = [
    # X causes/leads to Y
    (r"(.{10,80}?)\s+(?:caused|led to|resulted in|triggers?)\s+(.{10,80})",
     "CAUSES"),
    # X prevents/avoids Y
    (r"(.{10,80}?)\s+(?:prevents?|avoids?|stops?|eliminates?)\s+(.{10,80})",
     "PREVENTS"),
    # Because/since X, Y
    (r"(?:because|since|due to)\s+(.{10,80}?),\s+(.{10,80})",
     "CAUSES"),
    # X requires/needs Y
    (r"(.{10,80}?)\s+(?:requires?|needs?|depends on)\s+(.{10,80})",
     "REQUIRES"),
    # If/when X, then Y
    (r"(?:if|when|whenever)\s+(.{10,80}?),\s+(?:then\s+)?(.{10,80})",
     "LEADS_TO"),
    # X enables/allows Y
    (r"(.{10,80}?)\s+(?:enables?|allows?|makes? possible)\s+(.{10,80})",
     "ENABLES"),
    # X contradicts/conflicts with Y
    (r"(.{10,80}?)\s+(?:contradicts?|conflicts? with|opposes?)\s+(.{10,80})",
     "CONTRADICTS"),
    # X resolves/fixes Y
    (r"(.{10,80}?)\s+(?:resolves?|fixes?|solves?|addresses?)\s+(.{10,80})",
     "RESOLVES"),
]

# Compiled patterns
COMPILED_CAUSAL = [
    (re.compile(p, re.IGNORECASE), etype) for p, etype in CAUSAL_PATTERNS
]


def now_iso() -> str:
    """Return current UTC timestamp in ISO format."""
    return datetime.now(timezone.utc).isoformat()


def node_id(text: str) -> str:
    """Generate stable node ID from text (first 50 chars, lowercased, hashed)."""
    key = text[:50].lower().strip()
    return "N-" + hashlib.md5(key.encode("utf-8")).hexdigest()[:12]


def extract_causal_edges(text: str) -> list:
    """Extract causal relationships from text using regex patterns.

    Returns list of dicts with from_text, to_text, type, confidence.
    """
    edges = []
    for pattern, edge_type in COMPILED_CAUSAL:
        for match in pattern.finditer(text):
            from_text = match.group(1).strip()
            to_text = match.group(2).strip()
            # Quality gate: both sides must be substantial
            if len(from_text) < 10 or len(to_text) < 10:
                continue
            if len(from_text.split()) < 3 or len(to_text.split()) < 3:
                continue
            edges.append({
                "from_text": from_text[:100],
                "to_text": to_text[:100],
                "type": edge_type,
                "confidence": 0.6,
            })
    return edges


def parse_md_sections(filepath: Path) -> list:
    """Parse markdown file into list of structured entry dicts.

    Each entry has: title, body, source, date, confidence.
    """
    if not filepath.exists():
        return []
    text = filepath.read_text(encoding="utf-8", errors="replace")
    sections = text.split("\n## ")
    entries = []
    for section in sections[1:]:
        lines = section.strip().split("\n")
        title = lines[0].strip()
        body = "\n".join(lines[1:]).strip()
        if len(title) < 5:
            continue

        # Extract metadata
        source_match = re.search(r"\*\*Source:\*\*\s*(\S+)", body)
        date_match = re.search(r"\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})", body)
        conf_match = re.search(r"\*\*Confidence:\*\*\s*([\d.]+)", body)

        entries.append({
            "title": title[:100],
            "body": body[:300],
            "source": source_match.group(1) if source_match else "unknown",
            "date": date_match.group(1) if date_match else "",
            "confidence": float(conf_match.group(1)) if conf_match else 0.5,
        })
    return entries


# === Backward-compatible aliases (underscore-prefixed names) ===
# These match the original private API in knowledge_graph_build.py
# so any code that imported them directly continues to work.

_now_iso = now_iso
_node_id = node_id
_extract_causal_edges = extract_causal_edges
_parse_md_sections = parse_md_sections
_COMPILED_CAUSAL = COMPILED_CAUSAL
