# Copyright 2026, Jiri Eischmann <jiri@eischmann.cz> and the meshy contributors
# SPDX-License-Identifier: GPL-3.0-or-later

"""Contact detail dialog — shows info, routing, telemetry, and actions for a contact."""

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Adw, GLib, Gtk
from meshy.models import Contact, ContactType
from meshy.utils import format_trip_time


@Gtk.Template(resource_path="/page/codeberg/sesivany/Meshy/ui/contact-detail-dialog.ui")
class ContactDetailDialog(Adw.Dialog):
    __gtype_name__ = "MeshyContactDetailDialog"

    content_box = Gtk.Template.Child()
    info_group = Gtk.Template.Child()
    name_entry = Gtk.Template.Child()
    favorite_switch = Gtk.Template.Child()
    type_row = Gtk.Template.Child()
    key_row = Gtk.Template.Child()
    copy_btn = Gtk.Template.Child()
    last_seen_row = Gtk.Template.Child()
    routing_group = Gtk.Template.Child()
    routing_toggle = Gtk.Template.Child()
    out_path_entry = Gtk.Template.Child()
    reset_path_row = Gtk.Template.Child()
    reset_path_btn = Gtk.Template.Child()
    smart_paths_row = Gtk.Template.Child()
    auto_reset_switch = Gtk.Template.Child()
    flood_switch = Gtk.Template.Child()
    tele_group = Gtk.Template.Child()
    req_tele_row = Gtk.Template.Child()
    req_tele_btn = Gtk.Template.Child()
    danger_group = Gtk.Template.Child()
    remove_btn = Gtk.Template.Child()

    def __init__(self, contacts_view, contact: Contact, **kwargs):
        super().__init__(**kwargs)
        self._contacts_view = contacts_view
        self._window = contacts_view._window
        self._contact = contact
        self._path_map_row = None
        self._updating = False

        self.set_title(contact.name)
        self._populate(contact)
        self._add_conditional_widgets(contact)
        self._connect_signals(contact)

    def _populate(self, contact: Contact):
        self.name_entry.set_text(contact.name)
        self.favorite_switch.set_active(contact.is_favorite)
        self.type_row.set_subtitle(contact.type_label)
        self.key_row.set_subtitle(contact.public_key_hex)
        last_seen_str = (
            contact.last_seen.strftime("%Y-%m-%d %H:%M") if contact.last_seen else _("Never")
        )
        self.last_seen_row.set_subtitle(last_seen_str)

        # Routing state
        is_smart = contact.is_smart_routing
        self.routing_toggle.set_active_name("smart" if is_smart else "classic")
        if contact.path_hops:
            self.out_path_entry.set_text(",".join(h.lower() for h in contact.path_hops))
        self.out_path_entry.set_visible(not is_smart)
        self.reset_path_row.set_visible(not is_smart)
        self.reset_path_row.set_activatable_widget(self.reset_path_btn)
        self.smart_paths_row.set_visible(is_smart)
        self.auto_reset_switch.set_active(contact.auto_reset_path)
        self.flood_switch.set_active(contact.force_flood)
        self.flood_switch.set_visible(not is_smart)
        self._update_smart_subtitle(contact)

        self.req_tele_row.set_activatable_widget(self.req_tele_btn)

    def _add_conditional_widgets(self, contact: Contact):
        # Location row in info group
        if contact.has_location:
            loc_row = Adw.ActionRow(
                title=_("Location"),
                subtitle=f"{contact.latitude:.6f}, {contact.longitude:.6f}",
            )
            map_btn = Gtk.Button(icon_name="map-symbolic", valign=Gtk.Align.CENTER)
            map_btn.add_css_class("flat")
            map_btn.connect("clicked", lambda *_, c=contact: self._contacts_view._show_on_map(c))
            loc_row.add_suffix(map_btn)
            loc_row.set_activatable_widget(map_btn)
            self.info_group.add(loc_row)

        # Show Path on Map in routing group
        if contact.path_hops:
            hop_contacts = self._window.resolve_hop_prefixes(contact.path_hops)
            has_located = any(c and c.has_location for c in hop_contacts)
            if has_located or contact.has_location:
                self._path_map_row = Adw.ActionRow(
                    title=_("Show Path on Map"),
                    subtitle=_("Visualize the message route on the map"),
                )
                path_map_btn = Gtk.Button(icon_name="map-symbolic", valign=Gtk.Align.CENTER)
                path_map_btn.add_css_class("flat")
                path_map_btn.connect(
                    "clicked",
                    lambda *_, c=contact, hc=hop_contacts, hp=contact.path_hops: (
                        self._contacts_view._show_path_on_map(c, hc, hp)
                    ),
                )
                self._path_map_row.add_suffix(path_map_btn)
                self._path_map_row.set_activatable_widget(path_map_btn)
                self._path_map_row.set_visible(not contact.is_smart_routing)
                self.routing_group.add(self._path_map_row)

        # Telemetry switches (only for CHAT contacts)
        if contact.type == ContactType.CHAT:
            from meshy.models import (
                CONTACT_FLAG_TELE_BASE,
                CONTACT_FLAG_TELE_ENV,
                CONTACT_FLAG_TELE_LOC,
            )

            for title, subtitle, flag in (
                (
                    _("Allow Telemetry Requests"),
                    _("Allow this contact to query battery, voltage, temperature"),
                    CONTACT_FLAG_TELE_BASE,
                ),
                (
                    _("Include Location"),
                    _("Include GPS coordinates in telemetry responses"),
                    CONTACT_FLAG_TELE_LOC,
                ),
                (
                    _("Include Environment Sensors"),
                    _("Include humidity, pressure, air quality data"),
                    CONTACT_FLAG_TELE_ENV,
                ),
            ):
                sw = Adw.SwitchRow(title=title, subtitle=subtitle)
                sw.set_active(bool(contact.flags & flag))

                def _on_toggle(*args, _sw=sw, _flag=flag):
                    if _sw.get_active():
                        contact.flags |= _flag
                    else:
                        contact.flags &= ~_flag
                    self._window.storage.save_contact(contact)
                    self._window.update_contact_flags(contact)

                sw.connect("notify::active", _on_toggle)
                self.tele_group.add(sw)

        # Actions group (only for repeaters/rooms)
        actions_count = 0
        actions_group = Adw.PreferencesGroup(title=_("Actions"))

        if contact.type in (ContactType.ROOM, ContactType.REPEATER):
            mgmt_title = (
                _("Room Management")
                if contact.type == ContactType.ROOM
                else _("Repeater Management")
            )
            mgmt_row = Adw.ActionRow(
                title=mgmt_title,
                subtitle=_("Login and access status, CLI, neighbors, and settings"),
            )
            mgmt_btn = Gtk.Button(icon_name="utilities-terminal-symbolic", valign=Gtk.Align.CENTER)
            mgmt_btn.connect(
                "clicked",
                lambda *_: (
                    self.close(),
                    self._contacts_view._show_room_login(contact, repeater=True),
                ),
            )
            mgmt_row.add_suffix(mgmt_btn)
            mgmt_row.set_activatable_widget(mgmt_btn)
            actions_group.add(mgmt_row)
            actions_count += 1

        if contact.type in (ContactType.REPEATER, ContactType.ROOM):
            # Ping
            ping_row = Adw.ActionRow(
                title=_("Ping"),
                subtitle=_("Measure round-trip time to this node"),
            )
            ping_btn = Gtk.Button(
                icon_name="network-transmit-receive-symbolic", valign=Gtk.Align.CENTER
            )
            ping_btn.connect(
                "clicked", lambda *_: self._contacts_view._ping_contact(contact, ping_btn)
            )
            ping_row.add_suffix(ping_btn)
            ping_row.set_activatable_widget(ping_btn)
            actions_group.add(ping_row)
            actions_count += 1

            # Discover Paths
            discover_row = Adw.ActionRow(
                title=_("Discover Paths"),
                subtitle=_("Find routes to this node via flood"),
            )
            self._contacts_view._discover_path_label = Gtk.Label(label="")
            self._contacts_view._discover_path_label.add_css_class("dim-label")
            self._contacts_view._discover_path_label.set_valign(Gtk.Align.CENTER)
            discover_row.add_suffix(self._contacts_view._discover_path_label)
            discover_btn = Gtk.Button(icon_name="system-search-symbolic", valign=Gtk.Align.CENTER)
            discover_btn.connect(
                "clicked", lambda *_: self._contacts_view._on_path_discovery(contact, discover_btn)
            )
            discover_row.add_suffix(discover_btn)
            discover_row.set_activatable_widget(discover_btn)
            actions_group.add(discover_row)
            actions_count += 1

            # Regions
            regions_row = Adw.ActionRow(
                title=_("Regions"),
                subtitle=_("Query regions from this repeater"),
            )
            regions_label = Gtk.Label(label="")
            regions_label.add_css_class("dim-label")
            regions_label.set_valign(Gtk.Align.CENTER)
            regions_row.add_suffix(regions_label)
            regions_btn = Gtk.Button(icon_name="region-symbolic", valign=Gtk.Align.CENTER)

            def _on_regions_click(*_args, _contact=contact, _btn=regions_btn, _label=regions_label):
                _btn.set_sensitive(False)
                _label.set_label(_("Querying..."))

                def _on_response(text):
                    def _update():
                        _btn.set_sensitive(True)
                        if text:
                            names = []
                            for line in text.replace(",", "\n").split("\n"):
                                name = line.strip().strip("#")
                                if name and name != "*":
                                    names.append(name)
                            _label.set_label(", ".join(names) if names else _("None"))
                        else:
                            _label.set_label(_("No response"))

                    GLib.idle_add(_update)

                responded = [False]

                def _handle(text):
                    if responded[0]:
                        return
                    responded[0] = True
                    _on_response(text)

                self._window.request_regions(_contact, lambda text: _handle(text), use_path=True)

                def _retry():
                    if responded[0]:
                        return False
                    self._window.request_regions(
                        _contact, lambda text: _handle(text), use_path=True
                    )
                    return False

                def _give_up():
                    _handle(None)
                    return False

                GLib.timeout_add(5000, _retry)
                GLib.timeout_add(10000, _give_up)

            regions_btn.connect("clicked", _on_regions_click)
            regions_row.add_suffix(regions_btn)
            regions_row.set_activatable_widget(regions_btn)
            actions_group.add(regions_row)
            actions_count += 1

        if actions_count > 0:
            self.content_box.insert_child_after(actions_group, self.tele_group)

    def _connect_signals(self, contact: Contact):
        # Info signals
        self.favorite_switch.connect("notify::active", self._on_favorite_toggled, contact)
        self.copy_btn.connect("clicked", self._on_copy_key, contact)
        self.name_entry.connect("apply", self._on_apply, contact)
        self.out_path_entry.connect("apply", self._on_apply, contact)

        # Routing signals
        self.routing_toggle.connect("notify::active-name", self._on_toggle_changed, contact)
        self.out_path_entry.connect("changed", self._on_path_changed)
        self.reset_path_btn.connect("clicked", self._on_reset_path, contact)
        self.smart_paths_row.connect("activated", self._on_smart_paths_activated, contact)
        self.flood_switch.connect("notify::active", self._on_flood_toggled, contact)
        self.flood_switch.connect(
            "notify::active",
            lambda *a: self._on_apply(None, contact) if not self._updating else None,
        )
        self.auto_reset_switch.connect("notify::active", self._on_auto_reset_toggled, contact)

        # Telemetry
        self.req_tele_btn.connect(
            "clicked",
            lambda *_: self._contacts_view._request_telemetry(contact, self.req_tele_row),
        )

        # Danger zone
        self.remove_btn.connect("clicked", self._on_remove, contact)

        # Initial state
        self._update_flood_dependents()

    # --- Info callbacks ---

    def _on_favorite_toggled(self, switch, _pspec, contact):
        from meshy.models import CONTACT_FLAG_FAVORITE

        if switch.get_active():
            contact.flags |= CONTACT_FLAG_FAVORITE
        else:
            contact.flags &= ~CONTACT_FLAG_FAVORITE
        self._window.storage.save_contact(contact)
        self._window.update_contact_flags(contact)
        self._window.update_contacts_from_storage()

    def _on_copy_key(self, _btn, contact):
        clipboard = self._window.get_clipboard()
        clipboard.set(contact.public_key_hex)
        clipboard.store_async(None, None)
        self._window.show_toast(_("Public key copied"))

    # --- Routing callbacks ---

    def _on_toggle_changed(self, toggle_group, _pspec, contact):
        if self._updating:
            return
        is_smart = toggle_group.get_active_name() == "smart"
        contact.smart_route_selection = is_smart
        self._window.storage.save_contact(contact)
        is_smart = contact.is_smart_routing
        self.out_path_entry.set_visible(not is_smart)
        self.reset_path_row.set_visible(not is_smart)
        self.flood_switch.set_visible(not is_smart)
        if self._path_map_row is not None:
            self._path_map_row.set_visible(not is_smart)
        self.smart_paths_row.set_visible(is_smart)
        if is_smart:
            self._update_smart_subtitle(contact)
            self._update_flood_dependents()

    def _on_path_changed(self, *args):
        if self._updating:
            return
        if self.out_path_entry.get_text().strip():
            self._updating = True
            self.flood_switch.set_active(False)
            self._updating = False

    def _on_reset_path(self, _btn, contact):
        self._window.reset_contact_path(contact)
        self.out_path_entry.set_text("")

    def _on_smart_paths_activated(self, _row, contact):
        self._show_smart_paths_dialog(contact)

    def _on_flood_toggled(self, switch, _pspec, contact):
        if self._updating:
            return
        self._update_flood_dependents()
        if switch.get_active():
            self._updating = True
            self.out_path_entry.set_text("")
            self._updating = False
        contact.force_flood = switch.get_active()
        self._window.storage.save_contact(contact)

    def _on_auto_reset_toggled(self, switch, _pspec, contact):
        if self._updating:
            return
        contact.auto_reset_path = switch.get_active()
        self._window.storage.save_contact(contact)

    def _update_flood_dependents(self):
        is_flood = self.flood_switch.get_active()
        self.auto_reset_switch.set_sensitive(not is_flood)

    def _update_smart_subtitle(self, contact):
        count = len(self._window.storage.get_path_history(contact.public_key_hex))
        self.smart_paths_row.set_subtitle(
            _("%d paths available") % count if count else _("No paths yet")
        )

    # --- Apply ---

    def _on_apply(self, _widget, contact):
        new_name = self.name_entry.get_text().strip() or contact.name
        path_text = self.out_path_entry.get_text().strip()
        force_flood = self.flood_switch.get_active()

        if force_flood:
            self._window.set_contact_path(contact, 0xFF, b"", name=new_name)
        elif path_text:
            try:
                hops = [h.strip() for h in path_text.split(",") if h.strip()]
                hash_size = len(bytes.fromhex(hops[0]))
                path_bytes = b"".join(bytes.fromhex(h) for h in hops)
                hash_mode = hash_size - 1
                hop_count = len(hops)
                path_len_byte = (hash_mode << 6) | (hop_count & 0x3F)
                self._window.set_contact_path(contact, path_len_byte, path_bytes, name=new_name)
            except ValueError:
                pass
        else:
            self._window.reset_contact_path(contact)
            if new_name != contact.name:
                GLib.timeout_add(
                    500,
                    lambda: (
                        self._window.set_contact_path(contact, 0xFF, b"", name=new_name) or False
                    ),
                )

        if new_name != contact.name:
            contact.name = new_name
            self._window.storage.save_contact(contact)
            self._window.update_contacts_from_storage()
            self._window.refresh_chat_header()

    def _on_delete_path_record(self, _btn, record_id, row, paths_group, contact, dialog):
        self._window.storage.delete_path_record(record_id)
        paths_group.remove(row)
        count = len(self._window.storage.get_path_history(contact.public_key_hex))
        if count:
            self.smart_paths_row.set_subtitle(_("%d paths available") % count)
        else:
            self.smart_paths_row.set_subtitle(_("No paths yet"))
            dialog.close()

    # --- Smart paths dialog ---

    def _show_smart_paths_dialog(self, contact: Contact):
        from meshy.views.map_view import _ROUTE_COLORS

        path_svc = self._window._msg_ctrl.path_history
        records = self._window.storage.get_path_history(contact.public_key_hex)

        builder = Gtk.Builder.new_from_resource(
            "/page/codeberg/sesivany/Meshy/ui/smart-paths-dialog.ui"
        )
        dialog = builder.get_object("dialog")
        header = builder.get_object("header")
        box = builder.get_object("content")
        dialog.set_title(_("Smart Routes — %s") % contact.name)

        if records:
            map_btn = Gtk.Button(icon_name="map-symbolic", tooltip_text=_("Show on Map"))
            map_btn.add_css_class("flat")
            map_btn.connect(
                "clicked",
                lambda *_, d=dialog: self._show_all_routes_on_map(contact, records, path_svc, d),
            )
            header.pack_end(map_btn)

        paths_group = Adw.PreferencesGroup(title=_("Available Paths"))

        if records:
            for i, r in enumerate(sorted(records, key=path_svc.score_dict, reverse=True)):
                path_hex = r["path_bytes"]
                if path_hex:
                    chunk = r["path_hash_size"] * 2
                    hops = ",".join(path_hex[j : j + chunk] for j in range(0, len(path_hex), chunk))
                else:
                    hops = _("Direct")
                sub_parts = [
                    _("Score: %.2f") % path_svc.score_dict(r),
                    _("Weight: %.1f") % r["route_weight"],
                    f"{r['success_count']}✓ {r['failure_count']}✗",
                    _("%d hops") % r["hop_count"],
                ]
                trip_str = format_trip_time(r.get("last_trip_ms"))
                if trip_str:
                    sub_parts.append(trip_str)
                row = Adw.ActionRow(title=hops, subtitle=" · ".join(sub_parts))
                cr, cg, cb = _ROUTE_COLORS[i % len(_ROUTE_COLORS)]
                dot = Gtk.DrawingArea()
                dot.set_content_width(12)
                dot.set_content_height(12)
                dot.set_valign(Gtk.Align.CENTER)
                dot.set_draw_func(
                    lambda _area, ctx, w, h, _r=cr, _g=cg, _b=cb: (
                        ctx.set_source_rgb(_r, _g, _b),
                        ctx.arc(w / 2, h / 2, min(w, h) / 2, 0, 2 * 3.141593),
                        ctx.fill(),
                    )
                )
                row.add_prefix(dot)
                del_btn = Gtk.Button(icon_name="edit-delete-symbolic")
                del_btn.add_css_class("flat")
                del_btn.set_valign(Gtk.Align.CENTER)
                record_id = r["id"]
                del_btn.connect(
                    "clicked",
                    self._on_delete_path_record,
                    record_id,
                    row,
                    paths_group,
                    contact,
                    dialog,
                )
                row.add_suffix(del_btn)
                paths_group.add(row)
        else:
            empty_row = Adw.ActionRow(
                title=_("No paths recorded yet"),
                subtitle=_("Paths are discovered as messages are exchanged"),
            )
            paths_group.add(empty_row)

        box.append(paths_group)

        if records:
            clear_btn = Gtk.Button(label=_("Clear History"))
            clear_btn.add_css_class("destructive-action")
            clear_btn.set_halign(Gtk.Align.CENTER)

            def _on_clear(*_args):
                self._window.storage.clear_path_history(contact.public_key_hex)
                self.smart_paths_row.set_subtitle(_("No paths yet"))
                dialog.close()

            clear_btn.connect("clicked", _on_clear)
            box.append(clear_btn)

        dialog.present(self._window)

    # --- Smart routes map ---

    def _show_all_routes_on_map(self, contact, records, path_svc, parent_dialog):
        from gi.repository import Shumate
        from meshy.views.map_view import (
            _MARKER_COLORS,
            _MARKER_ICONS,
            _ROUTE_COLORS,
            _SELF_COLOR,
            _UNKNOWN_COLOR,
            MapView,
            companion_too_far,
        )

        sorted_records = sorted(records, key=path_svc.score_dict, reverse=True)

        device = self._window.device_info
        self_lat = getattr(device, "latitude", None)
        self_lon = getattr(device, "longitude", None)
        has_self = self_lat and self_lon and (abs(self_lat) > 1e-6 or abs(self_lon) > 1e-6)

        route_hops = []
        for i, rec in enumerate(sorted_records):
            color_rgb = _ROUTE_COLORS[i % len(_ROUTE_COLORS)]
            path_hex = rec["path_bytes"]

            raw_hops = []

            if path_hex:
                chunk = rec["path_hash_size"] * 2
                hop_hexes = [path_hex[j : j + chunk] for j in range(0, len(path_hex), chunk)]
                hop_contacts = self._window.resolve_hop_prefixes(hop_hexes)
                for j, hc in enumerate(hop_contacts):
                    prefix = hop_hexes[j] if j < len(hop_hexes) else None
                    if hc and hc.has_location:
                        raw_hops.append((hc.latitude, hc.longitude, hc, prefix, None))
                    else:
                        raw_hops.append((None, None, hc, prefix, None))

            if contact.has_location:
                raw_hops.append((contact.latitude, contact.longitude, contact, None, None))

            if has_self and not companion_too_far(self_lat, self_lon, raw_hops):
                raw_hops.insert(0, (self_lat, self_lon, None, None, None))

            hops = MapView.interpolate_unknown_hops(raw_hops)
            route_hops.append((color_rgb, hops))

        all_lats, all_lons = [], []
        for _color, hops in route_hops:
            for h in hops:
                if h[0] is not None:
                    all_lats.append(h[0])
                    all_lons.append(h[1])

        if not all_lats:
            self._window.show_toast(_("No location data available"))
            return

        dialog, map_widget, marker_layer, viewport = MapView.open_map_dialog(
            self._window, title=_("Routes to %s") % contact.name
        )

        map_widget.get_map().remove_layer(marker_layer)

        for color_rgb, hops in route_hops:
            path_layer = MapView.create_dashed_path_layer(
                viewport, color_rgb=color_rgb, stroke_width=3
            )
            for lat, lon, _hc, _pfx, _snr2 in hops:
                if lat is not None:
                    node = Shumate.Marker()
                    node.set_location(lat, lon)
                    path_layer.add_node(node)
            map_widget.get_map().add_layer(path_layer)

        map_widget.get_map().add_layer(marker_layer)

        placed = set()
        for _color, hops in route_hops:
            for lat, lon, hop_contact, prefix, _snr in hops:
                if lat is None:
                    continue
                key = (round(lat, 6), round(lon, 6))
                if key in placed:
                    continue
                placed.add(key)

                if hop_contact is None and prefix is None:
                    r, g, b = _SELF_COLOR
                    icon_name = "network-wireless-symbolic"
                    name = device.name or _("My Device")
                elif hop_contact is None:
                    r, g, b = _UNKNOWN_COLOR
                    icon_name = "repeater-symbolic"
                    name = prefix or _("Unknown")
                else:
                    r, g, b = _MARKER_COLORS.get(hop_contact.type, (0.5, 0.5, 0.5))
                    icon_name = _MARKER_ICONS.get(hop_contact.type, "avatar-default-symbolic")
                    name = hop_contact.name

                marker = MapView.create_marker(r, g, b, icon_name, name, lat, lon, show_label=True)
                marker_layer.add_marker(marker)

        # Legend bar
        toolbar_view = dialog.get_child()
        legend_scroll = Gtk.ScrolledWindow(
            hscrollbar_policy=Gtk.PolicyType.AUTOMATIC,
            vscrollbar_policy=Gtk.PolicyType.NEVER,
        )
        legend_box = Gtk.Box(
            spacing=16,
            margin_start=8,
            margin_end=8,
            margin_top=4,
            margin_bottom=4,
        )
        for i, rec in enumerate(sorted_records):
            cr, cg, cb = _ROUTE_COLORS[i % len(_ROUTE_COLORS)]
            item = Gtk.Box(spacing=4)
            dot = Gtk.DrawingArea()
            dot.set_content_width(10)
            dot.set_content_height(10)
            dot.set_valign(Gtk.Align.CENTER)
            dot.set_draw_func(
                lambda _area, ctx, w, h, _r=cr, _g=cg, _b=cb: (
                    ctx.set_source_rgb(_r, _g, _b),
                    ctx.arc(w / 2, h / 2, min(w, h) / 2, 0, 2 * 3.141593),
                    ctx.fill(),
                )
            )
            item.append(dot)
            path_hex = rec["path_bytes"]
            if path_hex:
                label_text = _("%d hops") % rec["hop_count"]
            else:
                label_text = _("Direct")
            lbl = Gtk.Label(label=label_text)
            lbl.add_css_class("caption")
            item.append(lbl)
            legend_box.append(item)
        legend_scroll.set_child(legend_box)
        toolbar_view.add_bottom_bar(legend_scroll)

        MapView.fit_viewport(viewport, all_lats, all_lons)
        dialog.present(parent_dialog)

    # --- Danger zone ---

    def _on_remove(self, _btn, contact):
        confirm = Adw.AlertDialog(
            heading=_("Remove Contact?"),
            body=_("Remove {} and all messages?").format(contact.name),
        )
        confirm.add_response("cancel", _("Cancel"))
        confirm.add_response("remove", _("Remove"))
        confirm.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE)
        confirm.connect(
            "response",
            lambda d, r: (
                (self._window.remove_contact(contact), self.close()) if r == "remove" else None
            ),
        )
        confirm.present(self._window)
