"""Vastian Meeting Analyzer — Signal Extractor.

Functions for extracting structured signals (decisions, action items,
blockers, risks, ideas, key signals) from parsed meeting sections.
"""

from __future__ import annotations

import json
import logging
from typing import Any

from .constants import SIGNAL_TYPES

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Primary extraction
# ---------------------------------------------------------------------------


def extract_signals_from_sections(parsed_sections: dict[str, str]) -> dict[str, Any]:
    """Extract structured signals from parsed sections.

    Args:
        parsed_sections: Dict of *section_name* -> *content* as produced by
            :func:`~vastian.meeting_analyzer.parser.parse_adaptive`.

    Returns:
        Dict with signal lists keyed by type.
    """
    signals: dict[str, Any] = {
        "decisions": [],
        "action_items": [],
        "blockers": [],
        "risks": [],
        "ideas": [],
        "key_signals": [],
        "context": "",
        "notes": "",
    }

    # Synthesized section gets highest priority
    synthesized_key = next(
        (k for k in parsed_sections if "synthesized" in k.lower()),
        None,
    )
    if synthesized_key:
        synthesized_signals = extract_from_synthesized(parsed_sections[synthesized_key])
        signals = merge_signals(signals, synthesized_signals)

    # Process each section
    for section_name, content in parsed_sections.items():
        if not content:
            continue

        section_lower = section_name.lower()

        if section_lower in ("decision", "decisions"):
            signals["decisions"].extend(extract_items(content))
        elif section_lower in (
            "action_item",
            "action items",
            "tasks",
            "commitments",
            "work identified",
        ):
            signals["action_items"].extend(extract_items(content))
        elif section_lower in ("blocker", "blockers", "blocked"):
            signals["blockers"].extend(extract_items(content))
        elif section_lower in ("risk", "risks", "concerns", "risks / open questions"):
            signals["risks"].extend(extract_items(content))
        elif section_lower in ("idea", "ideas", "suggestions"):
            signals["ideas"].extend(extract_items(content))
        elif section_lower in ("key_signal", "key signals", "insights", "outcomes"):
            signals["key_signals"].extend(extract_items(content))
        elif section_lower in ("context", "background"):
            signals["context"] = content
        elif section_lower in ("notes", "summary"):
            signals["notes"] = content

    return signals


# ---------------------------------------------------------------------------
# Item-level helpers
# ---------------------------------------------------------------------------


def extract_items(content: str) -> list[str]:
    """Extract individual bullet-point items from section content.

    Args:
        content: Section text content.

    Returns:
        List of cleaned item strings.
    """
    items: list[str] = []

    for line in content.splitlines():
        stripped = line.strip()

        # Skip empties and standalone emoji markers
        if not stripped or stripped in (
            "\U0001f6a6",
            "\U0001f9e9",
            "✨",
            "\U0001f4dd",
            "\U0001f7e9",
            "\U0001f7ea",
        ):
            continue

        # Skip sub-headers
        if stripped.startswith("###") or stripped.startswith("**"):
            continue

        item = stripped.lstrip("-•*").strip()

        if item and len(item) > 2:
            items.append(item)

    return items


def extract_from_synthesized(text: str) -> dict[str, list[str]]:
    """Extract signals from a *synthesized signals* block.

    The block may contain inline section headers (e.g. ``Decisions:``) followed
    by bullet items.

    Args:
        text: Synthesized signals section content.

    Returns:
        Dict with signal lists.
    """
    result: dict[str, list[str]] = {
        "decisions": [],
        "action_items": [],
        "blockers": [],
        "risks": [],
        "ideas": [],
        "key_signals": [],
    }

    current_section: str | None = None

    for line in text.splitlines():
        stripped = line.strip()

        if not stripped or stripped in ("\U0001f6a6", "\U0001f9e9", "✨", "\U0001f4dd"):
            continue

        stripped_lower = stripped.lower()
        if "decision" in stripped_lower and stripped_lower.endswith(":"):
            current_section = "decisions"
            continue
        elif "action" in stripped_lower and stripped_lower.endswith(":"):
            current_section = "action_items"
            continue
        elif "block" in stripped_lower and stripped_lower.endswith(":"):
            current_section = "blockers"
            continue
        elif "risk" in stripped_lower and stripped_lower.endswith(":"):
            current_section = "risks"
            continue
        elif "idea" in stripped_lower and stripped_lower.endswith(":"):
            current_section = "ideas"
            continue
        elif "key" in stripped_lower and "signal" in stripped_lower:
            current_section = "key_signals"
            continue

        if current_section:
            item = stripped.lstrip("-•*").strip()
            if item and item not in result[current_section]:
                result[current_section].append(item)

    return result


# ---------------------------------------------------------------------------
# Keyword fallback
# ---------------------------------------------------------------------------


def extract_signals_keyword_fallback(text: str) -> dict[str, list[str]]:
    """Keyword-based signal extraction used as a last-resort fallback.

    Args:
        text: Meeting text.

    Returns:
        Dict with extracted signals.
    """
    result: dict[str, list[str]] = {
        "decisions": [],
        "action_items": [],
        "blockers": [],
        "risks": [],
        "ideas": [],
        "key_signals": [],
    }

    for line in text.splitlines():
        stripped = line.strip()
        if not stripped:
            continue

        lower = stripped.lower()

        for signal_type, info in SIGNAL_TYPES.items():
            for keyword in info["keywords"]:
                if keyword.lower() in lower:
                    key = signal_type + "s" if not signal_type.endswith("s") else signal_type
                    if key == "action_items":
                        key = "action_items"
                    elif key == "key_signals":
                        key = "key_signals"

                    if key in result and stripped not in result[key]:
                        result[key].append(stripped)
                    break

    return result


# ---------------------------------------------------------------------------
# AI response parsing
# ---------------------------------------------------------------------------


def parse_ai_signal_response(response: str) -> dict[str, list[str]]:
    """Parse an AI model response containing extracted signals.

    Tries JSON first, then falls back to line-by-line heading detection.

    Args:
        response: AI model response text.

    Returns:
        Dict with signal lists.
    """
    result: dict[str, list[str]] = {
        "decisions": [],
        "action_items": [],
        "blockers": [],
        "risks": [],
        "ideas": [],
        "key_signals": [],
    }

    # Attempt JSON parse
    try:
        parsed = json.loads(response)
        if isinstance(parsed, dict):
            for key in result:
                if key in parsed and isinstance(parsed[key], list):
                    result[key] = parsed[key]
            return result
    except json.JSONDecodeError:
        pass

    # Fallback: line-by-line
    current_section: str | None = None
    for line in response.splitlines():
        stripped = line.strip()
        lower = stripped.lower()

        if "decision" in lower:
            current_section = "decisions"
        elif "action" in lower:
            current_section = "action_items"
        elif "blocker" in lower:
            current_section = "blockers"
        elif "risk" in lower:
            current_section = "risks"
        elif "idea" in lower:
            current_section = "ideas"
        elif "key" in lower and "signal" in lower:
            current_section = "key_signals"
        elif current_section and stripped.startswith("-"):
            item = stripped.lstrip("-•*").strip()
            if item and item not in result[current_section]:
                result[current_section].append(item)

    return result


# ---------------------------------------------------------------------------
# Merge / dedup utilities
# ---------------------------------------------------------------------------


def merge_signals(
    base: dict[str, Any],
    additional: dict[str, list[str]],
) -> dict[str, Any]:
    """Merge *additional* signals into *base*, skipping duplicates.

    Args:
        base: Base signals dict.
        additional: Additional signals to merge in.

    Returns:
        The merged *base* dict (mutated in-place).
    """
    for key, items in additional.items():
        if key in base and isinstance(base[key], list):
            for item in items:
                if item not in base[key]:
                    base[key].append(item)

    return base


def deduplicate_signals(signals: dict[str, Any]) -> dict[str, Any]:
    """Remove duplicate and near-duplicate signals (case-insensitive).

    Args:
        signals: Dict with signal lists.

    Returns:
        The same dict with duplicates removed.
    """
    for key, value in signals.items():
        if isinstance(value, list):
            seen: set[str] = set()
            unique: list[str] = []
            for item in value:
                normalized = item.lower().strip()
                if normalized not in seen:
                    seen.add(normalized)
                    unique.append(item)
            signals[key] = unique

    return signals


def extract_signals_from_meeting(parsed_sections: dict[str, str]) -> dict[str, Any]:
    """Legacy adapter — delegates to :func:`extract_signals_from_sections`."""
    return extract_signals_from_sections(parsed_sections)


__all__ = [
    "extract_signals_from_sections",
    "extract_items",
    "extract_from_synthesized",
    "extract_signals_keyword_fallback",
    "parse_ai_signal_response",
    "merge_signals",
    "deduplicate_signals",
    "extract_signals_from_meeting",
]
