from __future__ import annotations
import re
import base64
from textual import events
from textual.app import ComposeResult
from textual.containers import Vertical, Horizontal
from textual.message import Message
from textual.widgets import Static, Button, TextArea
from textual.widgets import Input
from rich.syntax import Syntax
from rich.markup import escape as markup_escape
from crypto_utils import fmt_time

_RE_BOLD = re.compile(r'\*\*(.+?)\*\*')
_RE_ITALIC_STAR = re.compile(r'(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)')
_RE_ITALIC_UNDER = re.compile(r'(?<!_)_(?!_)(.+?)(?<!_)_(?!_)')
_RE_UNDERLINE = re.compile(r'__(.+?)__')
_RE_STRIKE = re.compile(r'~~(.+?)~~')
_RE_CODE = re.compile(r'`([^`]+)`')

def _format_markdown(text: str) -> str:
    text = markup_escape(text)

    lines = text.split('\n')
    result_lines = []

    for line in lines:
        if line.startswith('## '):
            line = f"[bold cyan]{line[3:]}[/bold cyan]"
        elif line.startswith('# '):
            line = f"[bold magenta underline]{line[2:]}[/bold magenta underline]"
        else:
            line = _RE_BOLD.sub(r'[bold]\1[/bold]', line)
            line = _RE_ITALIC_STAR.sub(r'[italic]\1[/italic]', line)
            line = _RE_ITALIC_UNDER.sub(r'[italic]\1[/italic]', line)
            line = _RE_UNDERLINE.sub(r'[underline]\1[/underline]', line)
            line = _RE_STRIKE.sub(r'[strike]\1[/strike]', line)
            line = _RE_CODE.sub(r'[reverse]\1[/reverse]', line)

        result_lines.append(line)

    return '\n'.join(result_lines)

class SpoilerText(Static):

    DEFAULT_CSS = """
    SpoilerText {
        width: auto;
        height: auto;
        background: $text;
        color: $text;
    }
    SpoilerText.revealed {
        background: transparent;
        color: $text;
    }
    """

    def __init__(self, content: str, **kwargs):
        self._content = content
        self._revealed = False
        super().__init__("█" * min(len(content), 20) if len(content) > 0 else "███", **kwargs)

    def on_click(self, event: events.Click) -> None:
        self._revealed = not self._revealed
        if self._revealed:
            self.update(self._content)
            self.add_class("revealed")
        else:
            self.update("█" * min(len(self._content), 20) if len(self._content) > 0 else "███")
            self.remove_class("revealed")
        event.stop()

_color_cache: dict[str, tuple[str, str | None]] = {}
_color_cache_default: tuple[str, str | None] | None = None

_msg_spacing_cache: int | None = None

def _get_user_color(app, alias: str, is_self: bool = False, message: str = "", permissions: set[str] | None = None, msg_meta: dict | None = None) -> tuple[str, str | None]:
    global _color_cache_default

    try:
        if hasattr(app, "_color_rules_manager") and app._color_rules_manager:
            return app._color_rules_manager.get_color_for_user(
                alias=alias,
                message=message,
                permissions=permissions,
                is_self=is_self,
                msg_meta=msg_meta
            )

        if _color_cache_default is None and hasattr(app, "db"):
            import json
            raw = app.db.get_setting("appearance_color_rules")
            my_color = "#ffffff"
            if raw:
                try:
                    data = json.loads(raw)
                    my_color = data.get("my_color", "#ffffff")
                except Exception:
                    pass
            default = app.db.get_setting("appearance_default_color") or "#cccccc"
            _color_cache_default = (default, my_color)

        if _color_cache_default:
            if is_self:
                return (_color_cache_default[1], None)
            return (_color_cache_default[0], None)
    except Exception:
        pass

    return ("#ffffff", None)

def clear_color_cache():
    global _color_cache, _color_cache_default, _msg_spacing_cache
    _color_cache = {}
    _color_cache_default = None
    _msg_spacing_cache = None

def _get_color_for_message_group(app, messages: list[dict], alias: str, is_self: bool = False, permissions: set[str] | None = None) -> tuple[str, str | None]:
    try:
        if hasattr(app, "_color_rules_manager") and app._color_rules_manager:
            return app._color_rules_manager.get_color_for_message_group(
                messages=messages,
                alias=alias,
                is_self=is_self,
                permissions=permissions
            )
    except Exception:
        pass

    if messages:
        last_msg = messages[-1]
        return _get_user_color(
            app, alias, is_self=is_self,
            message=last_msg.get("body") or "",
            permissions=permissions,
            msg_meta=last_msg.get("meta")
        )

    return _get_user_color(app, alias, is_self=is_self)

def _styled_name(alias: str, color1: str, color2: str | None = None) -> str:
    safe_alias = markup_escape(str(alias))

    if color2:

        mid = len(safe_alias) // 2
        if mid == 0:
            mid = 1
        part1 = safe_alias[:mid]
        part2 = safe_alias[mid:]
        return f"[{color1}]{part1}[/][{color2}]{part2}[/]"
    else:
        return f"[{color1}]{safe_alias}[/]"

def _fmt_bytes(n: int | None) -> str:
    try:
        v = float(int(n or 0))
    except Exception:
        return "0 B"
    units = ["B", "KB", "MB", "GB", "TB"]
    i = 0
    while v >= 1024.0 and i < len(units) - 1:
        v /= 1024.0
        i += 1
    if i == 0:
        return f"{int(v)} {units[i]}"
    return f"{v:.1f} {units[i]}"

class FileCard(Vertical):
    def __init__(self, meta: dict, caption: str = ""):
        super().__init__(classes="file-card")
        self.meta = meta or {}
        self.caption = caption or ""

    def compose(self) -> ComposeResult:
        name = self.meta.get("name") or self.meta.get("file_id") or "file"
        size = _fmt_bytes(self.meta.get("bytes_total"))

        yield Static(f"📄 [b]{name}[/b]  {size}", classes="file-card-header-simple")
        if self.caption.strip():
            yield Static(self.caption, classes="file-card-caption")
        yield Button("Download", id="file_download", variant="primary", classes="file-download-btn")

class ImageCard(Vertical):
    def __init__(self, meta: dict, caption: str = ""):
        super().__init__(classes="image-card")
        self.meta = meta or {}
        self.caption = caption or ""
        self._preview_tmp_path: str | None = None

    def compose(self) -> ComposeResult:
        name = self.meta.get("name") or self.meta.get("file_id") or "image"
        size = _fmt_bytes(self.meta.get("bytes_total"))

        yield Static(f"📷 [b]{name}[/b]  {size}", classes="image-card-header-simple")
        if self.caption.strip():
            yield Static(self.caption, classes="image-card-caption")
        with Horizontal(classes="image-card-actions"):
            yield Button("Preview", id="image_preview", variant="primary")
            yield Button("Download", id="file_download", variant="primary")

    def ensure_preview_file(self) -> str | None:
        if self._preview_tmp_path:
            return self._preview_tmp_path

        b64 = self.meta.get("preview_png_b64")
        if not isinstance(b64, str) or not b64.strip():
            return None
        if len(b64) > 8_000_000:

            return None

        try:
            png = base64.b64decode(b64.encode("ascii"))
            import tempfile
            import pathlib

            tmp_dir = pathlib.Path(tempfile.gettempdir()) / "e2e-client-previews"
            tmp_dir.mkdir(parents=True, exist_ok=True)
            tmp_path = tmp_dir / f"preview_{abs(hash(b64))}.png"
            tmp_path.write_bytes(png)
            self._preview_tmp_path = str(tmp_path)
            return self._preview_tmp_path
        except Exception:
            return None

class ReplyBar(Static):
    def __init__(self):
        super().__init__("", id="reply_bar")

    def show_reply(self, alias: str, snippet: str):
        self.update(self.app.t("replying_to", alias=alias, snippet=snippet))

    def hide_reply(self):
        self.update("")

class InfoTab(Static):
    class Toggled(Message):
        def __init__(self, sender: "InfoTab"):
            super().__init__()
            self.sender = sender

    def set_expanded(self, expanded: bool) -> None:
        self.update("▾" if expanded else "▸")

    def on_click(self, event: events.Click) -> None:
        self.post_message(self.Toggled(self))
        event.stop()

class CallTab(Static):
    class Pressed(Message):
        def __init__(self, sender: "CallTab"):
            super().__init__()
            self.sender = sender

    def on_click(self, event: events.Click) -> None:
        self.post_message(self.Pressed(self))
        event.stop()

class _PanelResizeHandle(Static):
    def __init__(self):
        super().__init__("◢", id="peer_search_resize")
        self.dragging = False
        self.start_x = 0
        self.start_y = 0
        self.start_w = 0
        self.start_h = 0

    def on_mouse_down(self, event: events.MouseDown) -> None:
        self.dragging = True
        self.capture_mouse()
        self.start_x = event.screen_x
        self.start_y = event.screen_y
        parent = self.parent
        if parent is not None:
            self.start_w = parent.size.width
            self.start_h = parent.size.height

    def on_mouse_move(self, event: events.MouseMove) -> None:
        if not self.dragging:
            return
        parent = self.parent
        if parent is None:
            return

        dx = event.screen_x - self.start_x
        dy = event.screen_y - self.start_y
        new_w = self.start_w + dx
        new_h = self.start_h + dy

        screen_w = self.app.size.width
        screen_h = self.app.size.height

        new_w = max(24, min(int(new_w), int(screen_w - 2)))
        new_h = max(6, min(int(new_h), int(screen_h - 2)))

        parent.styles.width = new_w
        parent.styles.height = new_h

    def on_mouse_up(self, event: events.MouseUp) -> None:
        if self.dragging:
            self.dragging = False
            self.release_mouse()

class PeerSearchPanel(Vertical):

    class Submitted(Message):
        def __init__(self, sender: "PeerSearchPanel", alias: str):
            super().__init__()
            self.sender = sender
            self.alias = alias

    class Closed(Message):
        def __init__(self, sender: "PeerSearchPanel"):
            super().__init__()
            self.sender = sender

    def compose(self) -> ComposeResult:
        with Horizontal(id="peer_search_header"):
            yield Static("", id="peer_search_title")
            yield Static("×", id="peer_search_close")
        self.input = Input(placeholder="alias", id="peer_search_input")
        yield self.input
        with Horizontal(id="peer_search_footer"):
            yield Static("", id="peer_search_footer_spacer")
            yield Static(self.app.t("ok"), id="peer_search_ok")
            yield Static(self.app.t("cancel"), id="peer_search_cancel")
            yield _PanelResizeHandle()

    def on_mount(self) -> None:
        try:
            self.query_one("#peer_search_title", Static).update(self.app.t("dlg_peer_alias"))
        except Exception:
            pass

    def open(self, *, initial: str = "") -> None:
        self.styles.display = "block"
        try:
            self.input.value = initial
            self.input.cursor_position = len(self.input.value)
            self.set_focus(self.input)
        except Exception:
            pass

    def close(self) -> None:
        self.styles.display = "none"

    def _submit(self) -> None:
        v = (getattr(self, "input", None).value or "").strip()
        if not v:
            return
        self.post_message(self.Submitted(self, v))

    def on_input_submitted(self, event: Input.Submitted) -> None:
        if event.input.id == "peer_search_input":
            self._submit()
            event.stop()

    def on_click(self, event: events.Click) -> None:
        wid = getattr(getattr(event, "widget", None), "id", None)
        if wid in ("peer_search_close", "peer_search_cancel"):
            self.post_message(self.Closed(self))
            event.stop()
            return
        if wid == "peer_search_ok":
            self._submit()
            event.stop()

    def on_key(self, event: events.Key) -> None:
        if event.key == "escape":
            self.post_message(self.Closed(self))
            event.stop()

class ScrollableCode(Vertical):
    can_focus = True

    def __init__(self, code: str, language: str):
        super().__init__(classes="code-content-wrapper")
        self.code = code
        self.language = language

    def compose(self) -> ComposeResult:

        yield Static(
            Syntax(
                self.code,
                self.language if self.language else "text",
                line_numbers=True,
                word_wrap=False,
                background_color="default",
                theme="monokai",
            ),
            classes="code-content",
        )

    def on_mount(self) -> None:
        lines = self.code.count("\n") + 1
        self.styles.height = min(lines, 10)

    def on_click(self, event: events.Click) -> None:
        self.focus()

    def on_key(self, event: events.Key) -> None:
        if event.key == "up":
            self.scroll_up()
        elif event.key == "down":
            self.scroll_down()
        elif event.key == "pageup":
            self.scroll_page_up()
        elif event.key == "pagedown":
            self.scroll_page_down()
        elif event.key == "home":
            self.scroll_home()
        elif event.key == "end":
            self.scroll_end()

class CodeBlock(Vertical):
    def __init__(self, code: str, language: str = ""):
        super().__init__(classes="code-block")
        self.code = code
        self.language = language

    def _compute_desired_width(self) -> int:
        lines = self.code.splitlines() or [""]
        line_count = len(lines)

        comfort_padding = 5

        digits = len(str(max(1, line_count)))
        gutter = digits + 4

        longest = 0
        for line in lines:

            line_len = len(line.expandtabs(4))
            if line_len > longest:
                longest = line_len

        content_width = gutter + longest

        lang = (self.language or "text").strip()
        header_min = 4 + 4 + max(4, len(lang)) + 6

        return max(content_width, header_min, 20) + comfort_padding

    def _apply_width(self) -> None:
        try:
            parent_width = self.parent.size.width if self.parent is not None else 0
        except Exception:
            parent_width = 0

        max_allowed = max(20, (parent_width or self.app.size.width) - 4)
        desired = self._compute_desired_width()
        self.styles.width = min(desired, max_allowed)

    def on_mount(self) -> None:
        self._apply_width()

    def on_resize(self, event) -> None:
        self._apply_width()

    def compose(self) -> ComposeResult:

        with Horizontal(classes="code-header"):
            yield Static(self.language if self.language else "text", classes="code-lang")
            yield Button("📋", classes="code-btn copy-btn", id="code_copy")
            yield Button("⛶", classes="code-btn fs-btn", id="code_fullscreen")

        yield ScrollableCode(self.code, self.language)

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.has_class("fs-btn"):
            from ui.dialogs import CodeViewDialog

            self.app.push_screen(CodeViewDialog(self.code, self.language))
        elif event.button.has_class("copy-btn"):
            try:
                import pyperclip

                pyperclip.copy(self.code)
                self.app.notify(self.app.t("copied"))
            except ImportError:
                self.app.notify(self.app.t("pyperclip_not_installed"))
        event.stop()

class MessageWidget(Vertical):
    class ReplyLink(Static):
        class Clicked(Message):
            def __init__(self, sender: "MessageWidget.ReplyLink", target_msg_id: str):
                super().__init__()
                self.sender = sender
                self.target_msg_id = target_msg_id

        def __init__(self, target_msg_id: str, text: str):
            super().__init__(text, classes="msg-header", expand=True, shrink=False)
            self._target_msg_id = target_msg_id

        def on_click(self, event: events.Click) -> None:
            self.post_message(MessageWidget.ReplyLink.Clicked(self, self._target_msg_id))
            event.stop()

    class JumpRequested(Message):
        def __init__(self, sender: "MessageWidget", target_msg_id: str):
            super().__init__()
            self.sender = sender
            self.target_msg_id = target_msg_id

    class ContextRequested(Message):
        def __init__(self, sender: "MessageWidget", x: int = 0, y: int = 0):
            super().__init__()
            self.sender = sender
            self.x = x
            self.y = y

    class HistoryRequested(Message):
        def __init__(self, sender: "MessageWidget", msg_id: str):
            super().__init__()
            self.sender = sender
            self.msg_id = msg_id

    class InlineEditSubmitted(Message):
        def __init__(self, sender: "MessageWidget", msg_id: str, new_text: str):
            super().__init__()
            self.sender = sender
            self.msg_id = msg_id
            self.new_text = new_text

    class FileDownloadRequested(Message):
        def __init__(self, sender: "MessageWidget", file_meta: dict):
            super().__init__()
            self.sender = sender
            self.file_meta = file_meta

    class ImagePreviewRequested(Message):
        def __init__(self, sender: "MessageWidget", file_meta: dict):
            super().__init__()
            self.sender = sender
            self.file_meta = file_meta

    def __init__(self, msg: dict, show_header: bool, indent: int, reply_preview, group_messages: list[dict] | None = None, msg_class: str = "grouped"):
        super().__init__(classes="msg")
        self.msg = msg
        self.show_header = show_header
        self.indent = indent
        self.reply_preview = reply_preview
        self.group_messages = group_messages
        self.msg_class = msg_class
        self.expanded = False
        self._click_map: dict[int, tuple[str, str | None]] = {}
        self._editing: bool = False
        self._edit_initial: str = ""
        self._last_render_hash: str = ""

        self.styles.width = "1fr"

        self.can_focus = False

    auto_refresh = False

    def _compute_render_hash(self) -> str:
        return f"{self.msg.get('body')}|{self.msg.get('edited_ts')}|{self.msg.get('local_state')}|{self.msg.get('kind')}"

    def on_mount(self):
        self.refresh_content()

    def _s(self, key: str) -> str:
        try:
            return self.app.t(key)
        except Exception:
            fallback = {
                "read_more": "Read more…",
                "show_less": "Show less",
                "edited_tag": "edited",
                "pending_tag": "…",
                "copied": "Copied to clipboard",
            }
            return fallback.get(key, key)

    def _call_plugin_render_hook(self, sender: str, body: str, meta: dict) -> dict:
        result = {"text": body, "css_classes": [], "hidden": False}
        try:
            from plugins import get_plugin_loader, HookType
            loader = get_plugin_loader()

            plugin = loader.get_plugin("message_history")
            if plugin and plugin.enabled:
                plugin_result = plugin.on_message_render(body, sender, meta)
                if plugin_result is not None:
                    if isinstance(plugin_result, dict):
                        result["text"] = plugin_result.get("text", body)
                        result["css_classes"] = plugin_result.get("css_classes", [])
                        result["hidden"] = plugin_result.get("hidden", False)
                    elif isinstance(plugin_result, str):
                        result["text"] = plugin_result
                    return result

            hook_result = loader.call_hook_chain(
                HookType.ON_MESSAGE_RENDER,
                body,
                sender,
                meta
            )
            if hook_result is not None and hook_result is not False:
                if isinstance(hook_result, dict):
                    result["text"] = hook_result.get("text", body)
                    result["css_classes"] = hook_result.get("css_classes", [])
                    result["hidden"] = hook_result.get("hidden", False)
                elif isinstance(hook_result, str):
                    result["text"] = hook_result
        except Exception:
            pass
        return result

    def refresh_content(self, force: bool = False):
        current_hash = self._compute_render_hash()
        if not force and self._last_render_hash == current_hash and self.children:
            return
        self._last_render_hash = current_hash

        self.query("*").remove()

        sender = self.msg["sender_alias"]
        ts = self.msg["ts"]
        body = self.msg.get("body") or ""
        kind = self.msg.get("kind") or "text"
        meta = self.msg.get("meta") or {}
        edited = self.msg.get("edited_ts")
        pending = self.msg.get("local_state") == "pending"

        is_deleted = meta.get("_deleted", False)
        render_result = self._call_plugin_render_hook(sender, body, {
            "id": self.msg.get("msg_id") or self.msg.get("id"),
            "ts": ts,
            "kind": kind,
            "meta": meta,
            "edited": edited,
            "pending": pending,
            "_deleted": is_deleted,
            **meta
        })

        body = render_result["text"]
        extra_css_classes = render_result.get("css_classes", [])
        is_hidden = render_result.get("hidden", False)

        if is_hidden:
            return

        if self._editing:
            self._mount_inline_editor(sender=sender, ts=ts, edited=edited, pending=pending)
            return

        global _msg_spacing_cache
        if _msg_spacing_cache is None:
            try:
                _msg_spacing_cache = int(self.app.db.get_setting("appearance_msg_spacing") or 1)
            except Exception:
                _msg_spacing_cache = 1
        msg_spacing = _msg_spacing_cache

        try:
            if self.msg_class in ("break_user", "break_time"):
                self.styles.margin = (msg_spacing, 0, 0, 0)
            else:
                self.styles.margin = (0, 0, 0, 0)
        except Exception:
            pass

        self.styles.padding_left = self.indent * 2

        if self.indent > 0:
            self.add_class("msg_indented")
        else:
            self.remove_class("msg_indented")

        if self.show_header:

            is_self = False
            try:

                if hasattr(self.app, "net") and self.app.net and hasattr(self.app.net, "ident") and self.app.net.ident:
                    my_alias = self.app.net.ident.alias
                    if my_alias and sender == my_alias:
                        is_self = True
            except Exception:
                pass

            if self.group_messages:
                color1, color2 = _get_color_for_message_group(
                    self.app, self.group_messages, sender, is_self=is_self
                )
            else:
                color1, color2 = _get_user_color(self.app, sender, is_self=is_self, message=body, msg_meta=meta)
            styled_sender = _styled_name(sender, color1, color2)

            h = f"[b]{styled_sender}[/b]  {fmt_time(ts)}"
            if edited:
                h += f"  ({self._s('edited_tag')})"
            if pending:
                h += f"  ({self._s('pending_tag')})"

            header_row = Horizontal(classes="msg_header_row")

            self.mount(header_row)
            header_static = Static(h, classes="msg-header", expand=True, shrink=False)
            header_row.mount(header_static)

            if edited:
                try:
                    from plugins import get_plugin_loader
                    loader = get_plugin_loader()
                    plugin = loader.get_plugin("message_history")
                    msg_id = str(self.msg.get("msg_id") or "")
                    if plugin and plugin.enabled and msg_id:
                        try:
                            count = int(plugin.get_history_count(msg_id))
                        except Exception:
                            count = 0
                        if count > 0:
                            header_row.mount(Button(f"History {count}", id=f"hist_{msg_id}", classes="msg_hist_btn"))
                except Exception:
                    pass

        if self.reply_preview and self.msg.get("reply_to"):
            ra = self.reply_preview.get("sender_alias", "?")
            rb = self.reply_preview.get("body", "")
            if len(rb) > 80:
                rb = rb[:80] + "..."
            reply_target = str(self.msg["reply_to"])
            safe_ra = markup_escape(str(ra))
            safe_rb = markup_escape(str(rb))
            self.mount(MessageWidget.ReplyLink(reply_target, f"[dim]│ ↩ {safe_ra}: {safe_rb}[/dim]"))

        if kind == "file":
            mime = (meta.get("mime") or "") if isinstance(meta, dict) else ""
            if isinstance(mime, str) and mime.startswith("image/"):
                self.mount(ImageCard(meta, caption=body))
            else:
                self.mount(FileCard(meta, caption=body))
            return

        parts = re.split(r"```(\w*)\n(.*?)```", body, flags=re.DOTALL)

        for i in range(0, len(parts), 3):
            text_part = parts[i]
            if text_part.strip():
                spoiler_parts = re.split(r'\|\|(.+?)\|\|', text_part)
                text_container = Horizontal(classes="msg-text-row")
                self.mount(text_container)

                for css_cls in extra_css_classes:
                    text_container.add_class(css_cls)

                for j, sp in enumerate(spoiler_parts):
                    if j % 2 == 0:
                        if sp.strip():
                            formatted_text = _format_markdown(sp)
                            msg_static = Static(formatted_text, classes="msg-text", expand=False, shrink=True, markup=True)
                            for css_cls in extra_css_classes:
                                msg_static.add_class(css_cls)
                            text_container.mount(msg_static)
                    else:
                        text_container.mount(SpoilerText(sp, classes="msg-spoiler"))

            if i + 2 < len(parts):
                lang = parts[i + 1]
                code = parts[i + 2]
                self.mount(CodeBlock(code, lang))

    def _mount_inline_editor(self, *, sender: str, ts: int, edited, pending: bool) -> None:
        body = self._edit_initial if isinstance(self._edit_initial, str) else str(self.msg.get("body") or "")

        if self.show_header:
            h = f"[b]{sender}[/b]  {fmt_time(ts)}"
            if edited:
                h += f"  ({self._s('edited_tag')})"
            if pending:
                h += f"  ({self._s('pending_tag')})"
            self.mount(Static(h, classes="msg-header", expand=True, shrink=False))

        if self.reply_preview and self.msg.get("reply_to"):
            ra = self.reply_preview.get("sender_alias", "?")
            rb = self.reply_preview.get("body", "")
            if len(rb) > 80:
                rb = rb[:80] + "..."
            reply_target = str(self.msg["reply_to"])
            safe_ra = markup_escape(str(ra))
            safe_rb = markup_escape(str(rb))
            self.mount(MessageWidget.ReplyLink(reply_target, f"[dim]│ ↩ {safe_ra}: {safe_rb}[/dim]"))

        row = Horizontal(classes="msg_edit_row")
        self.mount(row)

        ta = TextArea(body, classes="msg_edit_ta")
        ta.show_line_numbers = False
        ta.wrap = True
        row.mount(ta)
        row.mount(Button(">", variant="primary", classes="msg_edit_send"))
        try:
            self.set_focus(ta)
        except Exception:
            pass

    def start_inline_edit(self) -> None:

        try:
            if (self.msg.get("kind") or "text") != "text":
                return
        except Exception:
            return
        self._editing = True
        self._edit_initial = str(self.msg.get("body") or "")
        self.refresh_content()

    def cancel_inline_edit(self) -> None:
        self._editing = False
        self._edit_initial = ""
        self.refresh_content()

    def _current_edit_text(self) -> str:
        try:
            ta = self.query_one(TextArea)
            return str(getattr(ta, "text", "") or "")
        except Exception:
            return ""

    def on_key(self, event: events.Key) -> None:
        if not self._editing:
            return
        if event.key == "escape":
            self.cancel_inline_edit()
            event.stop()

    def _handle_action(self, action: str, arg: str | None, event: events.Click | None = None) -> None:
        if action == "jump" and arg:
            self.post_message(MessageWidget.JumpRequested(self, arg))
            if event is not None:
                event.stop()

    def on_reply_link_clicked(self, event: ReplyLink.Clicked) -> None:
        self.post_message(MessageWidget.JumpRequested(self, event.target_msg_id))
        event.stop()

    def on_mouse_down(self, event: events.MouseDown) -> None:
        if event.button == 3:
            self.post_message(MessageWidget.ContextRequested(self, event.screen_x, event.screen_y))
            event.stop()
        elif event.button == 2:

            try:
                if hasattr(self.app, '_select_mode') and self.app._select_mode:
                    msg_id = self.msg.get("msg_id") or self.msg.get("id")
                    if msg_id and hasattr(self.app, '_last_selected_id') and self.app._last_selected_id:

                        self.app._select_range(self.app._last_selected_id, msg_id)
                        event.stop()
            except Exception:
                pass
        elif event.button == 1:

            try:
                if hasattr(self.app, '_select_mode') and self.app._select_mode:
                    msg_id = self.msg.get("msg_id") or self.msg.get("id")
                    if msg_id:

                        self.app._toggle_message_selection(msg_id)
                        self.app._last_selected_id = msg_id
                    event.stop()
            except Exception:
                pass

    def on_button_pressed(self, event: Button.Pressed) -> None:

        try:
            if event.button.has_class("msg_hist_btn"):
                msg_id = str(self.msg.get("msg_id") or "")
                if msg_id:
                    self.post_message(MessageWidget.HistoryRequested(self, msg_id))
                event.stop()
                return
        except Exception:
            pass

        if self._editing and event.button.has_class("msg_edit_send"):
            new_text = self._current_edit_text().strip()
            msg_id = str(self.msg.get("msg_id") or "")
            if msg_id and new_text and new_text != str(self.msg.get("body") or ""):
                self.post_message(MessageWidget.InlineEditSubmitted(self, msg_id, new_text))
            else:
                self.cancel_inline_edit()
            event.stop()
            return

        if event.button.id == "file_download":
            meta = self.msg.get("meta") or {}
            self.post_message(MessageWidget.FileDownloadRequested(self, meta))
            event.stop()
        elif event.button.id == "image_preview":
            meta = self.msg.get("meta") or {}
            self.post_message(MessageWidget.ImagePreviewRequested(self, meta))
            event.stop()
