"""Heuristic cyclic peptide scoring backend."""

from __future__ import annotations

import math

from peptidegym.peptide.properties import molecular_weight


class HeuristicCyclicScorer:
    """Scores peptide sequences for cyclic peptide drug fitness."""

    def score(self, sequence: str, cyclization_type: str = "none", **kwargs) -> dict[str, float]:
        length = len(sequence)

        # ── cyclization_bonus (0-1) ──
        cyclization_bonus = 0.0
        if cyclization_type == "head_to_tail":
            cyclization_bonus = 1.0 if length >= 4 else 0.0
        elif cyclization_type == "disulfide":
            cyclization_bonus = 1.0 if sequence.count("C") >= 2 else 0.0
        elif cyclization_type == "sidechain":
            has_k = "K" in sequence
            has_de = "D" in sequence or "E" in sequence
            cyclization_bonus = 1.0 if (has_k and has_de) else 0.0

        # ── permeability_score (0-1): Lipinski-like ──
        mw = molecular_weight(sequence)
        mw_score = 1.0 if mw < 800 else max(0.0, 1.0 - (mw - 800) / 400.0)

        # H-bond donors: S, T, N, Q, K, R, H, W, Y
        hbd_residues = "STNQKRHWY"
        hbd_count = sum(1 for aa in sequence if aa in hbd_residues)
        hbd_score = 1.0 if hbd_count <= 5 else max(0.0, 1.0 - (hbd_count - 5) / 5.0)

        # N-methylation proxy: proline count (natural N-methylation)
        pro_frac = sequence.count("P") / max(1, length)
        nmethyl_score = min(1.0, pro_frac * 5.0)  # bonus for proline content

        permeability_score = 0.45 * mw_score + 0.35 * hbd_score + 0.20 * nmethyl_score

        # ── structural_score (0-1) ──
        if length < 4:
            structural_score = max(0.0, length / 4.0 * 0.3)  # ring strain penalty
        elif 6 <= length <= 12:
            structural_score = 1.0
        elif length <= 20:
            structural_score = max(0.3, 1.0 - (length - 12) / 16.0)
        else:
            structural_score = max(0.0, 0.3 - (length - 20) / 40.0)

        # ── synthetic_score (0-1) ──
        # Penalize rare residues
        rare_penalty = sequence.count("W") * 0.1 + sequence.count("M") * 0.05
        rare_penalty = min(1.0, rare_penalty)

        # Reward common building blocks
        common_residues = "AGLVFP"
        common_frac = sum(1 for aa in sequence if aa in common_residues) / max(1, length)

        synthetic_score = max(0.0, min(1.0, 0.5 * (1.0 - rare_penalty) + 0.5 * common_frac))

        return {
            "cyclization_bonus": round(cyclization_bonus, 6),
            "permeability_score": round(min(1.0, max(0.0, permeability_score)), 6),
            "structural_score": round(min(1.0, max(0.0, structural_score)), 6),
            "synthetic_score": round(min(1.0, max(0.0, synthetic_score)), 6),
        }
