"""Gestion idempotente de bloques delimitados por sentinels HTML-comment dentro de un texto."""

import re


def upsert_sentinel_block(
    text: str, block_id: str, content: str, marker: str = "osintdb"
) -> str:
    """Inserta o reemplaza un bloque gestionado delimitado por sentinels HTML-comment.

    Un bloque gestionado queda envuelto entre dos comentarios HTML que actuan como
    sentinels:

        <!-- {marker}:begin id={block_id} -->
        ...contenido gestionado...
        <!-- {marker}:end id={block_id} -->

    Si el bloque (ambos sentinels con ese block_id) ya existe en el texto, su contenido
    se reemplaza por el nuevo manteniendo los sentinels. Si no existe, el bloque se anade
    al final del texto. La operacion es idempotente: aplicarla dos veces con el mismo
    content produce el mismo resultado.

    Args:
        text: Texto sobre el que operar (tipicamente el body de una nota Markdown de
            Obsidian). Puede contener otros bloques gestionados con ids distintos.
        block_id: Identificador unico del bloque dentro del texto. Se usa literal en
            la expresion regular, asi que se escapa con re.escape.
        content: Contenido a colocar entre los sentinels. No debe contener los propios
            sentinels.
        marker: Prefijo del namespace de los comentarios sentinel. Default "osintdb".
            Se usa literal en la expresion regular, asi que se escapa con re.escape.

    Returns:
        El texto resultante con el bloque insertado o reemplazado.

    Raises:
        ValueError: Si aparece solo uno de los dos sentinels del block_id (bloque
            corrupto) o si aparecen sentinels duplicados.
    """
    begin = f"<!-- {marker}:begin id={block_id} -->"
    end = f"<!-- {marker}:end id={block_id} -->"

    begin_re = re.escape(begin)
    end_re = re.escape(end)

    n_begin = len(re.findall(begin_re, text))
    n_end = len(re.findall(end_re, text))

    if n_begin != n_end:
        raise ValueError(
            f"Bloque corrupto para block_id={block_id!r} con marker={marker!r}: "
            f"se encontraron {n_begin} sentinel(s) de inicio y {n_end} de fin "
            f"(deben coincidir)."
        )

    if n_begin > 1:
        raise ValueError(
            f"Bloque duplicado para block_id={block_id!r} con marker={marker!r}: "
            f"se encontraron {n_begin} pares de sentinels (debe haber como maximo uno)."
        )

    if n_begin == 1:
        # El bloque existe: reemplazar todo lo que hay entre los sentinels.
        pattern = re.compile(begin_re + r".*?" + end_re, re.DOTALL)
        replacement = f"{begin}\n{content}\n{end}"
        return pattern.sub(lambda _: replacement, text, count=1)

    # El bloque no existe: anadirlo al final.
    block = f"{begin}\n{content}\n{end}\n"
    if text == "":
        return block
    if text.endswith("\n"):
        return f"{text}\n{block}"
    return f"{text}\n\n{block}"
