"""Chunking por sentence boundaries con sliding window overlap.

Validado empiricamente en notebook 06 (gliner_glirel_tuning) para pipelines
NER+RE con GLiNER2. Corrige el bug de bucle infinito de la version naive
cuando una frase supera max_chars.
"""

from __future__ import annotations

import re


def chunk_with_overlap(
    text: str,
    max_chars: int = 1500,
    overlap_sentences: int = 2,
) -> list[dict]:
    """Split text into chunks with sentence-level sliding window overlap.

    Each chunk has up to `max_chars` characters. Each chunk after the first
    starts with the last `overlap_sentences` sentences of the previous chunk
    if they fit. If a single sentence exceeds max_chars, it is force-included
    (chunk size will exceed max_chars rather than infinite-loop).

    Args:
        text: Input text to split. Sentences are detected by [.!?] followed by whitespace.
        max_chars: Maximum characters per chunk (soft limit; exceeded if a single
                   sentence is longer than max_chars to avoid infinite loop).
        overlap_sentences: Number of trailing sentences of the previous chunk to
                           prepend to the next chunk. 0 disables overlap.

    Returns:
        list of dicts: [{"text": str, "sentences": list[str]}, ...]
        Empty list if text is empty or contains only whitespace.
    """
    if not text or not text.strip():
        return []

    sentences = re.split(r"(?<=[\.!?])\s+", text)
    sentences = [s.strip() for s in sentences if s.strip()]
    if not sentences:
        return []

    chunks: list[dict] = []
    i = 0
    while i < len(sentences):
        current_sents: list[str] = []
        current_len = 0

        # Overlap desde el chunk anterior
        if chunks and overlap_sentences > 0:
            prev_sents = chunks[-1]["sentences"][-overlap_sentences:]
            overlap_len = sum(len(s) + 1 for s in prev_sents)
            next_len = len(sentences[i]) + 1
            if overlap_len + next_len <= max_chars:
                current_sents = list(prev_sents)
                current_len = overlap_len

        # AVANCE FORZADO: meter al menos UNA frase aunque exceda max_chars
        # (evita bucle infinito con frases muy largas)
        current_sents.append(sentences[i])
        current_len += len(sentences[i]) + 1
        i += 1

        # Seguir agregando frases mientras quepan
        while i < len(sentences) and current_len + len(sentences[i]) + 1 <= max_chars:
            current_sents.append(sentences[i])
            current_len += len(sentences[i]) + 1
            i += 1

        chunks.append({"text": " ".join(current_sents), "sentences": current_sents})

    return chunks
