"""F5 — String monomers (MC_40–MC_47)."""

_MAX_STR_LEN = 4096


def concat(a: str, b: str) -> str:
    """MC_40: concatenate strings (capped at 4096 chars)."""
    return (a + b)[:_MAX_STR_LEN]


def substr(s: str, start: int, end: int) -> str:
    """MC_41: substring [start, end)."""
    return s[start:end]


def trim(s: str) -> str:
    """MC_42: trim whitespace."""
    return s.strip()


def length(s: str) -> int:
    """MC_43: string length."""
    return len(s)


def contains(s: str, sub: str) -> bool:
    """MC_44: check if s contains sub."""
    return sub in s


def starts_with(s: str, prefix: str) -> bool:
    """MC_45: check prefix."""
    return s.startswith(prefix)


def ends_with(s: str, suffix: str) -> bool:
    """MC_46: check suffix."""
    return s.endswith(suffix)


def char_at(s: str, idx: int) -> str | None:
    """MC_47: character at index. Returns None if out of bounds."""
    if idx < 0 or idx >= len(s):
        return None
    return s[idx]
