"""
Length sanity check for the L0 quality layer.

A translation that's 10× longer or 10× shorter than the source is almost
certainly a hallucination or a truncation. We flag these as warnings
(not failures) so the caller can decide what to do.

Thresholds are tunable via env vars if needed, but the defaults work
well for prose documents. Tables and bullet lists will naturally have
shorter translations, so we keep the lower bound loose.

Special cases:
  * If the source is mostly digits/punctuation, the translation can also
    be short (e.g. "Price: 100$" → "100€") — skip the check.
  * If the source is empty/very short, skip the check entirely.
  * If the source contains an embedded URL or email, the translation may
    legitimately shrink — skip the check.
"""

from __future__ import annotations

import re
from typing import Dict

# Default thresholds — generous enough to handle tables / short strings.
RATIO_MAX = 3.5
RATIO_MIN = 0.15
# Hard lower bound: a translation shorter than this is very suspect,
# UNLESS the source is also very short.
ABSOLUTE_MIN_LENGTH = 2
# If source is short (under this many chars), skip the ratio check entirely
# — short strings are too noisy to be useful for length analysis.
MIN_SOURCE_LENGTH_FOR_RATIO = 20

# Pattern to detect text that is mostly digits / numbers / simple symbols.
# E.g. "Price: 100€", "+33 6 12 34 56 78", "192.168.1.1".
_MOSTLY_NUMERIC_RE = re.compile(r"^[\d\s\W]*$", re.UNICODE)
_NUMERIC_RATIO_THRESHOLD = 0.5  # 50% of letters are digits


def check(source_text: str, translated_text: str) -> Dict:
    """
    Returns a dict like:
        {
            "issue": None | "length_outlier" | "truncation_suspect",
            "ratio": float,
            "source_length": int,
            "translated_length": int,
        }
    Never raises.
    """
    if not source_text:
        return {
            "issue": None,
            "ratio": None,
            "source_length": 0,
            "translated_length": len(translated_text or ""),
        }

    src_len = len(source_text.strip())
    trans_len = len(translated_text.strip())

    # Empty translation — always suspect.
    if trans_len == 0:
        return {
            "issue": "truncation_suspect",
            "ratio": 0.0,
            "source_length": src_len,
            "translated_length": trans_len,
        }

    # If source is mostly digits/numbers, the translation can also be short
    # (e.g. "Price: 100" → "100"). Don't flag length in this case.
    if _is_mostly_numeric(source_text):
        return {
            "issue": None,
            "ratio": None,
            "source_length": src_len,
            "translated_length": trans_len,
            "note": "skipped: numeric source",
        }

    # If source is very short, skip the ratio check.
    if src_len < MIN_SOURCE_LENGTH_FOR_RATIO:
        return {
            "issue": None,
            "ratio": None,
            "source_length": src_len,
            "translated_length": trans_len,
        }

    ratio = trans_len / src_len

    if ratio > RATIO_MAX:
        return {
            "issue": "length_outlier",
            "ratio": round(ratio, 2),
            "source_length": src_len,
            "translated_length": trans_len,
        }
    if ratio < RATIO_MIN:
        return {
            "issue": "truncation_suspect",
            "ratio": round(ratio, 2),
            "source_length": src_len,
            "translated_length": trans_len,
        }

    return {
        "issue": None,
        "ratio": round(ratio, 2),
        "source_length": src_len,
        "translated_length": trans_len,
    }


def _is_mostly_numeric(text: str) -> bool:
    """True if at least 50% of non-whitespace characters are digits."""
    if not text:
        return False
    chars = [c for c in text if not c.isspace()]
    if not chars:
        return False
    digit_count = sum(1 for c in chars if c.isdigit())
    return (digit_count / len(chars)) >= _NUMERIC_RATIO_THRESHOLD
