"""Unified SVD component labels and automatic flip direction computation.

This module provides a single source of truth for SVD component labels,
deriving them from SVD_THEMES in explorer.py. It also computes flip
directions automatically based on party centroids.
"""

import logging
from typing import Dict, List, Optional, Tuple

from analysis.config import CANONICAL_LEFT, CANONICAL_RIGHT

_logger = logging.getLogger(__name__)

RIGHT_PARTIES = CANONICAL_RIGHT
LEFT_PARTIES = CANONICAL_LEFT

# Cache for SVD_THEMES to avoid repeated imports
_svd_themes_cache: Optional[Dict[int, Dict[str, str]]] = None


def _get_svd_themes() -> Dict[int, Dict[str, str]]:
    """Import SVD_THEMES from explorer.py.

    Returns:
        Dict mapping component number to theme dict with keys:
        - label: Short label for the component
        - explanation: Detailed explanation
        - positive_pole: Description of positive pole
        - negative_pole: Description of negative pole
        - flip: Whether to flip the axis
    """
    global _svd_themes_cache
    if _svd_themes_cache is not None:
        return _svd_themes_cache

    # Prefer the lightweight canonical source in analysis.config which is
    # intentionally free of heavy runtime dependencies. Fall back to
    # explorer.SVD_THEMES only when the config module is unavailable or
    # doesn't expose SVD_THEMES.
    try:
        from analysis import config as _cfg

        _svd_themes_cache = getattr(_cfg, "SVD_THEMES", {}) or {}
        if _svd_themes_cache:
            return _svd_themes_cache
    except Exception:
        _logger.exception(
            "Could not import analysis.config or read SVD_THEMES; falling back to explorer"
        )

    try:
        # Import explorer at runtime as a last resort; explorer may pull in
        # heavy dependencies (duckdb/plotly) so we only try this if config
        # didn't provide the themes.
        import explorer

        _svd_themes_cache = getattr(explorer, "SVD_THEMES", {}) or {}
        return _svd_themes_cache
    except ImportError as e:
        _logger.warning("Could not import explorer.SVD_THEMES: %s", e)
        return {}
    except Exception as e:
        _logger.exception("Failed to load SVD_THEMES from explorer.py: %s", e)
        return {}


def get_svd_label(component: int) -> str:
    """Get short label for SVD component.

    Args:
        component: SVD component number (1-indexed)

    Returns:
        Short label string (e.g., 'EU-integratie–Nationalisme')

    Raises:
        ValueError: If component < 1
    """
    if component < 1:
        raise ValueError(f"Component must be >= 1, got {component}")

    themes = _get_svd_themes()
    if component in themes:
        return themes[component].get("label", f"As {component}")

    # Fallback labels for components 1-3 (most commonly used)
    fallback_labels = {
        1: "EU-integratie–Nationalisme",
        2: "Populistisch–Institutioneel",
        3: "Verzorgingsstaat–Marktwerking",
    }
    return fallback_labels.get(component, f"As {component}")


def get_svd_theme(component: int) -> Dict[str, str]:
    """Get full theme dict for SVD component.

    Args:
        component: SVD component number (1-indexed)

    Returns:
        Dict with keys: label, explanation, positive_pole, negative_pole, flip
    """
    if component < 1:
        raise ValueError(f"Component must be >= 1, got {component}")

    themes = _get_svd_themes()
    if component in themes:
        return themes[component]

    # Return minimal fallback
    return {
        "label": get_svd_label(component),
        "explanation": "",
        "positive_pole": "",
        "negative_pole": "",
        "flip": False,
    }


def compute_flip_direction(
    component: int,
    party_scores: Dict[str, List[float]],
) -> bool:
    """Compute flip direction so right parties appear on the right side.

    Args:
        component: SVD component number (1-indexed)
        party_scores: Dict mapping party name to per-component scores.
                      party_scores[party][0] is score for component 1 (x-axis),
                      party_scores[party][1] is score for component 2 (y-axis).

    Returns:
        True if axis should be flipped so right parties are on right.
        False otherwise.
    """
    if component < 1:
        return False

    idx = component - 1  # Convert to 0-indexed

    right_scores = []
    left_scores = []

    for party, scores in party_scores.items():
        if len(scores) <= idx:
            continue

        score = scores[idx]
        if party in RIGHT_PARTIES:
            right_scores.append(score)
        elif party in LEFT_PARTIES:
            left_scores.append(score)

    if not right_scores or not left_scores:
        return False  # Default: no flip if insufficient data

    right_mean = sum(right_scores) / len(right_scores)
    left_mean = sum(left_scores) / len(left_scores)

    # Flip if right parties have lower mean (they're on the left)
    return right_mean < left_mean


def get_fallback_labels() -> Tuple[str, str]:
    """Get fallback labels for x and y axes (components 1 and 2).

    Returns:
        Tuple of (x_label, y_label)
    """
    return (get_svd_label(1), get_svd_label(2))
