"""MLLP (Minimal Lower Layer Protocol) framing over TCP.

Analyzers wrap each HL7 message in:  <VT> message <FS><CR>
  VT = 0x0B (start block)
  FS = 0x1C (end block)
  CR = 0x0D (carriage return)
"""

from __future__ import annotations

from typing import Iterator

VT = 0x0B
FS = 0x1C
CR = 0x0D


class MllpReader:
    """Accumulates bytes off a socket stream and yields complete HL7 messages."""

    def __init__(self) -> None:
        self._buffer = bytearray()

    def feed(self, chunk: bytes) -> Iterator[str]:
        """Add received bytes; yield each fully-framed message as a string."""
        self._buffer.extend(chunk)
        while True:
            start = self._buffer.find(VT)
            if start == -1:
                # No start byte yet — drop noise to avoid unbounded growth.
                self._buffer.clear()
                return
            end = self._buffer.find(FS, start + 1)
            if end == -1:
                # Incomplete message; keep from the start byte and wait for more.
                if start > 0:
                    del self._buffer[:start]
                return
            message = self._buffer[start + 1 : end]
            # Consume up to and including the trailing CR after FS (if present).
            consume_to = end + 1
            if consume_to < len(self._buffer) and self._buffer[consume_to] == CR:
                consume_to += 1
            del self._buffer[:consume_to]
            yield message.decode("utf-8", errors="replace")


def frame(message: str) -> bytes:
    """Wrap an HL7 message in MLLP framing bytes, ready to send."""
    return bytes([VT]) + message.encode("utf-8") + bytes([FS, CR])
