"""Matrix plumbing lifted from matrix-bridge (src/bot.py): message splitting, thread-root
detection, and a threaded-reply sender. Kept dependency-light so the rest of the bot is
testable without a live homeserver."""

MAX_MSG_CHARS = 30000  # well under Matrix's ~64KB event cap


def split_message(text, limit=MAX_MSG_CHARS):
    """Split text into <=limit-char chunks on newline boundaries (no truncation)."""
    if len(text) <= limit:
        return [text]
    chunks, buf = [], ""
    for line in text.splitlines(keepends=True):
        while len(line) > limit:
            if buf:
                chunks.append(buf)
                buf = ""
            chunks.append(line[:limit])
            line = line[limit:]
        if len(buf) + len(line) > limit:
            chunks.append(buf)
            buf = ""
        buf += line
    if buf:
        chunks.append(buf)
    return chunks


def thread_root_of(event):
    """Return the thread root event_id if this message is a threaded reply, else None."""
    relates = (getattr(event, "source", None) or {}).get("content", {}).get("m.relates_to") or {}
    if relates.get("rel_type") == "m.thread":
        return relates.get("event_id")
    return None


def thread_content(text, thread_root):
    """Build an m.room.message content dict, threaded under thread_root when given."""
    content = {"msgtype": "m.text", "body": text}
    if thread_root:
        content["m.relates_to"] = {
            "rel_type": "m.thread",
            "event_id": thread_root,
            "is_falling_back": True,
            "m.in_reply_to": {"event_id": thread_root},
        }
    return content


def make_say(client):
    """Return an async say(room_id, text, thread_root=None) bound to a matrix-nio client."""
    async def say(room_id, text, thread_root=None):
        for chunk in split_message(text):
            await client.room_send(room_id, "m.room.message", thread_content(chunk, thread_root))
    return say


def reply_content(text, reply_to_event_id):
    """Build a plain (non-threaded) reply: shows in the MAIN timeline as a reply to
    reply_to_event_id, unlike thread_content() which lands the message inside a thread."""
    content = {"msgtype": "m.text", "body": text}
    if reply_to_event_id:
        content["m.relates_to"] = {"m.in_reply_to": {"event_id": reply_to_event_id}}
    return content


def make_reply(client):
    """Return an async reply(room_id, text, reply_to) that posts a plain main-timeline reply —
    the brief 'proposed X — see thread' nudge alongside the in-thread proposal card."""
    async def reply(room_id, text, reply_to):
        for chunk in split_message(text):
            await client.room_send(room_id, "m.room.message", reply_content(chunk, reply_to))
    return reply
