import math
import zlib
import numpy as np
from collections import Counter

def shannon_entropy(s: str) -> float:
    if not s:
        return 0.0
    probs = [s.count(c) / len(s) for c in set(s)]
    return -sum(p * math.log2(p) for p in probs)

def bigram_entropy(s: str) -> float:
    if len(s) < 2:
        return 0.0
    bigrams = [s[i:i+2] for i in range(len(s) - 1)]
    counts = Counter(bigrams)
    total = len(bigrams)
    probs = [c / total for c in counts.values()]
    return -sum(p * math.log2(p) for p in probs)

def non_ascii_ratio(s: str) -> float:
    return sum(ord(c) > 127 for c in s) / len(s) if s else 0.0

def word_density(s: str) -> float:
    words = s.split()
    return math.log1p((len(s) / len(words))) if words else 0.0

def extract_features(s: str) -> np.ndarray:
    if not s:
        return np.zeros(10, dtype=float)

    n = len(s)
    encoded = s.encode("utf-8", errors="ignore")

    alpha_ratio = sum(c.isalpha() for c in s) / n
    digit_ratio = sum(c.isdigit() for c in s) / n
    padding_ratio = s.count("=") / n
    compressibility = len(zlib.compress(encoded)) / max(1, len(encoded))

    return np.array(
        [
            n,
            alpha_ratio,
            digit_ratio,
            padding_ratio,
            compressibility,
            shannon_entropy(s),
            bigram_entropy(s),
            non_ascii_ratio(s),
            word_density(s),
        ],
        dtype=float,
    )
