"""Text chunking for hierarchical embeddings.

Splits long texts into overlapping chunks for embedding.
Prefers paragraph boundaries, then sentence, then whitespace.
Never splits mid-word.
"""

CHUNK_SIZE = 1500
OVERLAP = 200


def chunk_text(
    text: str | None,
    chunk_size: int = CHUNK_SIZE,
    overlap: int = OVERLAP,
) -> list[str]:
    """Split text into overlapping chunks.

    Args:
        text: The text to chunk. Returns [] for empty/None.
        chunk_size: Target size per chunk in characters.
        overlap: Number of overlapping characters between adjacent chunks.

    Returns:
        List of text chunks. Short texts return a single-element list.
    """
    if not text or not text.strip():
        return []

    text = text.strip()
    if len(text) <= chunk_size:
        return [text]

    chunks = []
    start = 0

    while start < len(text):
        end = start + chunk_size

        if end >= len(text):
            chunks.append(text[start:])
            break

        split = _find_split(text, start, end)
        chunks.append(text[start:split].rstrip())

        start = max(start + 1, split - overlap)

    return chunks


def _find_split(text: str, start: int, end: int) -> int:
    """Find the best split point near end.

    Priority: paragraph boundary > sentence boundary > whitespace.
    Search window: end-200 to end.
    """
    search_start = max(start + 1, end - 200)
    window = text[search_start:end]

    para_idx = window.rfind("\n\n")
    if para_idx != -1:
        return search_start + para_idx + 2

    for sep in [". ", "? ", "! "]:
        sent_idx = window.rfind(sep)
        if sent_idx != -1:
            return search_start + sent_idx + len(sep)

    space_idx = window.rfind(" ")
    if space_idx != -1:
        return search_start + space_idx + 1

    return end
