"""Offline SSE event framing demo: bytes -> UTF-8 -> lines -> events.

Python 3.9+. No HTTP client, reconnect logic, id/retry state or model calls.
Malformed UTF-8 raises UnicodeDecodeError (stricter than browser decoding).
"""

import argparse
import codecs
import json


def events(chunks):
    """Yield {'event': name, 'data': text} from an iterable of byte chunks.

    Only a blank line dispatches an event. An unfinished event at EOF is
    discarded. An initial UTF-8 BOM is ignored; later BOMs remain ordinary text.
    id/retry and unknown fields are ignored deliberately in this small demo.
    """
    decoder = codecs.getincrementaldecoder("utf-8")(errors="strict")
    line = []
    data = []
    event_name = ""
    first_character = True
    skip_lf = False

    def accept_line(text):
        nonlocal data, event_name
        if text == "":
            ready = None
            if data:
                ready = {"event": event_name or "message", "data": "\n".join(data)}
            data = []
            event_name = ""
            return ready
        if text.startswith(":"):
            return None
        field, separator, value = text.partition(":")
        if not separator:
            value = ""
        if value.startswith(" "):
            value = value[1:]
        if field == "data":
            data.append(value)
        elif field == "event":
            event_name = value
        return None

    def accept_text(text):
        nonlocal first_character, skip_lf, line
        for char in text:
            if first_character:
                first_character = False
                if char == "\ufeff":
                    continue
            if skip_lf:
                skip_lf = False
                if char == "\n":
                    continue
            if char == "\r" or char == "\n":
                ready = accept_line("".join(line))
                line = []
                skip_lf = char == "\r"
                if ready is not None:
                    yield ready
            else:
                line.append(char)

    for chunk in chunks:
        yield from accept_text(decoder.decode(chunk, final=False))
    yield from accept_text(decoder.decode(b"", final=True))
    # EOF is not a blank line: do not dispatch the pending line/event here.


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("path", help="Local UTF-8 SSE fixture")
    parser.add_argument("--chunk-size", type=int, default=7)
    args = parser.parse_args()
    if args.chunk_size < 1:
        parser.error("--chunk-size must be positive")
    with open(args.path, "rb") as stream:
        chunks = iter(lambda: stream.read(args.chunk_size), b"")
        for event in events(chunks):
            print(json.dumps(event, ensure_ascii=False))


if __name__ == "__main__":
    main()
