import json
import time
import tkinter as tk
from datetime import datetime
from tkinter import messagebox, simpledialog

from PIL import Image, ImageTk

from core.constants import BASE_DIR
from core.storage import save_chat, save_settings
from core.ui import center_window, create_card, show_empty


class ChatPage:
    """Chat module with personal-space and enterprise-space separation.

    The module uses a generic personal communication + team collaboration
    pattern. Personal friends/groups stay separate from enterprise staff,
    external contacts, and team groups. Internal IDs stay hidden from users;
    friend discovery uses public contact methods such as QR code, phone,
    email, or account name.
    """

    def __init__(self, app, parent):
        self.app = app
        self.colors = app.colors
        self.frame = tk.Frame(parent, bg=self.colors["bg"])
        default_scope = "personal"
        self.chat_scope = str(self.app.user_settings.get("chat_scope", default_scope) or default_scope)
        if self.chat_scope not in ("enterprise", "personal"):
            self.chat_scope = default_scope
        if not getattr(self.app, "is_enterprise_mode", lambda: True)():
            self.chat_scope = "personal"
        self.current_thread = "company-group" if self.chat_scope == "enterprise" else "personal-notes"
        self.contact_query = ""
        self.sticker_manifest = self._load_sticker_manifest()
        self.current_pack = self.sticker_manifest[0]["id"] if self.sticker_manifest else "work"
        self._sticker_refs = []
        self.recent_stickers = list(self.app.user_settings.get("recent_stickers", []) or [])[:8]
        self._sticker_thumb_cache = {}
        self._sticker_msg_cache = {}
        self._build()

    def _load_sticker_manifest(self):
        path = BASE_DIR / "assets" / "chat_stickers" / "manifest.json"
        if not path.exists():
            return []
        try:
            return json.loads(path.read_text(encoding="utf-8")).get("packs", [])
        except Exception:
            return []

    def _sticker_file(self, file_name):
        return BASE_DIR / "assets" / "chat_stickers" / file_name

    def _sticker_photo(self, file_name, size):
        cache = self._sticker_thumb_cache if size <= 72 else self._sticker_msg_cache
        key = (file_name, size)
        if key in cache:
            return cache[key]
        try:
            img = Image.open(self._sticker_file(file_name)).convert("RGBA")
            img.thumbnail((size, size), Image.LANCZOS)
            photo = ImageTk.PhotoImage(img)
            cache[key] = photo
            return photo
        except Exception:
            return None

    def _build(self):
        layout = tk.Frame(self.frame, bg=self.colors["bg"])
        layout.pack(fill=tk.BOTH, expand=True)

        left = create_card(layout, self.colors)
        left.pack(side=tk.LEFT, fill=tk.Y)
        left.configure(width=370)
        left.pack_propagate(False)

        right = create_card(layout, self.colors)
        right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(14, 0))

        title_row = tk.Frame(left, bg=self.colors["card"])
        title_row.pack(fill=tk.X, padx=18, pady=(16, 8))
        self.contacts_title = tk.Label(title_row, text=self.app.t("chat_contacts"), font=("Microsoft YaHei UI", 15, "bold"), fg=self.colors["text"], bg=self.colors["card"])
        self.contacts_title.pack(side=tk.LEFT)
        tk.Button(title_row, text=self.app.t("chat_add_group_short"), command=self.add_group, relief=tk.FLAT, bd=0, bg=self.colors["card_soft"], fg=self.colors["primary"], activebackground=self.colors["primary_light"], activeforeground=self.colors["primary"], font=("Microsoft YaHei UI", 9, "bold"), cursor="hand2").pack(side=tk.RIGHT, padx=(6, 0))
        tk.Button(title_row, text=self.app.t("chat_my_code_short"), command=self.show_my_contact_code, relief=tk.FLAT, bd=0, bg=self.colors["card_soft"], fg=self.colors["primary"], activebackground=self.colors["primary_light"], activeforeground=self.colors["primary"], font=("Microsoft YaHei UI", 9, "bold"), cursor="hand2").pack(side=tk.RIGHT, padx=(6, 0))
        tk.Button(title_row, text=self.app.t("chat_add_friend_short"), command=self.add_friend, relief=tk.FLAT, bd=0, bg=self.colors["primary"], fg="white", activebackground=self.colors["primary"], activeforeground="white", font=("Microsoft YaHei UI", 9, "bold"), cursor="hand2").pack(side=tk.RIGHT)

        self.scope_row = tk.Frame(left, bg=self.colors["card"])
        self.scope_row.pack(fill=tk.X, padx=12, pady=(0, 8))
        self._build_scope_buttons()

        self.space_hint = tk.Label(left, text="", bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 8), wraplength=300, justify=tk.LEFT)
        self.space_hint.pack(fill=tk.X, padx=18, pady=(0, 6))

        self.search_box = tk.Frame(left, bg=self.colors["bg"], highlightthickness=1, highlightbackground=self.colors["border"])
        self.search_box.pack(fill=tk.X, padx=18, pady=(0, 10))
        tk.Label(self.search_box, text="⌕", bg=self.colors["bg"], fg=self.colors["text_light"], font=("Microsoft YaHei UI", 11, "bold")).pack(side=tk.LEFT, padx=(10, 4))
        self.contact_search = tk.Entry(self.search_box, relief=tk.FLAT, bd=0, bg=self.colors["bg"], fg=self.colors["text_light"], font=("Microsoft YaHei UI", 10))
        self.contact_search.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=8, pady=1)
        self.contact_search.insert(0, self.app.t("chat_search_placeholder"))
        self.contact_search.bind("<FocusIn>", self._contact_search_focus_in)
        self.contact_search.bind("<FocusOut>", self._contact_search_focus_out)
        self.contact_search.bind("<KeyRelease>", self._on_contact_search_changed)

        self.contacts_frame = tk.Frame(left, bg=self.colors["card"])
        self.contacts_frame.pack(fill=tk.BOTH, expand=True, padx=12, pady=(0, 12))

        top = tk.Frame(right, bg=self.colors["card"])
        top.pack(fill=tk.X, padx=18, pady=(16, 8))
        self.thread_title = tk.Label(top, text="", font=("Microsoft YaHei UI", 16, "bold"), fg=self.colors["text"], bg=self.colors["card"], wraplength=780, justify=tk.LEFT, anchor="w")
        self.thread_title.pack(side=tk.LEFT, fill=tk.X, expand=True)
        self.read_hint = tk.Label(top, text="", font=("Microsoft YaHei UI", 9, "bold"), fg=self.colors["text_secondary"], bg=self.colors["card"])
        self.read_hint.pack(side=tk.RIGHT)

        self.messages_wrap = tk.Frame(right, bg=self.colors["card"])
        self.messages_wrap.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
        self.canvas = tk.Canvas(self.messages_wrap, bg=self.colors["card"], highlightthickness=0)
        self.scrollbar = tk.Scrollbar(self.messages_wrap, orient="vertical", command=self.canvas.yview)
        self.messages_frame = tk.Frame(self.canvas, bg=self.colors["card"])
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.msg_window = self.canvas.create_window((0, 0), window=self.messages_frame, anchor="nw", tags="inner")
        self.messages_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
        self.canvas.bind("<Configure>", lambda e: self.canvas.itemconfig(self.msg_window, width=max(e.width - 8, 300)))

        self.sticker_panel = tk.Frame(right, bg=self.colors["card"])
        self.sticker_panel.pack(fill=tk.X, padx=18, pady=(0, 8))
        self._build_sticker_panel()

        input_row = tk.Frame(right, bg=self.colors["card"])
        input_row.pack(fill=tk.X, padx=18, pady=(0, 18))
        input_box = tk.Frame(input_row, bg=self.colors["bg"], highlightthickness=1, highlightbackground=self.colors["border"])
        input_box.pack(side=tk.LEFT, fill=tk.X, expand=True)
        tk.Frame(input_box, width=12, bg=self.colors["bg"]).pack(side=tk.LEFT)
        self.input = tk.Entry(input_box, relief=tk.FLAT, bd=0, bg=self.colors["bg"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 11))
        self.input.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=11, pady=1)
        self.input.insert(0, self.app.t("chat_input_placeholder"))
        self.input.bind("<FocusIn>", self._focus_in)
        self.input.bind("<KeyPress>", self._focus_in)
        self.input.bind("<FocusOut>", self._focus_out)
        self.input.bind("<Return>", lambda e: self.send_message())
        self.send_btn = tk.Button(input_row, text=self.app.t("chat_send"), command=self.send_message, relief=tk.FLAT, bd=0, bg=self.colors["primary"], fg="white", activebackground=self.colors["primary"], activeforeground="white", font=("Microsoft YaHei UI", 10, "bold"), padx=18, pady=10, cursor="hand2")
        self.send_btn.pack(side=tk.LEFT, padx=(10, 0))

    def _build_scope_buttons(self):
        for child in self.scope_row.winfo_children():
            child.destroy()
        self._scope_button(self.scope_row, "chat_scope_personal_short", "personal")
        if getattr(self.app, "is_enterprise_mode", lambda: True)():
            self._scope_button(self.scope_row, "chat_scope_enterprise_short", "enterprise")
        else:
            self.chat_scope = "personal"

    def _scope_button(self, parent, text_key, scope):
        active = self.chat_scope == scope
        btn = tk.Button(
            parent,
            text=self.app.t(text_key),
            command=lambda: self.set_scope(scope),
            relief=tk.FLAT,
            bd=0,
            padx=8,
            pady=7,
            bg=self.colors["primary"] if active else self.colors["card_soft"],
            fg="white" if active else self.colors["text_secondary"],
            activebackground=self.colors["primary"],
            activeforeground="white",
            font=("Microsoft YaHei UI", 9, "bold"),
            cursor="hand2",
        )
        btn.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 4))
        return btn

    def _all_sticker_items(self):
        items = []
        for pack in self.sticker_manifest:
            for item in pack.get("items", []):
                if isinstance(item, dict):
                    data = dict(item)
                    data["pack_id"] = pack.get("id")
                    items.append(data)
        return items

    def _remember_sticker(self, sticker_id, label, file_name):
        item = {"id": sticker_id, "label": label, "file": file_name}
        recent = [x for x in list(getattr(self, "recent_stickers", []) or []) if x.get("id") != sticker_id]
        recent.insert(0, item)
        self.recent_stickers = recent[:8]
        try:
            self.app.user_settings["recent_stickers"] = self.recent_stickers
            save_settings(self.app.user_settings)
        except Exception:
            pass

    def _build_sticker_panel(self):
        for child in self.sticker_panel.winfo_children():
            child.destroy()
        self._sticker_refs = []

        shell = tk.Frame(self.sticker_panel, bg=self.colors["card_soft"], highlightthickness=0)
        shell.pack(fill=tk.X, padx=0, pady=(0, 4))

        head = tk.Frame(shell, bg=self.colors["card_soft"])
        head.pack(fill=tk.X, padx=10, pady=(8, 4))
        tk.Label(
            head,
            text=self.app.t("chat_stickers"),
            bg=self.colors["card_soft"],
            fg=self.colors["text_secondary"],
            font=("Microsoft YaHei UI", 9, "bold"),
        ).pack(side=tk.LEFT, padx=(0, 10))

        for pack in self.sticker_manifest:
            active = self.current_pack == pack.get("id")
            btn = tk.Button(
                head,
                text=self.app.t(pack.get("label_key", "chat_pack_work")),
                command=lambda p=pack.get("id"): self.set_sticker_pack(p),
                relief=tk.FLAT,
                bd=0,
                bg=self.colors["primary"] if active else self.colors["card"],
                fg="white" if active else self.colors["text_secondary"],
                activebackground=self.colors["primary_dark"] if active else self.colors["primary_light"],
                activeforeground="white" if active else self.colors["primary"],
                font=("Microsoft YaHei UI", 8, "bold"),
                padx=10,
                pady=4,
                cursor="hand2",
            )
            btn.pack(side=tk.LEFT, padx=(0, 6))

        body_canvas = tk.Canvas(shell, bg=self.colors["card_soft"], highlightthickness=0, height=100)
        body_canvas.pack(fill=tk.X, padx=8, pady=(0, 8))
        row = tk.Frame(body_canvas, bg=self.colors["card_soft"])
        row_id = body_canvas.create_window((0, 0), window=row, anchor="nw")

        def sync_row(event=None):
            try:
                body_canvas.configure(scrollregion=body_canvas.bbox("all"))
                body_canvas.itemconfig(row_id, height=body_canvas.winfo_height())
            except Exception:
                pass

        row.bind("<Configure>", sync_row)
        body_canvas.bind("<Configure>", sync_row)

        def wheel_x(event):
            try:
                delta = getattr(event, "delta", 0)
                if delta:
                    body_canvas.xview_scroll(-1 * int(delta / 120) if abs(delta) >= 120 else (-1 if delta > 0 else 1), "units")
                    return "break"
            except Exception:
                return None

        body_canvas.bind("<MouseWheel>", wheel_x, add="+")
        row.bind("<MouseWheel>", wheel_x, add="+")

        pack = next((p for p in self.sticker_manifest if p.get("id") == self.current_pack), None)
        if not pack:
            return

        all_items = {item.get("id"): item for item in self._all_sticker_items()}
        render_items = []
        for item in self.recent_stickers:
            source = all_items.get(item.get("id"), item)
            if source and source not in render_items:
                render_items.append(source)
        for item in pack.get("items", []):
            if item and item not in render_items:
                render_items.append(item)

        def make_card(item, is_recent=False):
            card_bg = self.colors["primary_light"] if is_recent else self.colors["card"]
            card = tk.Frame(row, bg=card_bg, highlightthickness=0, bd=0, cursor="hand2")
            card.pack(side=tk.LEFT, padx=(0, 8), pady=6)
            photo = self._sticker_photo(item.get("file", ""), 52)
            if photo:
                self._sticker_refs.append(photo)
                img = tk.Label(card, image=photo, bg=card_bg, cursor="hand2")
                img.pack(padx=10, pady=10)
            else:
                img = tk.Label(card, text="😊", bg=card_bg, fg=self.colors["primary"], font=("Microsoft YaHei UI", 24), cursor="hand2")
                img.pack(padx=11, pady=9)
            for w in card.winfo_children() + [card]:
                w.bind("<Button-1>", lambda e, sid=item.get("id"), label=item.get("label"), file=item.get("file"): self.send_sticker(sid, label, file))
                w.bind("<MouseWheel>", wheel_x, add="+")
            try:
                tip = ("★ " if is_recent else "") + str(item.get("label", ""))
                ToolTip(card, lambda label=tip: label)
            except Exception:
                pass

        recent_ids = {item.get("id") for item in self.recent_stickers[:8]}
        for item in render_items:
            make_card(item, item.get("id") in recent_ids)

    def _contact_search_text(self):
        try:
            value = self.contact_search.get().strip()
        except Exception:
            return ""
        if not value or value in self.app.all_values("chat_search_placeholder"):
            return ""
        return value.lower()

    def _contact_search_focus_in(self, event=None):
        try:
            if self.contact_search.get() in self.app.all_values("chat_search_placeholder"):
                self.contact_search.delete(0, tk.END)
                self.contact_search.config(fg=self.colors["text"])
        except Exception:
            pass

    def _contact_search_focus_out(self, event=None):
        try:
            if not self.contact_search.get().strip():
                self.contact_search.insert(0, self.app.t("chat_search_placeholder"))
                self.contact_search.config(fg=self.colors["text_light"])
        except Exception:
            pass

    def _on_contact_search_changed(self, event=None):
        query = self._contact_search_text()
        if query == getattr(self, "contact_query", ""):
            return
        self.contact_query = query
        self._render_contacts()

    def set_sticker_pack(self, pack_id):
        self.current_pack = pack_id
        self._build_sticker_panel()
        self._sticker_panel_built = True

    def set_scope(self, scope):
        if scope == "enterprise" and not getattr(self.app, "is_enterprise_mode", lambda: True)():
            scope = "personal"
        self.chat_scope = "personal" if scope == "personal" else "enterprise"
        self.app.user_settings["chat_scope"] = self.chat_scope
        save_settings(self.app.user_settings)
        self.current_thread = "personal-notes" if self.chat_scope == "personal" else "company-group"
        self._sticker_panel_built = False
        self.refresh()

    def _focus_in(self, event=None):
        if self.input.get() == self.app.t("chat_input_placeholder"):
            self.input.delete(0, tk.END)
            self.input.config(fg=self.colors["text"])

    def _focus_out(self, event=None):
        if not self.input.get().strip():
            self.input.insert(0, self.app.t("chat_input_placeholder"))
            self.input.config(fg=self.colors["text_secondary"])

    def _thread_store(self):
        key = "personal_threads" if self.chat_scope == "personal" else "enterprise_threads"
        threads = self.app.chat.setdefault(key, {})
        if self.chat_scope == "enterprise" and not threads and self.app.chat.get("threads"):
            threads.update(self.app.chat.get("threads", {}))
        return threads

    def _friends_key(self):
        return "personal_friends" if self.chat_scope == "personal" else "enterprise_friends"

    def _groups_key(self):
        return "personal_groups" if self.chat_scope == "personal" else "enterprise_groups"

    def _thread_prefix(self):
        return "p" if self.chat_scope == "personal" else "e"

    def _scope_name(self):
        return self.app.t("chat_scope_personal") if self.chat_scope == "personal" else self.app.t("chat_scope_enterprise")

    def _detect_contact_method(self, value):
        raw = str(value or "").strip()
        if not raw:
            return "", ""
        normalized = raw.replace(" ", "")
        upper = raw.upper()
        if upper.startswith("NX-") or upper.startswith("NEXA-"):
            return "qr", raw
        if "@" in raw and "." in raw.split("@")[-1]:
            return "email", raw
        digits = "".join(ch for ch in normalized if ch.isdigit() or ch == "+")
        if len([ch for ch in digits if ch.isdigit()]) >= 6:
            return "phone", raw
        return "account", raw

    def _contact_method_label(self, method):
        mapping = {
            "phone": "chat_contact_method_phone",
            "email": "chat_contact_method_email",
            "account": "chat_contact_method_account",
            "qr": "chat_contact_method_qr",
        }
        return self.app.t(mapping.get(method, "chat_contact_method_account"))

    def _friend_contact_summary(self, friend):
        if not isinstance(friend, dict):
            return self.app.t("chat_friend_external")
        method = friend.get("contact_method") or self._detect_contact_method(friend.get("contact_value", ""))[0]
        value = str(friend.get("contact_value") or friend.get("phone") or friend.get("email") or friend.get("account") or friend.get("qr_code") or "").strip()
        if not value:
            return self.app.t("chat_friend_external")
        return f"{self._contact_method_label(method)} · {value}"

    def add_friend(self):
        win = tk.Toplevel(self.frame)
        win.title(self.app.t("chat_add_friend"))
        win.configure(bg=self.colors["bg"])
        try:
            self.app._set_window_icon(win)
        except Exception:
            pass
        win.transient(self.frame.winfo_toplevel())
        win.grab_set()
        center_window(win, self.app.root, 520, 430, min_width=460, min_height=360)

        card = create_card(win, self.colors)
        card.pack(fill=tk.BOTH, expand=True, padx=18, pady=18)
        tk.Label(card, text=self.app.t("chat_add_friend"), bg=self.colors["card"], fg=self.colors["text"], font=("Microsoft YaHei UI", 16, "bold")).pack(anchor="w", padx=18, pady=(16, 6))
        scope_hint_key = "chat_personal_friend_isolation_hint" if self.chat_scope == "personal" else "chat_enterprise_friend_isolation_hint"
        tk.Label(card, text=self.app.t("chat_add_friend_hint"), bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9), wraplength=460, justify=tk.LEFT).pack(anchor="w", padx=18, pady=(0, 6))
        tk.Label(card, text=self.app.t(scope_hint_key), bg=self.colors["primary_light"], fg=self.colors["primary"], font=("Microsoft YaHei UI", 8, "bold"), wraplength=452, justify=tk.LEFT, padx=10, pady=6).pack(fill=tk.X, padx=18, pady=(0, 10))

        name_var = tk.StringVar()
        contact_var = tk.StringVar()

        def field(label_key, var, placeholder_key=None):
            tk.Label(card, text=self.app.t(label_key), bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9, "bold")).pack(anchor="w", padx=18, pady=(8, 2))
            box = tk.Entry(card, textvariable=var, relief=tk.FLAT, bd=0, bg=self.colors["bg"], fg=self.colors["text"], font=("Microsoft YaHei UI", 11))
            box.pack(fill=tk.X, padx=18, ipady=9)
            if placeholder_key:
                box.insert(0, self.app.t(placeholder_key))
                box.config(fg=self.colors["text_light"])
                def focus_in(event, entry=box, key=placeholder_key):
                    if entry.get() == self.app.t(key):
                        entry.delete(0, tk.END)
                        entry.config(fg=self.colors["text"])
                def focus_out(event, entry=box, key=placeholder_key):
                    if not entry.get().strip():
                        entry.insert(0, self.app.t(key))
                        entry.config(fg=self.colors["text_light"])
                box.bind("<FocusIn>", focus_in)
                box.bind("<FocusOut>", focus_out)
            return box

        name_entry = field("chat_add_friend_display_name", name_var)
        contact_entry = field("chat_add_friend_contact", contact_var, "chat_add_friend_contact_placeholder")
        tk.Label(card, text=self.app.t("chat_add_friend_no_puid_hint"), bg=self.colors["card"], fg=self.colors["text_light"], font=("Microsoft YaHei UI", 8), wraplength=460, justify=tk.LEFT).pack(anchor="w", padx=18, pady=(8, 10))

        footer = tk.Frame(card, bg=self.colors["card"])
        footer.pack(fill=tk.X, padx=18, pady=(12, 16))

        def save_friend():
            contact_value = contact_var.get().strip()
            if contact_value == self.app.t("chat_add_friend_contact_placeholder"):
                contact_value = ""
            display_name = name_var.get().strip()
            if not contact_value:
                messagebox.showwarning(self.app.t("warning_title"), self.app.t("chat_add_friend_contact_required"), parent=win)
                return
            method, normalized = self._detect_contact_method(contact_value)
            if not display_name:
                display_name = normalized
            friend_id = f"{self._thread_prefix()}f-{int(time.time() * 1000)}"
            friend = {
                "id": friend_id,
                "name": display_name,
                "contact_method": method,
                "contact_value": normalized,
                "phone": normalized if method == "phone" else "",
                "email": normalized if method == "email" else "",
                "account": normalized if method == "account" else "",
                "qr_code": normalized if method == "qr" else "",
                "scope": self.chat_scope,
                "created_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
            }
            self.app.chat.setdefault(self._friends_key(), []).append(friend)
            self._thread_store().setdefault(f"friend-{friend_id}", [])
            save_chat(self.app.chat)
            self.current_thread = f"friend-{friend_id}"
            win.destroy()
            self.refresh()

        tk.Button(footer, text=self.app.t("cancel_btn"), command=win.destroy, relief=tk.FLAT, bd=0, bg=self.colors["card_soft"], fg=self.colors["text_secondary"], activebackground=self.colors["primary_light"], activeforeground=self.colors["primary"], font=("Microsoft YaHei UI", 10, "bold"), padx=18, pady=10, cursor="hand2").pack(side=tk.RIGHT)
        tk.Button(footer, text=self.app.t("chat_add_friend_save"), command=save_friend, relief=tk.FLAT, bd=0, bg=self.colors["primary"], fg="white", activebackground=self.colors["primary"], activeforeground="white", font=("Microsoft YaHei UI", 10, "bold"), padx=18, pady=10, cursor="hand2").pack(side=tk.RIGHT, padx=(0, 10))
        name_entry.focus_set()

    def show_my_contact_code(self):
        card = self.app.contact_card() if hasattr(self.app, "contact_card") else {}
        code = card.get("public_contact_code") or ""
        win = tk.Toplevel(self.frame)
        win.title(self.app.t("chat_my_code_title"))
        win.configure(bg=self.colors["bg"])
        try:
            self.app._set_window_icon(win)
        except Exception:
            pass
        win.transient(self.frame.winfo_toplevel())
        center_window(win, self.app.root, 420, 520, min_width=380, min_height=460)
        card_frame = create_card(win, self.colors)
        card_frame.pack(fill=tk.BOTH, expand=True, padx=18, pady=18)
        tk.Label(card_frame, text=self.app.t("chat_my_code_title"), bg=self.colors["card"], fg=self.colors["text"], font=("Microsoft YaHei UI", 16, "bold")).pack(anchor="w", padx=18, pady=(16, 6))
        tk.Label(card_frame, text=self.app.t("chat_my_code_hint"), bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9), wraplength=350, justify=tk.LEFT).pack(anchor="w", padx=18, pady=(0, 14))

        qr_wrap = tk.Frame(card_frame, bg="white", highlightthickness=1, highlightbackground=self.colors["border"])
        qr_wrap.pack(pady=(4, 10))
        try:
            import qrcode
            qr = qrcode.QRCode(border=2, box_size=8)
            qr.add_data(code)
            qr.make(fit=True)
            qr_img = qr.make_image(fill_color="#111827", back_color="white").convert("RGB")
            qr_img = qr_img.resize((210, 210), Image.Resampling.NEAREST if hasattr(Image, "Resampling") else Image.NEAREST)
            self.my_contact_qr_photo = ImageTk.PhotoImage(qr_img)
            tk.Label(qr_wrap, image=self.my_contact_qr_photo, bg="white").pack(padx=8, pady=8)
        except Exception:
            qr_box = tk.Canvas(qr_wrap, width=210, height=210, bg="white", highlightthickness=0)
            qr_box.pack(padx=8, pady=8)
            # Fallback QR-style visual derived from the public code. It avoids
            # exposing internal IDs and keeps the real machine ID private.
            seed = sum((idx + 1) * ord(ch) for idx, ch in enumerate(code or "NEXA"))
            cell = 10
            margin = 10
            for y in range(19):
                for x in range(19):
                    finder = (x < 5 and y < 5) or (x > 13 and y < 5) or (x < 5 and y > 13)
                    bit = finder or ((seed + x * 17 + y * 31 + x * y * 7) % 5 in (0, 1))
                    if bit:
                        qr_box.create_rectangle(margin + x * cell, margin + y * cell, margin + (x + 1) * cell, margin + (y + 1) * cell, fill="#111827", outline="#111827")
        tk.Label(card_frame, text=code, bg=self.colors["card"], fg=self.colors["primary"], font=("Microsoft YaHei UI", 14, "bold")).pack(pady=(0, 6))
        tk.Label(card_frame, text=self.app.t("chat_my_code_privacy"), bg=self.colors["card"], fg=self.colors["text_light"], font=("Microsoft YaHei UI", 8), wraplength=350, justify=tk.LEFT).pack(padx=18, pady=(0, 12))

        footer = tk.Frame(card_frame, bg=self.colors["card"])
        footer.pack(fill=tk.X, padx=18, pady=(8, 16))
        def copy_code():
            try:
                self.app.root.clipboard_clear()
                self.app.root.clipboard_append(code)
                self.app.set_status("status_saved")
            except Exception:
                pass
        tk.Button(footer, text=self.app.t("chat_copy_code"), command=copy_code, relief=tk.FLAT, bd=0, bg=self.colors["primary"], fg="white", activebackground=self.colors["primary"], activeforeground="white", font=("Microsoft YaHei UI", 10, "bold"), padx=18, pady=10, cursor="hand2").pack(side=tk.LEFT)
        tk.Button(footer, text=self.app.t("profile_close"), command=win.destroy, relief=tk.FLAT, bd=0, bg=self.colors["card_soft"], fg=self.colors["text_secondary"], activebackground=self.colors["primary_light"], activeforeground=self.colors["primary"], font=("Microsoft YaHei UI", 10, "bold"), padx=18, pady=10, cursor="hand2").pack(side=tk.RIGHT)

    def add_group(self):
        name = simpledialog.askstring(self.app.t("chat_add_group"), self.app.t("chat_add_group_name").format(scope=self._scope_name()), parent=self.frame)
        if not name:
            return
        group_id = f"{self._thread_prefix()}g-{int(time.time() * 1000)}"
        group = {"id": group_id, "name": name.strip(), "scope": self.chat_scope, "members": [self.app.user_settings.get("username", "")], "created_at": datetime.now().strftime("%Y-%m-%d %H:%M")}
        self.app.chat.setdefault(self._groups_key(), []).append(group)
        self._thread_store().setdefault(f"group-{group_id}", [])
        save_chat(self.app.chat)
        self.current_thread = f"group-{group_id}"
        self.refresh()

    def _current_friend(self):
        if not self.current_thread.startswith("friend-"):
            return None
        fid = self.current_thread.replace("friend-", "", 1)
        return next((x for x in self.app.chat.get(self._friends_key(), []) if x.get("id") == fid), None)

    def _current_group(self):
        if not self.current_thread.startswith("group-"):
            return None
        gid = self.current_thread.replace("group-", "", 1)
        return next((x for x in self.app.chat.get(self._groups_key(), []) if x.get("id") == gid), None)

    def remove_current_friend(self):
        friend = self._current_friend()
        if not friend:
            return
        if not messagebox.askyesno(self.app.t("chat_remove_friend"), self.app.t("chat_remove_friend_confirm").format(name=friend.get("name", ""))):
            return
        self.app.chat[self._friends_key()] = [x for x in self.app.chat.get(self._friends_key(), []) if x.get("id") != friend.get("id")]
        self._thread_store().pop(self.current_thread, None)
        self.current_thread = "personal-notes" if self.chat_scope == "personal" else "company-group"
        save_chat(self.app.chat)
        self.refresh()

    def remove_current_group(self):
        group = self._current_group()
        if not group:
            return
        if not messagebox.askyesno(self.app.t("chat_remove_group"), self.app.t("chat_remove_group_confirm").format(name=group.get("name", ""))):
            return
        self.app.chat[self._groups_key()] = [x for x in self.app.chat.get(self._groups_key(), []) if x.get("id") != group.get("id")]
        self._thread_store().pop(self.current_thread, None)
        self.current_thread = "personal-notes" if self.chat_scope == "personal" else "company-group"
        save_chat(self.app.chat)
        self.refresh()

    def _thread_name(self, thread_id):
        if self.chat_scope == "personal":
            if thread_id == "personal-notes":
                return self.app.t("chat_personal_notes")
            if thread_id == "personal-todo":
                return self.app.t("chat_personal_todo")
        if thread_id == "company-group":
            company_name = self.app.enterprise.get("company_name") or self.app.t("chat_group")
            return f"{company_name} · {self.app.t('chat_group')}"
        if thread_id.startswith("group-"):
            gid = thread_id.replace("group-", "", 1)
            group = next((x for x in self.app.chat.get(self._groups_key(), []) if x.get("id") == gid), None)
            return f"{self.app.t('chat_group')} · {group.get('name', '')}" if group else self.app.t("chat_group")
        if thread_id.startswith("friend-"):
            fid = thread_id.replace("friend-", "", 1)
            friend = next((x for x in self.app.chat.get(self._friends_key(), []) if x.get("id") == fid), None)
            return friend.get("name", self._scope_name()) if friend else self._scope_name()
        for emp in self.app.enterprise.get("employees", []):
            if f"emp-{emp.get('id')}" == thread_id:
                return emp.get("name", "")
        return self.app.t("chat_select_hint")

    def refresh(self):
        self._build_scope_buttons()
        if not getattr(self, "_sticker_panel_built", False):
            self._build_sticker_panel()
            self._sticker_panel_built = True
        self.space_hint.config(text=self.app.t("chat_structure_hint_enterprise") if self.chat_scope == "enterprise" else self.app.t("chat_structure_hint_personal"))
        self._render_contacts()
        self._render_messages()

    def update_language(self):
        self.contacts_title.config(text=self.app.t("chat_contacts"))
        self.send_btn.config(text=self.app.t("chat_send"))
        self._sticker_panel_built = False
        if not self.input.get().strip() or self.input.get() in self.app.all_values("chat_input_placeholder"):
            self.input.delete(0, tk.END)
            self.input.insert(0, self.app.t("chat_input_placeholder"))
            self.input.config(fg=self.colors["text_secondary"])
        try:
            if not self.contact_search.get().strip() or self.contact_search.get() in self.app.all_values("chat_search_placeholder"):
                self.contact_search.delete(0, tk.END)
                self.contact_search.insert(0, self.app.t("chat_search_placeholder"))
                self.contact_search.config(fg=self.colors["text_light"])
        except Exception:
            pass
        self.refresh()

    def _section_label(self, text_key):
        tk.Label(self.contacts_frame, text=self.app.t(text_key), bg=self.colors["card"], fg=self.colors["text_light"], font=("Microsoft YaHei UI", 8, "bold")).pack(anchor="w", padx=8, pady=(12, 4))

    def _thread_preview(self, thread_id):
        messages = self._thread_store().get(thread_id, [])
        if not messages:
            return self.app.t("chat_preview_empty"), ""
        msg = messages[-1]
        preview = self.app.t("sticker_preview") if msg.get("type") == "sticker" else msg.get("text", "")
        preview = str(preview or self.app.t("chat_preview_empty")).replace("\n", " ").strip()
        if len(preview) > 34:
            preview = preview[:31].rstrip() + "..."
        return preview, str(msg.get("time", ""))[-5:]

    def _thread_unread_count(self, thread_id):
        if self.chat_scope != "enterprise" or thread_id == self.current_thread:
            return 0
        username = self.app.user_settings.get("username") or "Me"
        count = 0
        for msg in self._thread_store().get(thread_id, []):
            if msg.get("sender") != username and username not in msg.get("read_by", []):
                count += 1
        return count

    def _contact_matches(self, *values):
        query = getattr(self, "contact_query", "") or self._contact_search_text()
        if not query:
            return True
        haystack = " ".join(str(v or "") for v in values).lower()
        return query in haystack

    def _contact_button(self, thread_id, name, icon, removable=None):
        preview, last_time = self._thread_preview(thread_id)
        if not self._contact_matches(name, preview, thread_id):
            return False
        active = thread_id == self.current_thread
        bg = self.colors["primary_light"] if active else self.colors["card_soft"]
        row = tk.Frame(self.contacts_frame, bg=bg, highlightthickness=1, highlightbackground=self.colors["primary"] if active else self.colors["border"], cursor="hand2")
        row.pack(fill=tk.X, pady=3)
        row.bind("<Button-1>", lambda e, tid=thread_id: self.open_thread(tid))

        avatar_bg = self.colors["primary"] if active else self.colors["card"]
        avatar_fg = "white" if active else self.colors["primary"]
        avatar = tk.Label(row, text=icon, width=3, bg=avatar_bg, fg=avatar_fg, font=("Microsoft YaHei UI", 12, "bold"), cursor="hand2")
        avatar.pack(side=tk.LEFT, padx=(8, 6), pady=8, ipady=4)
        avatar.bind("<Button-1>", lambda e, tid=thread_id: self.open_thread(tid))

        center = tk.Frame(row, bg=bg, cursor="hand2")
        center.pack(side=tk.LEFT, fill=tk.X, expand=True, pady=7)
        center.bind("<Button-1>", lambda e, tid=thread_id: self.open_thread(tid))
        name_lbl = tk.Label(center, text=name, bg=bg, fg=self.colors["primary"] if active else self.colors["text"], font=("Microsoft YaHei UI", 10, "bold"), anchor="w", justify=tk.LEFT, wraplength=210, cursor="hand2")
        name_lbl.pack(fill=tk.X)
        name_lbl.bind("<Button-1>", lambda e, tid=thread_id: self.open_thread(tid))
        preview_lbl = tk.Label(center, text=preview, bg=bg, fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 8), anchor="w", justify=tk.LEFT, wraplength=210, cursor="hand2")
        preview_lbl.pack(fill=tk.X, pady=(2, 0))
        preview_lbl.bind("<Button-1>", lambda e, tid=thread_id: self.open_thread(tid))

        meta = tk.Frame(row, bg=bg)
        meta.pack(side=tk.RIGHT, padx=(2, 8), pady=8)
        if last_time:
            tk.Label(meta, text=last_time, bg=bg, fg=self.colors["text_light"], font=("Microsoft YaHei UI", 8)).pack(anchor="e")
        unread = self._thread_unread_count(thread_id)
        if unread:
            tk.Label(meta, text=str(min(unread, 99)), bg=self.colors["danger"], fg="white", font=("Microsoft YaHei UI", 8, "bold"), padx=6, pady=1).pack(anchor="e", pady=(5, 0))
        if removable == "friend":
            tk.Button(meta, text="×", command=lambda tid=thread_id: [self.open_thread(tid), self.remove_current_friend()], relief=tk.FLAT, bd=0, bg=bg, fg=self.colors["danger"], font=("Microsoft YaHei UI", 10, "bold"), cursor="hand2").pack(anchor="e", pady=(3, 0))
        elif removable == "group":
            tk.Button(meta, text="×", command=lambda tid=thread_id: [self.open_thread(tid), self.remove_current_group()], relief=tk.FLAT, bd=0, bg=bg, fg=self.colors["danger"], font=("Microsoft YaHei UI", 10, "bold"), cursor="hand2").pack(anchor="e", pady=(3, 0))
        return True

    def _render_contacts(self):
        for child in self.contacts_frame.winfo_children():
            child.destroy()
        if self.chat_scope == "enterprise" and (not self.app.enterprise.get("company_created") or not self.app.enterprise.get("company_name")):
            show_empty(self.contacts_frame, self.app.t("company_required_hint"), self.colors)
            return

        rendered = 0
        self._section_label("chat_section_messages")
        if self.chat_scope == "personal":
            rendered += 1 if self._contact_button("personal-notes", self.app.t("chat_personal_notes"), "◇") else 0
            rendered += 1 if self._contact_button("personal-todo", self.app.t("chat_personal_todo"), "✓") else 0
        else:
            company_name = self.app.enterprise.get("company_name", "")
            rendered += 1 if self._contact_button("company-group", f"{company_name} · {self.app.t('chat_group')}", "▥") else 0

        groups = self.app.chat.get(self._groups_key(), [])
        if groups:
            self._section_label("chat_section_groups")
            for group in groups:
                rendered += 1 if self._contact_button(f"group-{group.get('id')}", group.get("name", ""), "◎", removable="group") else 0

        if self.chat_scope == "enterprise":
            employees = [emp for emp in self.app.enterprise.get("employees", []) if emp.get("status", "active") == "active"]
            if employees:
                self._section_label("chat_section_staff")
                for emp in employees:
                    rendered += 1 if self._contact_button(f"emp-{emp.get('id')}", emp.get("name", ""), "●") else 0

        friends = self.app.chat.get(self._friends_key(), [])
        if friends:
            self._section_label("chat_section_external" if self.chat_scope == "enterprise" else "chat_section_friends")
            for friend in friends:
                rendered += 1 if self._contact_button(f"friend-{friend.get('id')}", friend.get("name", ""), "☆", removable="friend") else 0
        if rendered == 0:
            show_empty(self.contacts_frame, self.app.t("chat_search_empty"), self.colors)

    def open_thread(self, thread_id):
        self.current_thread = thread_id
        self._thread_store().setdefault(thread_id, [])
        self._mark_thread_read()
        self.refresh()

    def _mark_thread_read(self):
        if self.chat_scope != "enterprise":
            return
        username = self.app.user_settings.get("username") or "Me"
        changed = False
        for msg in self._thread_store().setdefault(self.current_thread, []):
            read_by = msg.setdefault("read_by", [])
            if username not in read_by:
                read_by.append(username)
                changed = True
        if changed:
            save_chat(self.app.chat)

    def _render_messages(self):
        for child in self.messages_frame.winfo_children():
            child.destroy()
        self.thread_title.config(text=self._thread_name(self.current_thread))
        friend = self._current_friend()
        group = self._current_group()
        if friend:
            self.read_hint.config(text=self._friend_contact_summary(friend))
        elif group:
            self.read_hint.config(text=self.app.t("chat_group_members").format(count=len(group.get("members", []))))
        else:
            self.read_hint.config(text=self.app.t("chat_read_status_hint") if self.chat_scope == "enterprise" else self.app.t("chat_personal_no_read"))
        messages = self._thread_store().setdefault(self.current_thread, [])
        self._mark_thread_read()
        if not messages:
            show_empty(self.messages_frame, self.app.t("chat_empty"), self.colors)
            return
        username = self.app.user_settings.get("username") or "Me"
        self._sticker_refs = []
        display_limit = 60 if getattr(self.app, "is_low_power_mode", lambda: False)() else 100
        for msg in messages[-display_limit:]:
            mine = msg.get("sender") == username
            wrap = tk.Frame(self.messages_frame, bg=self.colors["card"])
            wrap.pack(fill=tk.X, pady=6)
            bubble_bg = self.colors["primary_light"] if mine else self.colors["card_soft"]
            bubble = tk.Frame(wrap, bg=bubble_bg, highlightthickness=1, highlightbackground=self.colors["border"])
            bubble.pack(anchor="e" if mine else "w", padx=14)
            sender = f"{msg.get('sender','')} · {msg.get('time','')}"
            tk.Label(bubble, text=sender, font=("Microsoft YaHei UI", 8), fg=self.colors["text_secondary"], bg=bubble_bg).pack(anchor="w", padx=10, pady=(8, 0))
            if msg.get("type") == "sticker":
                photo = self._sticker_photo(msg.get("sticker_file", ""), 96)
                if photo:
                    self._sticker_refs.append(photo)
                    tk.Label(bubble, image=photo, bg=bubble_bg).pack(anchor="w", padx=8, pady=(6, 4))
                # Sticker messages are image-only to avoid broken glyphs and clutter.
            else:
                tk.Label(bubble, text=msg.get("text", ""), font=("Microsoft YaHei UI", 11), fg=self.colors["text"], bg=bubble_bg, wraplength=620, justify=tk.LEFT).pack(anchor="w", padx=10, pady=(6, 4))
            if self.chat_scope == "enterprise" and mine:
                read_by = [x for x in msg.get("read_by", []) if x != username]
                read_text = self.app.t("chat_read_by").format(count=len(read_by)) if read_by else self.app.t("chat_delivered")
                tk.Label(bubble, text=read_text, font=("Microsoft YaHei UI", 8, "bold"), fg=self.colors["primary"], bg=bubble_bg).pack(anchor="e", padx=10, pady=(0, 8))
            else:
                tk.Frame(bubble, height=4, bg=bubble_bg).pack(fill=tk.X)
        self.canvas.after(90 if getattr(self.app, "is_low_power_mode", lambda: False)() else 50, lambda: self.canvas.yview_moveto(1.0))

    def _append_message(self, msg):
        self._thread_store().setdefault(self.current_thread, []).append(msg)
        save_chat(self.app.chat)
        self.app.set_status("status_saved")
        self.refresh()

    def _can_send(self):
        if self.chat_scope == "enterprise" and (not self.app.enterprise.get("company_created") or not self.app.enterprise.get("company_name")):
            show_empty(self.messages_frame, self.app.t("company_required_hint"), self.colors)
            return False
        return True

    def send_sticker(self, sticker_id, label, file_name):
        if not self._can_send():
            return
        sender = self.app.user_settings.get("username") or "Me"
        msg = {"sender": sender, "type": "sticker", "sticker_id": sticker_id, "sticker_label": label, "sticker_file": file_name, "time": datetime.now().strftime("%Y-%m-%d %H:%M"), "scope": self.chat_scope, "thread": self.current_thread}
        if self.chat_scope == "enterprise":
            msg["read_by"] = [sender]
        self._remember_sticker(sticker_id, label, file_name)
        self._append_message(msg)

    def send_message(self):
        if not self._can_send():
            return
        text = self.input.get().strip()
        if not text or text == self.app.t("chat_input_placeholder") or text in self.app.all_values("chat_input_placeholder"):
            return
        sender = self.app.user_settings.get("username") or "Me"
        msg = {"sender": sender, "text": text, "time": datetime.now().strftime("%Y-%m-%d %H:%M"), "type": "text", "scope": self.chat_scope, "thread": self.current_thread}
        if self.chat_scope == "enterprise":
            msg["read_by"] = [sender]
        self._append_message(msg)
        self.input.delete(0, tk.END)
        self._focus_out()
