# geney/folding/vienna.py — ViennaRNA wrapper for local MFE.
"""Single call site for RNA folding; returns MFE (kcal/mol) only."""

from __future__ import annotations

__all__ = ["fold", "fold_full", "fold_available"]

_RNA: object = None


def _get_rna():
    """Lazy import of ViennaRNA to avoid import-time failure if not installed."""
    global _RNA
    if _RNA is None:
        try:
            import RNA  # noqa: F401
            _RNA = __import__("RNA")
        except ImportError:
            _RNA = False
    return _RNA


def fold_available() -> bool:
    """Return True if ViennaRNA is installed and usable."""
    return _get_rna() is not None and _get_rna() is not False


def fold(seq: str) -> float:
    """
    Fold RNA sequence and return minimum free energy (MFE) in kcal/mol.

    Args:
        seq: RNA sequence (A/C/G/U or DNA; will be treated as RNA).

    Returns:
        MFE as float (kcal/mol).

    Raises:
        RuntimeError: If ViennaRNA is not installed (pip install ViennaRNA).
        ValueError: If sequence is invalid or folding fails.
    """
    rna = _get_rna()
    if rna is None or rna is False:
        raise RuntimeError(
            "ViennaRNA is required for mRNA folding. Install with: pip install ViennaRNA"
        )
    if not seq or not isinstance(seq, str):
        raise ValueError("Sequence must be a non-empty string")
    # ViennaRNA expects RNA; allow DNA letters and convert U <-> T if needed
    seq_clean = seq.upper().replace("T", "U").strip()
    valid = set("ACGU")
    if not all(c in valid for c in seq_clean):
        raise ValueError(
            f"Sequence contains invalid characters; use A/C/G/U or A/C/G/T. Got: {seq[:80]!r}..."
            if len(seq) > 80 else f"Sequence: {seq!r}"
        )
    try:
        structure, mfe = rna.fold(seq_clean)
    except Exception as e:
        raise ValueError(f"ViennaRNA fold failed: {e}") from e
    return float(mfe)


def fold_full(seq: str) -> tuple[str, float]:
    """Fold RNA sequence and return (dot_bracket_structure, MFE_kcal_mol).

    Same validation and T->U conversion as fold(), but returns both the
    secondary structure in dot-bracket notation and the MFE.

    Args:
        seq: RNA sequence (A/C/G/U or DNA; will be treated as RNA).

    Returns:
        Tuple of (dot_bracket_structure, MFE_as_float_kcal_mol).

    Raises:
        RuntimeError: If ViennaRNA is not installed.
        ValueError: If sequence is invalid or folding fails.
    """
    rna = _get_rna()
    if rna is None or rna is False:
        raise RuntimeError(
            "ViennaRNA is required for mRNA folding. Install with: pip install ViennaRNA"
        )
    if not seq or not isinstance(seq, str):
        raise ValueError("Sequence must be a non-empty string")
    seq_clean = seq.upper().replace("T", "U").strip()
    valid = set("ACGU")
    if not all(c in valid for c in seq_clean):
        raise ValueError(
            f"Sequence contains invalid characters; use A/C/G/U or A/C/G/T. Got: {seq[:80]!r}..."
            if len(seq) > 80 else f"Sequence: {seq!r}"
        )
    try:
        structure, mfe = rna.fold(seq_clean)
    except Exception as e:
        raise ValueError(f"ViennaRNA fold failed: {e}") from e
    return structure, float(mfe)
