
import copy
import csv
import re
import time
import tkinter as tk
from datetime import datetime
from pathlib import Path
from tkinter import filedialog, messagebox

from core.storage import save_documents, save_settings
from core.ui import ToolTip, center_window, create_button, create_card, create_dialog_window, prompt_text_dialog, show_empty


def now_text():
    return datetime.now().strftime("%Y-%m-%d %H:%M")


def make_doc_id():
    return str(int(time.time() * 1000))


class FilesPage:
    """Files module: personal/enterprise scoped documents, sheets, and file records."""

    def __init__(self, app, parent):
        self.app = app
        self.colors = app.colors
        self.frame = tk.Frame(parent, bg=self.colors["bg"])
        self.current_file_id = None
        self.current_filter = "all"
        self.current_folder = "__all__"
        self.sort_mode = "updated"
        self._autosave_job = None
        self._doc_autosave_job = None
        self._file_search_job = None
        # v2026.06.20.21: render long file lists in small chunks so opening
        # the Files page does not freeze when many local/enterprise documents exist.
        self._file_list_render_job = None
        self._file_list_render_token = 0
        # v2026.06.20.24: opened-file metadata is saved with debounce so
        # recent files do not add disk churn during fast navigation.
        self._open_meta_save_job = None
        # v2026.06.20.23: keep rendered file-card references so opening a file
        # only updates the active highlight instead of rebuilding the whole list.
        self._file_cards = {}
        # v2026.06.20.31: local search text cache avoids repeatedly lowercasing
        # large document contents while typing in the file search box.
        self._file_search_cache = {}
        self._last_flush_at = 0.0
        self.local_scope = self.app.current_data_scope() if hasattr(self.app, "current_data_scope") else self.app.get_app_mode()
        self.sheet_cells = []
        self.draw_color = self.colors["primary"]
        self.draw_eraser = False
        self.last_draw = None
        self._build()

    def _build(self):
        hero = create_card(self.frame, self.colors, fill=tk.X, pady=(0, 10))
        hero_inner = tk.Frame(hero, bg=self.colors["card"])
        hero_inner.pack(fill=tk.X, padx=20, pady=16)

        left = tk.Frame(hero_inner, bg=self.colors["card"])
        left.pack(side=tk.LEFT, fill=tk.X, expand=True)
        title_row = tk.Frame(left, bg=self.colors["card"])
        title_row.pack(anchor="w", fill=tk.X)
        self.page_title = tk.Label(title_row, text=self.app.t("shared_files_title"), font=("Microsoft YaHei UI", 22, "bold"), fg=self.colors["text"], bg=self.colors["card"])
        self.page_title.pack(side=tk.LEFT)
        self.scope_badge = tk.Label(title_row, text="", font=("Microsoft YaHei UI", 9, "bold"), fg=self.colors["primary"], bg=self.colors["primary_light"], padx=10, pady=4)
        self.scope_badge.pack(side=tk.LEFT, padx=12)
        self.page_desc = tk.Label(left, text=self.app.t("shared_files_desc"), font=("Microsoft YaHei UI", 10), fg=self.colors["text_secondary"], bg=self.colors["card"])
        self.page_desc.pack(anchor="w", pady=(4, 0))
        self.scope_switch = tk.Frame(left, bg=self.colors["card"])
        self.scope_switch.pack(anchor="w", pady=(10, 0))
        self.personal_scope_btn = tk.Button(self.scope_switch, text=self.app.t("mode_personal"), command=lambda: self._switch_scope("personal"), relief=tk.FLAT, bd=0, font=("Microsoft YaHei UI", 9, "bold"), padx=12, pady=6, cursor="hand2")
        self.personal_scope_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.enterprise_scope_btn = tk.Button(self.scope_switch, text=self.app.t("mode_enterprise"), command=lambda: self._switch_scope("enterprise"), relief=tk.FLAT, bd=0, font=("Microsoft YaHei UI", 9, "bold"), padx=12, pady=6, cursor="hand2")
        self.enterprise_scope_btn.pack(side=tk.LEFT)

        # Title/description are now shown in the global page header to avoid duplicate headings.
        try:
            title_row.pack_forget()
            self.page_desc.pack_forget()
            self.scope_switch.pack_configure(pady=(0, 0))
            hero_inner.pack_configure(padx=18, pady=10)
        except Exception:
            pass

        actions = tk.Frame(hero_inner, bg=self.colors["card"])
        actions.pack(side=tk.RIGHT)
        self.new_doc_btn = create_button(actions, self.app.t("shared_new_doc"), self.new_doc, self.colors)
        self.new_doc_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.new_sheet_btn = create_button(actions, self.app.t("shared_new_sheet"), self.new_sheet, self.colors)
        self.new_sheet_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.batch_create_btn = create_button(actions, self.app.t("shared_batch_create"), self.batch_create_dialog, self.colors)
        self.batch_create_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.upload_btn = create_button(actions, self.app.t("shared_upload"), self.upload_file, self.colors)
        self.upload_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.save_btn = create_button(actions, self.app.t("shared_save"), self.save_current, self.colors)
        self.save_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.duplicate_btn = create_button(actions, self.app.t("shared_duplicate_file"), self.duplicate_current_file, self.colors)
        self.duplicate_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.favorite_btn = create_button(actions, self.app.t("shared_mark_favorite"), self.toggle_current_favorite, self.colors)
        self.favorite_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.archive_btn = create_button(actions, self.app.t("shared_archive_file"), self.archive_current_file, self.colors)
        self.archive_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.properties_btn = create_button(actions, self.app.t("shared_file_properties"), self.show_file_properties, self.colors)
        self.properties_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.full_editor_btn = create_button(actions, self.app.t("shared_open_editor"), self.open_full_editor, self.colors)
        self.full_editor_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.export_btn = create_button(actions, self.app.t("shared_export"), self.export_current_file, self.colors)
        self.export_btn.pack(side=tk.LEFT)

        content = tk.Frame(self.frame, bg=self.colors["bg"])
        content.pack(fill=tk.BOTH, expand=True)

        self.left_card = create_card(content, self.colors)
        self.left_card.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 12))
        self.left_card.configure(width=380)
        self.left_card.pack_propagate(False)

        filter_bar = tk.Frame(self.left_card, bg=self.colors["card"])
        filter_bar.pack(fill=tk.X, padx=14, pady=(14, 8))
        self.filter_buttons = {}
        for key, label_key in [("all", "shared_filter_all"), ("doc", "shared_filter_docs"), ("sheet", "shared_filter_sheets"), ("favorite", "shared_filter_favorites"), ("recent", "shared_filter_recent"), ("archive", "shared_filter_archive")]:
            btn = tk.Button(
                filter_bar,
                text=self.app.t(label_key),
                command=lambda k=key: self.set_filter(k),
                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", 9, "bold"),
                padx=12,
                pady=7,
                cursor="hand2",
            )
            btn.pack(side=tk.LEFT, padx=(0, 6))
            self.filter_buttons[key] = btn

        # v2026.06.20.27: add lightweight folder organization to the Files module.
        # Folders are user-facing labels; the internal document id remains hidden and stable.
        folder_row = tk.Frame(self.left_card, bg=self.colors["card"])
        folder_row.pack(fill=tk.X, padx=14, pady=(0, 10))
        self.folder_label = tk.Label(folder_row, text=self.app.t("shared_folder_label"), bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9, "bold"))
        self.folder_label.pack(side=tk.LEFT, padx=(2, 8))
        self.folder_var = tk.StringVar(value=self.app.t("shared_all_folders"))
        self.folder_menu = tk.OptionMenu(folder_row, self.folder_var, self.app.t("shared_all_folders"))
        self.folder_menu.configure(
            bg=self.colors["card_soft"],
            fg=self.colors["text"],
            activebackground=self.colors["primary_light"],
            activeforeground=self.colors["primary"],
            relief=tk.FLAT,
            bd=0,
            highlightthickness=0,
            font=("Microsoft YaHei UI", 9, "bold"),
            cursor="hand2",
        )
        self.folder_menu.pack(side=tk.LEFT, fill=tk.X, expand=True)
        self.new_folder_btn = tk.Button(folder_row, text="+", command=self.new_folder_dialog, bg=self.colors["card_soft"], fg=self.colors["primary"], relief=tk.FLAT, bd=0, font=("Microsoft YaHei UI", 10, "bold"), cursor="hand2", padx=8, pady=3)
        self.new_folder_btn.pack(side=tk.LEFT, padx=(8, 0))
        self.move_folder_btn = tk.Button(folder_row, text="↦", command=self.move_current_to_folder, bg=self.colors["card_soft"], fg=self.colors["primary"], relief=tk.FLAT, bd=0, font=("Microsoft YaHei UI", 10, "bold"), cursor="hand2", padx=8, pady=3)
        self.move_folder_btn.pack(side=tk.LEFT, padx=(6, 0))
        ToolTip(self.new_folder_btn, lambda: self.app.t("shared_new_folder"))
        ToolTip(self.move_folder_btn, lambda: self.app.t("shared_move_to_folder"))

        search_box = tk.Frame(self.left_card, bg=self.colors["card_soft"], highlightthickness=1, highlightbackground=self.colors["border"])
        search_box.pack(fill=tk.X, padx=14, pady=(0, 12))
        tk.Label(search_box, text="⌕", bg=self.colors["card_soft"], fg=self.colors["text_light"], font=("Microsoft YaHei UI", 11, "bold")).pack(side=tk.LEFT, padx=(10, 0))
        self.file_search_var = tk.StringVar()
        self.file_search = tk.Entry(search_box, textvariable=self.file_search_var, relief=tk.FLAT, bd=0, bg=self.colors["card_soft"], fg=self.colors["text"], font=("Microsoft YaHei UI", 10))
        self.file_search.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=8, pady=8)
        self.file_search.bind("<KeyRelease>", self._debounced_refresh_file_list)
        self.clear_search_btn = tk.Button(search_box, text="×", command=self.clear_file_search, bg=self.colors["card_soft"], fg=self.colors["text_light"], activebackground=self.colors["primary_light"], activeforeground=self.colors["primary"], relief=tk.FLAT, bd=0, font=("Microsoft YaHei UI", 10, "bold"), cursor="hand2")
        self.clear_search_btn.pack(side=tk.RIGHT, padx=(0, 8))

        sort_row = tk.Frame(self.left_card, bg=self.colors["card"])
        sort_row.pack(fill=tk.X, padx=14, pady=(0, 10))
        tk.Label(sort_row, text=self.app.t("shared_sort_label"), bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9, "bold")).pack(side=tk.LEFT, padx=(2, 8))
        self.sort_buttons = {}
        for key, label_key in [("updated", "shared_sort_recent"), ("opened", "shared_sort_opened"), ("name", "shared_sort_name"), ("type", "shared_sort_type"), ("folder", "shared_sort_folder")]:
            btn = tk.Button(
                sort_row,
                text=self.app.t(label_key),
                command=lambda k=key: self.set_sort(k),
                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", 8, "bold"),
                padx=8,
                pady=5,
                cursor="hand2",
            )
            btn.pack(side=tk.LEFT, padx=(0, 5))
            self.sort_buttons[key] = btn

        self.list_summary = tk.Label(self.left_card, text="", bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9))
        self.list_summary.pack(anchor="w", padx=16, pady=(0, 8))

        self.list_canvas = tk.Canvas(self.left_card, bg=self.colors["card"], highlightthickness=0)
        self.list_scroll = tk.Scrollbar(self.left_card, orient="vertical", command=self.list_canvas.yview)
        self.list_inner = tk.Frame(self.list_canvas, bg=self.colors["card"])
        self.list_canvas.configure(yscrollcommand=self.list_scroll.set)
        self.list_window = self.list_canvas.create_window((0, 0), window=self.list_inner, anchor="nw")
        self.list_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(8, 0), pady=(0, 12))
        self.list_scroll.pack(side=tk.RIGHT, fill=tk.Y, padx=(0, 8), pady=(0, 12))
        self.list_scroll_visible = True
        self.list_canvas.bind("<MouseWheel>", lambda e: self._mousewheel(self.list_canvas, e))
        self.list_inner.bind("<MouseWheel>", lambda e: self._mousewheel(self.list_canvas, e))
        self.list_inner.bind("<Configure>", self._update_list_scroll)
        self.list_canvas.bind("<Configure>", self._update_list_scroll)

        self.right_card = create_card(content, self.colors)
        self.right_card.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.file_header = tk.Frame(self.right_card, bg=self.colors["card"])
        self.file_header.pack(fill=tk.X, padx=18, pady=(16, 8))
        self.type_badge_box = tk.Frame(self.file_header, bg=self.colors["primary_light"], width=54, height=28)
        self.type_badge_box.pack(side=tk.LEFT)
        self.type_badge_box.pack_propagate(False)
        self.type_badge = tk.Label(self.type_badge_box, text="DOC", font=("Microsoft YaHei UI", 9, "bold"), bg=self.colors["primary_light"], fg=self.colors["primary"])
        self.type_badge.pack(expand=True, fill=tk.BOTH)
        self.title_var = tk.StringVar(value=self.app.t("shared_untitled_doc"))
        self.title_entry = tk.Entry(self.file_header, textvariable=self.title_var, font=("Microsoft YaHei UI", 14, "bold"), relief=tk.FLAT, bd=0, bg=self.colors["card"], fg=self.colors["text"])
        self.title_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=12)
        self.title_entry.bind("<KeyRelease>", lambda e: self._mark_unsaved())
        self.title_entry.bind("<FocusOut>", lambda e: self.save_current(silent=True))
        self.title_entry.bind("<Return>", lambda e: self.save_current())
        self.share_state = tk.Label(self.file_header, text=self.app.t("shared_status_shared"), bg=self.colors["success_light"], fg=self.colors["success"], font=("Microsoft YaHei UI", 9, "bold"), padx=8, pady=4)
        self.share_state.pack(side=tk.RIGHT)

        # v2026.06.20.21: the document/sheet toolbar can become wider than
        # small laptop screens. Keep every action available through a horizontal
        # toolbar instead of letting buttons disappear outside the right edge.
        self.tool_shell = tk.Frame(self.right_card, bg=self.colors["card"])
        self.tool_shell.pack(fill=tk.X, padx=18, pady=(0, 8))
        self.tool_canvas = tk.Canvas(self.tool_shell, bg=self.colors["card"], highlightthickness=0, height=44)
        self.tool_canvas.pack(fill=tk.X, expand=True)
        self.tool_xbar = tk.Scrollbar(self.tool_shell, orient="horizontal", command=self.tool_canvas.xview)
        self.tool_canvas.configure(xscrollcommand=self.tool_xbar.set)
        self.tool_scroll_visible = False
        self.tool_area = tk.Frame(self.tool_canvas, bg=self.colors["card"])
        self.tool_window = self.tool_canvas.create_window((0, 0), window=self.tool_area, anchor="nw")
        self.tool_area.bind("<Configure>", self._update_tool_scroll)
        self.tool_canvas.bind("<Configure>", self._update_tool_scroll)
        self.tool_canvas.bind("<Shift-MouseWheel>", lambda e, c=self.tool_canvas: self._xwheel(c, e))
        self.tool_area.bind("<Shift-MouseWheel>", lambda e, c=self.tool_canvas: self._xwheel(c, e))

        self.editor_host = tk.Frame(self.right_card, bg=self.colors["card"])
        self.editor_host.pack(fill=tk.BOTH, expand=True, padx=18, pady=(0, 8))

        self.file_footer = tk.Frame(self.right_card, bg=self.colors["card_soft"], highlightthickness=1, highlightbackground=self.colors["border"])
        self.file_footer.pack(fill=tk.X, padx=18, pady=(0, 18))
        self.file_status_label = tk.Label(self.file_footer, text="", bg=self.colors["card_soft"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9, "bold"), anchor="w")
        self.file_status_label.pack(side=tk.LEFT, padx=12, pady=8)
        self.file_hint_label = tk.Label(self.file_footer, text=self.app.t("shared_shortcuts_hint"), bg=self.colors["card_soft"], fg=self.colors["text_light"], font=("Microsoft YaHei UI", 9), anchor="e")
        self.file_hint_label.pack(side=tk.RIGHT, padx=12, pady=8)

        self._bind_shortcuts()
        self.set_filter("all")
        self.refresh()

    def _update_list_scroll(self, event=None):
        self.list_canvas.configure(scrollregion=self.list_canvas.bbox("all"))
        try:
            self.list_canvas.itemconfig(self.list_window, width=max(self.list_canvas.winfo_width() - 4, 260))
            needed = self.list_inner.winfo_reqheight() > self.list_canvas.winfo_height() + 2
            if needed and not self.list_scroll_visible:
                self.list_scroll.pack(side=tk.RIGHT, fill=tk.Y, padx=(0, 8), pady=(0, 12))
                self.list_scroll_visible = True
            elif not needed and self.list_scroll_visible:
                self.list_scroll.pack_forget()
                self.list_scroll_visible = False
        except Exception:
            pass

    def _update_tool_scroll(self, event=None):
        try:
            if not hasattr(self, "tool_canvas") or not hasattr(self, "tool_area"):
                return
            self.tool_canvas.configure(scrollregion=self.tool_canvas.bbox("all"))
            req_w = int(self.tool_area.winfo_reqwidth() or 0)
            req_h = int(self.tool_area.winfo_reqheight() or 40)
            view_w = int(self.tool_canvas.winfo_width() or 1)
            needed = req_w > view_w + 6
            if needed and not getattr(self, "tool_scroll_visible", False):
                self.tool_xbar.pack(fill=tk.X, pady=(2, 0))
                self.tool_scroll_visible = True
            elif not needed and getattr(self, "tool_scroll_visible", False):
                self.tool_xbar.pack_forget()
                self.tool_scroll_visible = False
                self.tool_canvas.xview_moveto(0)
            self.tool_canvas.configure(height=max(42, min(req_h + 4, 54)))
        except Exception:
            pass

    def _mousewheel(self, canvas, event):
        delta = getattr(event, "delta", 0)
        if not delta:
            return
        step = -1 * int(delta / 120) if abs(delta) >= 120 else (-1 if delta > 0 else 1)
        canvas.yview_scroll(step, "units")
        return "break"

    def _is_active_page(self):
        return getattr(self.app, "current_page", "") == "files"

    def _bind_shortcuts(self):
        try:
            # Avoid duplicated global shortcuts after page rebuilds.
            for seq in ("<Control-s>", "<Control-n>", "<Control-Shift-N>", "<Control-f>", "<Control-h>", "<Control-H>", "<Control-d>", "<Control-e>", "<Delete>", "<F2>"):
                try:
                    self.frame.unbind_all(seq)
                except Exception:
                    pass
            self.frame.bind_all("<Control-s>", self._shortcut_save, add="+")
            self.frame.bind_all("<Control-n>", self._shortcut_new_doc, add="+")
            self.frame.bind_all("<Control-Shift-N>", self._shortcut_new_sheet, add="+")
            self.frame.bind_all("<Control-f>", self._shortcut_focus_search, add="+")
            self.frame.bind_all("<Control-h>", self._shortcut_replace, add="+")
            self.frame.bind_all("<Control-H>", self._shortcut_replace, add="+")
            self.frame.bind_all("<Control-d>", self._shortcut_duplicate, add="+")
            self.frame.bind_all("<Control-e>", self._shortcut_export, add="+")
            self.frame.bind_all("<Delete>", self._shortcut_delete_current, add="+")
            self.frame.bind_all("<F2>", self._shortcut_rename, add="+")
        except Exception:
            pass

    def _shortcut_save(self, event=None):
        if not self._is_active_page():
            return None
        self.save_current()
        return "break"

    def _shortcut_new_doc(self, event=None):
        if not self._is_active_page():
            return None
        self.new_doc()
        return "break"

    def _shortcut_new_sheet(self, event=None):
        if not self._is_active_page():
            return None
        self.new_sheet()
        return "break"

    def _shortcut_rename(self, event=None):
        if not self._is_active_page():
            return None
        try:
            self.title_entry.focus_set()
            self.title_entry.selection_range(0, tk.END)
        except Exception:
            pass
        return "break"

    def _shortcut_focus_search(self, event=None):
        if not self._is_active_page():
            return None
        try:
            self.file_search.focus_set()
            self.file_search.selection_range(0, tk.END)
        except Exception:
            pass
        return "break"

    def _shortcut_replace(self, event=None):
        if not self._is_active_page():
            return None
        doc = self.app.documents.get(self.current_file_id or "")
        if isinstance(doc, dict) and doc.get("type") != "sheet":
            self.replace_in_doc()
            return "break"
        return None

    def _shortcut_duplicate(self, event=None):
        if not self._is_active_page():
            return None
        self.duplicate_current_file()
        return "break"

    def _shortcut_export(self, event=None):
        if not self._is_active_page():
            return None
        self.export_current_file()
        return "break"

    def _shortcut_delete_current(self, event=None):
        if not self._is_active_page():
            return None
        try:
            focus = self.app.root.focus_get()
            if focus in (getattr(self, "file_search", None), getattr(self, "title_entry", None), getattr(self, "sheet_formula_entry", None), getattr(self, "text_editor", None)):
                return None
            if getattr(self, "_active_sheet_cell", None) and focus and focus.winfo_class() == "Entry":
                rr, cc, var = self._active_sheet_cell
                self.sheet_raw[rr][cc] = ""
                var.set("")
                if hasattr(self, "sheet_formula_var"):
                    self.sheet_formula_var.set("")
                self._mark_unsaved()
                self._refresh_sheet_display()
                self._schedule_autosave()
                return "break"
        except Exception:
            pass
        if self.current_file_id:
            self.delete_file(self.current_file_id)
        return "break"

    def set_sort(self, mode):
        if mode not in ("updated", "opened", "name", "type", "folder"):
            mode = "updated"
        self.sort_mode = mode
        for key, btn in getattr(self, "sort_buttons", {}).items():
            active = key == mode
            btn.config(
                bg=self.colors["primary"] if active else self.colors["card_soft"],
                fg="white" if active else self.colors["text_secondary"],
            )
        self.refresh_file_list()

    def _show_file_menu(self, event, doc_id):
        if doc_id not in self.app.documents:
            return "break"
        menu = tk.Menu(self.frame, tearoff=0)
        menu.add_command(label=self.app.t("shared_open_file"), command=lambda did=doc_id: self.open_file(did))
        menu.add_command(label=self.app.t("shared_rename_file"), command=lambda did=doc_id: self._rename_from_menu(did))
        menu.add_command(label=self.app.t("shared_move_to_folder"), command=lambda did=doc_id: self.move_file_to_folder_by_id(did))
        fav_label = self.app.t("shared_unmark_favorite") if self.app.documents.get(doc_id, {}).get("favorite") else self.app.t("shared_mark_favorite")
        menu.add_command(label=fav_label, command=lambda did=doc_id: self.toggle_file_favorite_by_id(did))
        menu.add_command(label=self.app.t("shared_duplicate_file"), command=lambda did=doc_id: self._duplicate_file_by_id(did))
        menu.add_separator()
        menu.add_command(label=self.app.t("shared_export"), command=lambda did=doc_id: self._export_file_by_id(did))
        menu.add_command(label=self.app.t("shared_delete_file"), command=lambda did=doc_id: self.delete_file(did))
        try:
            menu.tk_popup(event.x_root, event.y_root)
        finally:
            menu.grab_release()
        return "break"

    def _rename_from_menu(self, doc_id):
        if doc_id not in self.app.documents:
            return
        self.open_file(doc_id)
        try:
            self.title_entry.focus_set()
            self.title_entry.selection_range(0, tk.END)
        except Exception:
            pass

    def _duplicate_file_by_id(self, doc_id):
        if doc_id not in self.app.documents:
            return
        self.open_file(doc_id)
        self.duplicate_current_file()

    def _export_file_by_id(self, doc_id):
        if doc_id not in self.app.documents:
            return
        self.open_file(doc_id)
        self.export_current_file()

    def _schedule_autosave(self, event=None):
        if not self.current_file_id:
            return
        if self._autosave_job:
            try:
                self.app.root.after_cancel(self._autosave_job)
            except Exception:
                pass
        delay = 1500 if getattr(self.app, "is_low_power_mode", lambda: False)() else 1000
        self._autosave_job = self.app.root.after(delay, self._run_autosave)

    def _run_autosave(self):
        self._autosave_job = None
        try:
            if self.save_current(silent=True):
                self.app.set_status("status_saved")
        except Exception:
            pass

    def clear_file_search(self):
        if hasattr(self, "file_search_var"):
            self.file_search_var.set("")
        self.refresh_file_list()

    def _debounced_refresh_file_list(self, event=None):
        try:
            if self._file_search_job:
                self.frame.after_cancel(self._file_search_job)
        except Exception:
            pass
        self._file_search_job = self.frame.after(160, self.refresh_file_list)

    def _cached_doc_search_text(self, doc_id, doc, title_text=None, folder_text=None):
        """Return cached lower-case searchable text for one file.

        Searching many large documents used to rebuild ``content.lower()`` for
        every keypress.  Cache by a small signature so typing in the search box
        stays smooth even when the file list grows.
        """
        if not isinstance(doc, dict):
            return ""
        title_text = title_text if title_text is not None else self.app.display_doc_title(doc.get("title", ""))
        folder_text = folder_text if folder_text is not None else self._folder_display(doc.get("folder"))
        content = str(doc.get("content", "") or "")
        signature = (
            str(title_text),
            str(folder_text),
            str(doc.get("updated", "")),
            len(content),
            content[:64],
            content[-64:] if len(content) > 64 else "",
        )
        cached = self._file_search_cache.get(doc_id)
        if cached and cached.get("signature") == signature:
            return cached.get("text", "")
        search_text = f"{title_text}\n{folder_text}\n{content}".lower()
        self._file_search_cache[doc_id] = {"signature": signature, "text": search_text}
        return search_text

    def _prune_file_search_cache(self):
        try:
            active_ids = set(self.app.documents.keys())
            for doc_id in list(self._file_search_cache.keys()):
                if doc_id not in active_ids:
                    self._file_search_cache.pop(doc_id, None)
        except Exception:
            pass

    def flush_pending_changes(self, reason=""):
        """Save visible file edits before page switch / quit without rebuilding UI."""
        now = time.monotonic()
        # Coalesce accidental duplicate flush calls from fast page switching.
        if now - getattr(self, "_last_flush_at", 0.0) < 0.25:
            return False
        self._last_flush_at = now
        try:
            if self._autosave_job:
                self.app.root.after_cancel(self._autosave_job)
                self._autosave_job = None
        except Exception:
            pass
        try:
            if self._doc_autosave_job:
                self.app.root.after_cancel(self._doc_autosave_job)
                self._doc_autosave_job = None
        except Exception:
            pass
        changed = False
        try:
            changed = bool(self.save_current(silent=True))
        except Exception:
            changed = False
        try:
            if self._open_meta_save_job:
                self.frame.after_cancel(self._open_meta_save_job)
                self._open_meta_save_job = None
                save_documents(self.app.documents)
                changed = True
        except Exception:
            pass
        return changed

    def _format_size(self, size):
        try:
            size = int(size or 0)
        except Exception:
            return ""
        if size <= 0:
            return ""
        units = ["B", "KB", "MB", "GB"]
        value = float(size)
        idx = 0
        while value >= 1024 and idx < len(units) - 1:
            value /= 1024.0
            idx += 1
        return f"{value:.1f} {units[idx]}" if idx else f"{int(value)} {units[idx]}"

    def _mark_unsaved(self):
        try:
            self.share_state.config(text=self.app.t("shared_status_unsaved"), bg=self.colors["warning_light"], fg=self.colors["warning"])
            self._update_file_footer(status_key="shared_status_unsaved")
        except Exception:
            pass

    def _update_file_footer(self, doc=None, status_key=None):
        if not hasattr(self, "file_status_label"):
            return
        if doc is None and self.current_file_id:
            doc = self.app.documents.get(self.current_file_id)
        if not isinstance(doc, dict):
            self.file_status_label.config(text=self.app.t("shared_no_file_selected"))
            return
        doc_type = self.app.t("shared_type_sheet") if doc.get("type") == "sheet" else self.app.t("shared_type_doc")
        title = self.app.display_doc_title(doc.get("title", ""))
        size_text = self._format_size(doc.get("file_size"))
        updated = doc.get("updated", "")
        status = self.app.t(status_key or "shared_status_saved")
        parts = [status, doc_type, title, f"{self.app.t('shared_folder_label')} {self._folder_display(doc.get('folder'))}"]
        if size_text:
            parts.append(size_text)
        if updated:
            parts.append(updated)
        self.file_status_label.config(text=" · ".join(parts))

    def _render_empty_editor(self):
        self.current_file_id = None
        self.title_var.set("")
        self.type_badge_box.config(bg=self.colors["card_soft"]); self.type_badge.config(text="—", bg=self.colors["card_soft"], fg=self.colors["text_secondary"])
        self.share_state.config(text="")
        self._update_file_footer(doc=None)
        self._clear_tools()
        empty = tk.Frame(self.editor_host, bg=self.colors["card_soft"], highlightthickness=1, highlightbackground=self.colors["border"])
        empty.pack(fill=tk.BOTH, expand=True)
        tk.Label(empty, text=self.app.t("shared_no_file_title"), bg=self.colors["card_soft"], fg=self.colors["text"], font=("Microsoft YaHei UI", 18, "bold")).pack(pady=(100, 8))
        tk.Label(empty, text=self.app.t("shared_no_file_hint"), bg=self.colors["card_soft"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 10), wraplength=520, justify=tk.CENTER).pack()
        action = tk.Frame(empty, bg=self.colors["card_soft"])
        action.pack(pady=18)
        create_button(action, self.app.t("shared_new_doc"), self.new_doc, self.colors, filled=True).pack(side=tk.LEFT, padx=(0, 10))
        create_button(action, self.app.t("shared_new_sheet"), self.new_sheet, self.colors).pack(side=tk.LEFT)

    def _ensure_unique_title(self, title, exclude_id=None):
        base = (title or self.app.t("shared_untitled_doc")).strip()
        existing = {str(d.get("title", "")).strip().lower() for did, d in self._visible_docs().items() if did != exclude_id and isinstance(d, dict)}
        if base.lower() not in existing:
            return base
        idx = 2
        while f"{base} ({idx})".lower() in existing:
            idx += 1
        return f"{base} ({idx})"

    def _make_unique_doc_id(self):
        doc_id = make_doc_id()
        while doc_id in self.app.documents:
            time.sleep(0.001)
            doc_id = make_doc_id()
        return doc_id

    def _ensure_document_shapes(self):
        changed = False
        for doc_id, doc in list(self.app.documents.items()):
            if not isinstance(doc, dict):
                continue
            if "type" not in doc:
                doc["type"] = "doc"
                changed = True
            if "shared" not in doc:
                doc["shared"] = True
                changed = True
            if "owner" not in doc:
                doc["owner"] = self.app.user_settings.get("username") or "Local"
                changed = True
            if "scope" not in doc:
                # Old files are treated as enterprise files to keep existing company data together.
                doc["scope"] = "enterprise"
                changed = True
            if "favorite" not in doc:
                doc["favorite"] = False
                changed = True
            if "archived" not in doc:
                doc["archived"] = False
                changed = True
            if "created" not in doc:
                doc["created"] = doc.get("updated") or now_text()
                changed = True
            if "last_opened" not in doc:
                doc["last_opened"] = ""
                changed = True
            if "folder" not in doc:
                doc["folder"] = ""
                changed = True
        if changed:
            save_documents(self.app.documents)

    def _scope(self):
        # v2026.06.20.80: Files follows the active product edition exactly.
        # No internal Personal/Enterprise switch is allowed to reveal the other scope.
        return self.app.current_data_scope() if hasattr(self.app, "current_data_scope") else self.app.get_app_mode()

    def _scope_label(self):
        return self.app.t("mode_personal") if self._scope() == "personal" else self.app.t("mode_enterprise")

    def _switch_scope(self, mode):
        current = self._scope()
        if mode != current:
            try:
                self.app.set_status("personal_isolation_guard_title")
            except Exception:
                pass
            return
        self.local_scope = current
        self.current_file_id = None
        self.refresh()

    def _update_scope_switch(self):
        active_scope = self._scope()
        try:
            if hasattr(self, "scope_switch"):
                self.scope_switch.pack_forget()
        except Exception:
            pass
        pairs = (("personal", getattr(self, "personal_scope_btn", None)), ("enterprise", getattr(self, "enterprise_scope_btn", None)))
        for key, btn in pairs:
            if not btn:
                continue
            active = key == active_scope
            btn.config(
                bg=self.colors["primary"] if active else self.colors["card_soft"],
                fg="white" if active else self.colors["text_secondary"],
                activebackground=self.colors["primary" if active else "primary_light"],
                activeforeground="white" if active else self.colors["primary"],
            )

    def _is_visible_doc(self, doc):
        if not isinstance(doc, dict):
            return False
        scope = doc.get("scope") or "enterprise"
        return scope == self._scope()

    def _visible_docs(self):
        return {doc_id: doc for doc_id, doc in self.app.documents.items() if self._is_visible_doc(doc)}

    def _active_docs(self):
        return {doc_id: doc for doc_id, doc in self._visible_docs().items() if not bool(doc.get("archived"))}

    def _folder_display(self, folder):
        folder = str(folder or "").strip()
        return folder or self.app.t("shared_folder_general")

    def _known_folders(self):
        folders = []
        seen = set()
        for name in self.app.user_settings.get("file_folders", []) if isinstance(self.app.user_settings.get("file_folders", []), list) else []:
            clean = str(name or "").strip()
            if clean and clean.lower() not in seen:
                folders.append(clean)
                seen.add(clean.lower())
        for doc in self._visible_docs().values():
            clean = str(doc.get("folder") or "").strip() if isinstance(doc, dict) else ""
            if clean and clean.lower() not in seen:
                folders.append(clean)
                seen.add(clean.lower())
        return sorted(folders, key=lambda x: x.lower())

    def _update_folder_menu(self):
        if not hasattr(self, "folder_menu"):
            return
        all_label = self.app.t("shared_all_folders")
        general_label = self.app.t("shared_folder_general")
        label_map = {all_label: "__all__", general_label: ""}
        menu = self.folder_menu["menu"]
        menu.delete(0, tk.END)

        def add_option(label, value):
            label_map[label] = value
            menu.add_command(label=label, command=lambda v=value: self.set_folder(v))

        add_option(all_label, "__all__")
        add_option(general_label, "")
        for folder in self._known_folders():
            add_option(self._folder_display(folder), folder)
        self._folder_label_map = label_map
        if self.current_folder == "__all__":
            self.folder_var.set(all_label)
        else:
            self.folder_var.set(self._folder_display(self.current_folder))

    def set_folder(self, folder):
        self.current_folder = "__all__" if folder == "__all__" else str(folder or "").strip()
        self._update_folder_menu()
        self.refresh_file_list()

    def new_folder_dialog(self):
        name = prompt_text_dialog(
            self.app.root,
            self.app.t("shared_new_folder"),
            self.app.t("shared_new_folder_hint"),
            self.colors,
            confirm_text=self.app.t("ok_btn") if "ok_btn" in self.app.lang else "OK",
            cancel_text=self.app.t("schedule_cancel"),
        )
        name = str(name or "").strip()
        if not name:
            return
        folders = self.app.user_settings.setdefault("file_folders", [])
        if not isinstance(folders, list):
            folders = []
            self.app.user_settings["file_folders"] = folders
        if name.lower() not in {str(x).strip().lower() for x in folders if str(x).strip()}:
            folders.append(name)
            try:
                save_settings(self.app.user_settings)
            except Exception:
                pass
        self.set_folder(name)

    def _normalize_folder_input(self, value):
        folder = str(value or "").strip()
        if not folder:
            return None
        if folder.lower() in {"general", self.app.t("shared_folder_general").lower()}:
            return ""
        return folder

    def move_current_to_folder(self):
        if not self.current_file_id:
            return
        self.move_file_to_folder_by_id(self.current_file_id)

    def move_file_to_folder_by_id(self, doc_id):
        doc = self.app.documents.get(doc_id)
        if not isinstance(doc, dict):
            return
        current = self._folder_display(doc.get("folder"))
        name = prompt_text_dialog(
            self.app.root,
            self.app.t("shared_move_to_folder"),
            self.app.t("shared_move_folder_hint"),
            self.colors,
            initialvalue=current,
            confirm_text=self.app.t("ok_btn") if "ok_btn" in self.app.lang else "OK",
            cancel_text=self.app.t("schedule_cancel"),
        )
        folder = self._normalize_folder_input(name)
        if folder is None:
            return
        doc["folder"] = folder
        doc["updated"] = now_text()
        if folder:
            folders = self.app.user_settings.setdefault("file_folders", [])
            if isinstance(folders, list) and folder.lower() not in {str(x).strip().lower() for x in folders if str(x).strip()}:
                folders.append(folder)
                try:
                    save_settings(self.app.user_settings)
                except Exception:
                    pass
        save_documents(self.app.documents)
        self._update_folder_menu()
        self._apply_file_card_style(doc_id)
        self._update_file_footer(doc, status_key="shared_status_saved")
        if self.current_folder not in ("__all__", doc.get("folder", "")):
            self.refresh_file_list()

    def set_filter(self, file_type):
        self.current_filter = file_type
        for key, btn in self.filter_buttons.items():
            active = key == file_type
            btn.config(
                bg=self.colors["primary"] if active else self.colors["card_soft"],
                fg="white" if active else self.colors["text_secondary"],
            )
        self.refresh_file_list()

    def refresh(self):
        if hasattr(self, "scope_badge"):
            self.scope_badge.config(text=self._scope_label())
        self._update_scope_switch()
        self._ensure_document_shapes()
        self._update_folder_menu()
        visible_docs = self._active_docs()
        if not visible_docs:
            self._render_empty_editor()
        elif not self.current_file_id or self.current_file_id not in visible_docs:
            self.current_file_id = next(iter(visible_docs.keys()))
            self.open_file(self.current_file_id)
        self.refresh_file_list()

    def refresh_file_list(self):
        # v2026.06.20.21: large workspaces used to rebuild every file card
        # synchronously.  Chunk rendering keeps the UI responsive while the list
        # is being repainted.
        try:
            if self._file_list_render_job:
                self.frame.after_cancel(self._file_list_render_job)
        except Exception:
            pass
        self._file_list_render_job = None
        self._file_list_render_token += 1
        token = self._file_list_render_token

        for child in self.list_inner.winfo_children():
            child.destroy()
        self._file_cards = {}
        self._ensure_document_shapes()
        self._prune_file_search_cache()
        keyword = self.file_search_var.get().strip().lower() if hasattr(self, "file_search_var") else ""
        visible_all = list(self._visible_docs().items())
        counts = {"all": 0, "doc": 0, "sheet": 0, "favorite": 0, "recent": 0, "archive": 0}
        for _doc_id, _doc in visible_all:
            if isinstance(_doc, dict):
                if _doc.get("archived"):
                    counts["archive"] += 1
                    continue
                counts["all"] += 1
                counts[_doc.get("type", "doc")] = counts.get(_doc.get("type", "doc"), 0) + 1
                if _doc.get("favorite"):
                    counts["favorite"] += 1
                if _doc.get("last_opened"):
                    counts["recent"] += 1
        for key, label_key in [("all", "shared_filter_all"), ("doc", "shared_filter_docs"), ("sheet", "shared_filter_sheets"), ("favorite", "shared_filter_favorites"), ("recent", "shared_filter_recent"), ("archive", "shared_filter_archive")]:
            btn = getattr(self, "filter_buttons", {}).get(key)
            if btn:
                btn.config(text=f"{self.app.t(label_key)} {counts.get(key, 0)}")
        items = []
        for doc_id, doc in visible_all:
            if not isinstance(doc, dict):
                continue
            file_type = doc.get("type", "doc")
            title = self.app.display_doc_title(doc.get("title", ""))
            content = str(doc.get("content", ""))
            archived = bool(doc.get("archived"))
            if self.current_filter == "archive":
                if not archived:
                    continue
            elif archived:
                continue
            elif self.current_filter == "favorite":
                if not doc.get("favorite"):
                    continue
            elif self.current_filter == "recent":
                if not doc.get("last_opened"):
                    continue
            elif self.current_filter != "all" and file_type != self.current_filter:
                continue
            if self.current_folder != "__all__" and str(doc.get("folder") or "").strip() != self.current_folder:
                continue
            folder_text = self._folder_display(doc.get("folder"))
            if keyword and keyword not in self._cached_doc_search_text(doc_id, doc, title, folder_text):
                continue
            items.append((doc_id, doc))
        if self.current_filter == "recent" and self.sort_mode == "updated":
            items.sort(key=lambda item: item[1].get("last_opened", ""), reverse=True)
        elif self.sort_mode == "name":
            items.sort(key=lambda item: self.app.display_doc_title(item[1].get("title", "")).lower())
        elif self.sort_mode == "type":
            items.sort(key=lambda item: (item[1].get("type", "doc"), self.app.display_doc_title(item[1].get("title", "")).lower()))
        elif self.sort_mode == "folder":
            items.sort(key=lambda item: (self._folder_display(item[1].get("folder")).lower(), self.app.display_doc_title(item[1].get("title", "")).lower()))
        elif self.sort_mode == "opened":
            items.sort(key=lambda item: item[1].get("last_opened", ""), reverse=True)
        else:
            items.sort(key=lambda item: item[1].get("updated", ""), reverse=True)
        if self.current_filter != "favorite":
            items.sort(key=lambda item: 0 if item[1].get("favorite") else 1)

        if hasattr(self, "list_summary"):
            self.list_summary.config(text=self.app.t("shared_items_count").format(count=len(items), scope=self._scope_label()))
        if not items:
            show_empty(self.list_inner, self.app.empty_text("shared_empty"), self.colors)
            self._update_list_scroll()
            return
        self._file_list_items = items
        self._render_file_list_chunk(token, 0)

    def _render_file_list_chunk(self, token, start):
        if token != getattr(self, "_file_list_render_token", 0):
            return
        items = getattr(self, "_file_list_items", [])
        chunk = 28 if len(items) > 220 else (36 if len(items) > 80 else 60)
        end = min(start + chunk, len(items))
        for doc_id, doc in items[start:end]:
            self._file_card(doc_id, doc)
        self._update_list_scroll()
        if end < len(items):
            try:
                self._file_list_render_job = self.frame.after(12, lambda: self._render_file_list_chunk(token, end))
            except Exception:
                self._file_list_render_job = None
        else:
            self._file_list_render_job = None

    def _apply_file_card_style(self, doc_id):
        refs = getattr(self, "_file_cards", {}).get(doc_id)
        doc = self.app.documents.get(doc_id, {}) if hasattr(self.app, "documents") else {}
        if not refs:
            return
        active = doc_id == self.current_file_id
        bg = self.colors["primary_light"] if active else self.colors["card_soft"]
        fg = self.colors["primary"] if active else self.colors["text"]
        try:
            for widget in refs.get("bg_widgets", []):
                widget.configure(bg=bg)
            for widget in refs.get("fg_widgets", []):
                widget.configure(bg=bg, fg=fg)
            if refs.get("delete_btn"):
                refs["delete_btn"].configure(bg=bg)
            if refs.get("favorite_btn"):
                refs["favorite_btn"].configure(
                    text="★" if doc.get("favorite") else "☆",
                    bg=bg,
                    fg=self.colors["warning"] if doc.get("favorite") else self.colors["text_light"],
                )
        except Exception:
            pass

    def _refresh_file_card_states(self):
        for doc_id in list(getattr(self, "_file_cards", {}).keys()):
            self._apply_file_card_style(doc_id)

    def _update_favorite_button(self, doc=None):
        if not hasattr(self, "favorite_btn"):
            return
        if doc is None and self.current_file_id:
            doc = self.app.documents.get(self.current_file_id, {})
        fav = bool(isinstance(doc, dict) and doc.get("favorite"))
        try:
            self.favorite_btn.config(text=self.app.t("shared_unmark_favorite") if fav else self.app.t("shared_mark_favorite"))
        except Exception:
            pass

    def toggle_file_favorite_by_id(self, doc_id):
        doc = self.app.documents.get(doc_id)
        if not isinstance(doc, dict):
            return
        doc["favorite"] = not bool(doc.get("favorite"))
        doc["updated"] = now_text()
        save_documents(self.app.documents)
        self._apply_file_card_style(doc_id)
        if doc_id == self.current_file_id:
            self._update_favorite_button(doc)
            self._update_file_footer(doc, status_key="shared_status_saved")
        # If the current filter is Favorites, the item may need to disappear.
        if self.current_filter == "favorite" and not doc.get("favorite"):
            self.refresh_file_list()

    def toggle_current_favorite(self):
        if not self.current_file_id:
            return
        self.toggle_file_favorite_by_id(self.current_file_id)

    def _update_archive_button(self, doc=None):
        if not hasattr(self, "archive_btn"):
            return
        if doc is None and self.current_file_id:
            doc = self.app.documents.get(self.current_file_id, {})
        archived = bool(isinstance(doc, dict) and doc.get("archived"))
        try:
            self.archive_btn.config(text=self.app.t("shared_restore_file") if archived else self.app.t("shared_archive_file"))
        except Exception:
            pass

    def toggle_file_archive_by_id(self, doc_id):
        doc = self.app.documents.get(doc_id)
        if not isinstance(doc, dict):
            return
        doc["archived"] = not bool(doc.get("archived"))
        doc["updated"] = now_text()
        save_documents(self.app.documents)
        if doc_id == self.current_file_id:
            self._update_archive_button(doc)
            self._update_file_footer(doc, status_key="shared_status_saved")
            if doc.get("archived") and self.current_filter != "archive":
                remaining = list(self._active_docs().keys())
                self.current_file_id = remaining[0] if remaining else None
                if self.current_file_id:
                    self.open_file(self.current_file_id)
                else:
                    self._render_empty_editor()
        self.refresh_file_list()
        try:
            self.app.refresh_home()
        except Exception:
            pass

    def archive_current_file(self):
        if not self.current_file_id:
            return
        self.toggle_file_archive_by_id(self.current_file_id)

    def show_file_properties(self, doc_id=None):
        doc_id = doc_id or self.current_file_id
        doc = self.app.documents.get(doc_id or "")
        if not isinstance(doc, dict):
            return
        win = create_dialog_window(
            self.app.root,
            self.app.t("shared_file_properties"),
            self.colors,
            width=560,
            height=440,
            modal=False,
            resizable=True,
            min_width=500,
            min_height=360,
        )
        body = tk.Frame(win, bg=self.colors["card"])
        body.pack(fill=tk.BOTH, expand=True, padx=22, pady=18)
        tk.Label(body, text=self.app.display_doc_title(doc.get("title", "")), bg=self.colors["card"], fg=self.colors["text"], font=("Microsoft YaHei UI", 16, "bold"), wraplength=500, justify=tk.LEFT).pack(anchor="w", pady=(0, 12))
        info = [
            (self.app.t("shared_property_type"), self.app.t("shared_type_sheet") if doc.get("type") == "sheet" else self.app.t("shared_type_doc")),
            (self.app.t("shared_property_scope"), self.app.t("mode_personal") if doc.get("scope") == "personal" else self.app.t("mode_enterprise")),
            (self.app.t("shared_property_owner"), str(doc.get("owner") or "-")),
            (self.app.t("shared_property_folder"), self._folder_display(doc.get("folder"))),
            (self.app.t("shared_property_created"), str(doc.get("created") or "-")),
            (self.app.t("shared_property_updated"), str(doc.get("updated") or "-")),
            (self.app.t("shared_property_last_opened"), str(doc.get("last_opened") or "-")),
            (self.app.t("shared_property_size"), self._format_size(doc.get("file_size")) or "-"),
            (self.app.t("shared_property_status"), self.app.t("shared_status_archived") if doc.get("archived") else self.app.t("shared_status_active")),
        ]
        if doc.get("type") == "sheet":
            info.append((self.app.t("shared_property_sheet_size"), self._sheet_stats_text(doc)))
        else:
            info.append((self.app.t("shared_property_doc_stats"), self._doc_stats_text(str(doc.get("content", "")))))
        for label, value in info:
            row = tk.Frame(body, bg=self.colors["card"])
            row.pack(fill=tk.X, pady=4)
            tk.Label(row, text=label, bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 10, "bold"), width=16, anchor="w").pack(side=tk.LEFT)
            tk.Label(row, text=value, bg=self.colors["card"], fg=self.colors["text"], font=("Microsoft YaHei UI", 10), anchor="w", wraplength=340, justify=tk.LEFT).pack(side=tk.LEFT, fill=tk.X, expand=True)
        footer = tk.Frame(body, bg=self.colors["card"])
        footer.pack(fill=tk.X, pady=(16, 0))
        create_button(footer, self.app.t("ok_btn") if "ok_btn" in self.app.lang else "OK", win.destroy, self.colors, filled=True).pack(side=tk.RIGHT)

    def _file_card(self, doc_id, doc):
        active = doc_id == self.current_file_id
        bg = self.colors["primary_light"] if active else self.colors["card_soft"]
        fg = self.colors["primary"] if active else self.colors["text"]
        icon = "▦" if doc.get("type") == "sheet" else "▤"
        card = tk.Frame(self.list_inner, bg=bg, highlightthickness=1, highlightbackground=self.colors["border"], cursor="hand2")
        card.pack(fill=tk.X, padx=6, pady=5)
        card.bind("<Button-1>", lambda e, did=doc_id: self.open_file(did))
        card.bind("<Button-3>", lambda e, did=doc_id: self._show_file_menu(e, did))
        row = tk.Frame(card, bg=bg)
        row.pack(fill=tk.X, padx=10, pady=(10, 3))
        icon_box = tk.Frame(row, bg=self.colors["card"], width=34, height=34, highlightthickness=1, highlightbackground=self.colors["border"])
        icon_box.pack(side=tk.LEFT)
        icon_box.pack_propagate(False)
        icon_label = tk.Label(icon_box, text=icon, bg=self.colors["card"], fg=fg, font=("Microsoft YaHei UI", 13, "bold"))
        icon_label.pack(expand=True, fill=tk.BOTH)
        title_label = tk.Label(row, text=self.app.display_doc_title(doc.get("title")), bg=bg, fg=fg, font=("Microsoft YaHei UI", 10, "bold"), anchor="w")
        title_label.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(8, 0))
        fav_btn = tk.Button(row, text="★" if doc.get("favorite") else "☆", command=lambda did=doc_id: self.toggle_file_favorite_by_id(did), bg=bg, fg=self.colors["warning"] if doc.get("favorite") else self.colors["text_light"], relief=tk.FLAT, bd=0, cursor="hand2", font=("Microsoft YaHei UI", 11, "bold"))
        fav_btn.pack(side=tk.RIGHT, padx=(4, 0))
        delete_btn = tk.Button(row, text="×", command=lambda did=doc_id: self.delete_file(did), bg=bg, fg=self.colors["danger"], relief=tk.FLAT, bd=0, cursor="hand2", font=("Microsoft YaHei UI", 10, "bold"))
        delete_btn.pack(side=tk.RIGHT)
        size_text = self._format_size(doc.get("file_size"))
        meta_parts = [self._scope_label(), self.app.t('shared_type_sheet') if doc.get('type') == 'sheet' else self.app.t('shared_type_doc'), f"{self.app.t('shared_folder_label')} {self._folder_display(doc.get('folder'))}"]
        if doc.get("favorite"):
            meta_parts.append(self.app.t("shared_favorite_badge"))
        if doc.get("archived"):
            meta_parts.append(self.app.t("shared_archived_badge"))
        if size_text:
            meta_parts.append(size_text)
        if doc.get("last_opened") and self.current_filter == "recent":
            meta_parts.append(self.app.t("shared_last_opened_badge") + " " + doc.get("last_opened", ""))
        elif doc.get("updated"):
            meta_parts.append(doc.get("updated", ""))
        meta = " · ".join(meta_parts)
        meta_label = tk.Label(card, text=meta, bg=bg, fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 8), anchor="w")
        meta_label.pack(fill=tk.X, padx=12, pady=(0, 10))
        self._file_cards[doc_id] = {
            "bg_widgets": [card, row],
            "fg_widgets": [title_label, meta_label],
            "delete_btn": delete_btn,
            "favorite_btn": fav_btn,
        }
        for child in card.winfo_children():
            child.bind("<Button-3>", lambda e, did=doc_id: self._show_file_menu(e, did))
            child.bind("<Double-Button-1>", lambda e, did=doc_id: self.open_file(did))

    def batch_create_dialog(self):
        dlg = tk.Toplevel(self.app.root)
        dlg.title(self.app.t("shared_batch_create"))
        dlg.configure(bg=self.colors["card"])
        dlg.transient(self.app.root)
        try:
            center_window(dlg, self.app.root, 560, 520, min_width=480, min_height=420)
        except Exception:
            dlg.geometry("560x520")

        head = tk.Frame(dlg, bg=self.colors["card"])
        head.pack(fill=tk.X, padx=22, pady=(18, 10))
        tk.Label(head, text=self.app.t("shared_batch_create"), bg=self.colors["card"], fg=self.colors["text"], font=("Microsoft YaHei UI", 18, "bold")).pack(side=tk.LEFT)

        body = tk.Frame(dlg, bg=self.colors["card"])
        body.pack(fill=tk.BOTH, expand=True, padx=22, pady=(0, 18))
        tk.Label(body, text=self.app.t("shared_batch_hint"), bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 10), wraplength=500, justify=tk.LEFT).pack(anchor="w", pady=(0, 8))

        type_row = tk.Frame(body, bg=self.colors["card"])
        type_row.pack(fill=tk.X, pady=(0, 10))
        file_type = tk.StringVar(value="doc")
        tk.Radiobutton(type_row, text=self.app.t("shared_type_doc"), variable=file_type, value="doc", bg=self.colors["card"], fg=self.colors["text"], activebackground=self.colors["card"], font=("Microsoft YaHei UI", 10, "bold")).pack(side=tk.LEFT, padx=(0, 16))
        tk.Radiobutton(type_row, text=self.app.t("shared_type_sheet"), variable=file_type, value="sheet", bg=self.colors["card"], fg=self.colors["text"], activebackground=self.colors["card"], font=("Microsoft YaHei UI", 10, "bold")).pack(side=tk.LEFT)

        text_box = tk.Text(body, height=14, wrap=tk.WORD, relief=tk.FLAT, bd=0, bg=self.colors["card_soft"], fg=self.colors["text"], insertbackground=self.colors["primary"], font=("Microsoft YaHei UI", 11), padx=12, pady=12)
        text_box.pack(fill=tk.BOTH, expand=True)
        text_box.insert("1.0", self.app.t("shared_batch_placeholder"))

        def focus_in(_=None):
            if text_box.get("1.0", tk.END).strip() == self.app.t("shared_batch_placeholder"):
                text_box.delete("1.0", tk.END)

        text_box.bind("<FocusIn>", focus_in)

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

        def create_batch():
            raw = text_box.get("1.0", tk.END).strip()
            if not raw or raw == self.app.t("shared_batch_placeholder"):
                return
            titles = []
            for line in raw.splitlines():
                title = line.strip()
                if title:
                    titles.append(title)
            if not titles:
                return
            first_id = None
            folder = "" if self.current_folder == "__all__" else self.current_folder
            for title in titles:
                doc_id = self._make_unique_doc_id()
                title = self._ensure_unique_title(title)
                base = {
                    "owner": self.app.user_settings.get("username") or "Local",
                    "scope": self._scope(),
                    "shared": True,
                    "folder": folder,
                    "archived": False,
                    "created": now_text(),
                    "updated": now_text(),
                }
                if file_type.get() == "sheet":
                    self.app.documents[doc_id] = {
                        **base,
                        "type": "sheet",
                        "title": title,
                        "sheet": [["" for _ in range(8)] for _ in range(12)],
                    }
                else:
                    self.app.documents[doc_id] = {
                        **base,
                        "type": "doc",
                        "title": title,
                        "content": "",
                    }
                first_id = first_id or doc_id
            save_documents(self.app.documents)
            self.current_file_id = first_id
            dlg.destroy()
            if first_id:
                self.open_file(first_id)
            self.refresh_file_list()
            self.app.set_status("shared_batch_done")

        create_button(footer, self.app.t("schedule_cancel"), dlg.destroy, self.colors).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
        create_button(footer, self.app.t("shared_batch_confirm"), create_batch, self.colors, filled=True).pack(side=tk.LEFT, fill=tk.X, expand=True)
        text_box.bind("<Control-Return>", lambda _e: [create_batch(), "break"][-1])
        dlg.bind("<Control-Return>", lambda _e: [create_batch(), "break"][-1])
        text_box.focus_set()

    def new_doc(self):
        doc_id = self._make_unique_doc_id()
        self.app.documents[doc_id] = {
            "type": "doc",
            "title": self._ensure_unique_title(self.app.t("shared_untitled_doc")),
            "content": "",
            "owner": self.app.user_settings.get("username") or "Local",
            "scope": self._scope(),
            "shared": True,
            "folder": "" if self.current_folder == "__all__" else self.current_folder,
            "archived": False,
            "created": now_text(),
            "updated": now_text(),
        }
        save_documents(self.app.documents)
        self.open_file(doc_id)
        self.app.set_status("status_saved")

    def new_sheet(self):
        doc_id = self._make_unique_doc_id()
        self.app.documents[doc_id] = {
            "type": "sheet",
            "title": self._ensure_unique_title(self.app.t("shared_untitled_sheet")),
            "sheet": [["" for _ in range(8)] for _ in range(12)],
            "content": "",
            "owner": self.app.user_settings.get("username") or "Local",
            "scope": self._scope(),
            "shared": True,
            "folder": "" if self.current_folder == "__all__" else self.current_folder,
            "archived": False,
            "created": now_text(),
            "updated": now_text(),
        }
        save_documents(self.app.documents)
        self.open_file(doc_id)
        self.app.set_status("status_saved")

    def _import_file_as_document(self, path):
        p = Path(path)
        doc_id = self._make_unique_doc_id()
        suffix = p.suffix.lower()
        size = p.stat().st_size if p.exists() else 0
        if suffix in (".csv", ".tsv"):
            rows = []
            try:
                with open(path, "r", encoding="utf-8-sig", newline="") as f:
                    reader = csv.reader(f, delimiter="\t" if suffix == ".tsv" else ",")
                    for idx, row in enumerate(reader):
                        if idx >= 300:
                            break
                        rows.append(row[:30])
            except Exception:
                rows = []
            rows = rows or [["" for _ in range(8)] for _ in range(12)]
            max_cols = max([len(r) for r in rows] + [8])
            rows = [r + ["" for _ in range(max(0, max_cols - len(r)))] for r in rows]
            self.app.documents[doc_id] = {
                "type": "sheet",
                "title": self._ensure_unique_title(p.stem),
                "sheet": rows,
                "content": "",
                "owner": self.app.user_settings.get("username") or "Local",
                "scope": self._scope(),
                "shared": True,
                "attachment": str(p),
                "updated": now_text(),
                "file_size": size,
            }
        else:
            content = self.app.t("shared_binary_hint")
            if suffix in (".txt", ".md"):
                raw = b""
                try:
                    with open(path, "rb") as f:
                        raw = f.read(300 * 1024)
                    content = raw.decode("utf-8", errors="replace")
                    if size > 300 * 1024:
                        content += "\n\n" + self.app.t("shared_preview_limited")
                except Exception:
                    content = self.app.t("shared_binary_hint")
            self.app.documents[doc_id] = {
                "type": "doc",
                "title": self._ensure_unique_title(p.stem),
                "content": content,
                "owner": self.app.user_settings.get("username") or "Local",
                "scope": self._scope(),
                "shared": True,
                "attachment": str(p),
                "updated": now_text(),
                "file_size": size,
            }
        return doc_id

    def upload_file(self):
        paths = filedialog.askopenfilenames(
            title=self.app.t("shared_upload"),
            filetypes=[
                ("Office files", "*.txt *.md *.csv *.tsv *.doc *.docx *.xls *.xlsx"),
                ("All files", "*.*"),
            ],
        )
        if not paths:
            return
        self.app.root.config(cursor="watch")
        self.app.root.update_idletasks()
        first_id = None
        try:
            for path in paths:
                try:
                    new_id = self._import_file_as_document(path)
                    first_id = first_id or new_id
                except Exception:
                    continue
            if not first_id:
                messagebox.showwarning(self.app.t("warning_title"), self.app.t("shared_upload_failed"))
                return
            save_documents(self.app.documents)
            self.open_file(first_id)
            self.refresh_file_list()
            self.app.set_status("status_saved")
        finally:
            self.app.root.config(cursor="")

    def duplicate_current_file(self):
        if not self.current_file_id or self.current_file_id not in self.app.documents:
            return
        self.save_current(silent=True)
        source = self.app.documents.get(self.current_file_id)
        if not isinstance(source, dict):
            return
        new_doc = copy.deepcopy(source)
        new_id = self._make_unique_doc_id()
        new_doc["title"] = self._ensure_unique_title(f"{self.app.display_doc_title(source.get('title'))} - {self.app.t('shared_copy_suffix')}")
        new_doc["updated"] = now_text()
        new_doc["created"] = now_text()
        new_doc["favorite"] = False
        new_doc["archived"] = False
        new_doc["last_opened"] = ""
        new_doc["history"] = []
        new_doc["owner"] = self.app.user_settings.get("username") or new_doc.get("owner") or "Local"
        new_doc["scope"] = self._scope()
        self.app.documents[new_id] = new_doc
        save_documents(self.app.documents)
        self.open_file(new_id)
        self.app.set_status("status_saved")

    def _center_popup(self, win, w=1100, h=760):
        center_window(win, self.app.root, w, h, min_width=720, min_height=520)

    def open_full_editor(self):
        if not self.current_file_id or self.current_file_id not in self.app.documents:
            return
        self.save_current(silent=True)
        doc = self.app.documents[self.current_file_id]
        win = tk.Toplevel(self.app.root)
        win.title(f"{self.app.t('shared_open_editor')} - {self.app.display_doc_title(doc.get('title'))}")
        win.configure(bg=self.colors["bg"])
        win.transient(self.app.root)
        self._center_popup(win, 1240, 820)

        header = tk.Frame(win, bg=self.colors["card"], highlightthickness=1, highlightbackground=self.colors["border"])
        header.pack(fill=tk.X, padx=18, pady=(18, 10))
        title_var = tk.StringVar(value=self.app.display_doc_title(doc.get("title")))
        badge = tk.Label(header, text="SHEET" if doc.get("type") == "sheet" else "DOC", font=("Microsoft YaHei UI", 9, "bold"), bg=self.colors["primary_light"], fg=self.colors["primary"], padx=10, pady=5)
        badge.pack(side=tk.LEFT, padx=(14, 10), pady=12)
        title_entry = tk.Entry(header, textvariable=title_var, relief=tk.FLAT, bd=0, bg=self.colors["card"], fg=self.colors["text"], font=("Microsoft YaHei UI", 16, "bold"))
        title_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=4)

        body = tk.Frame(win, bg=self.colors["bg"])
        body.pack(fill=tk.BOTH, expand=True, padx=18, pady=(0, 18))

        local_cells = []
        local_sheet_raw = []
        active_popup_cell = {"cell": None}
        text_editor = None

        def save_and_close(close=False):
            doc["title"] = title_var.get().strip() or self.app.t("shared_untitled_doc")
            if doc.get("type") == "sheet":
                cell = active_popup_cell.get("cell")
                if cell:
                    r, c, var = cell
                    try:
                        local_sheet_raw[r][c] = var.get()
                    except Exception:
                        pass
                doc["sheet"] = local_sheet_raw
                # Reuse the same formula engine by temporarily pointing to popup sheet data.
                old_raw = getattr(self, "sheet_raw", None)
                self.sheet_raw = local_sheet_raw
                doc["content"] = " ".join(" ".join(str(self._display_sheet_value(r, c)) for c in range(len(row))) for r, row in enumerate(local_sheet_raw))
                if old_raw is not None:
                    self.sheet_raw = old_raw
            else:
                doc["content"] = text_editor.get("1.0", tk.END).strip()
            doc["updated"] = now_text()
            doc["shared"] = True
            self._notify_mentions(doc)
            save_documents(self.app.documents)
            self.open_file(self.current_file_id)
            self.app.refresh_home()
            self.app.set_status("status_saved")
            if close:
                win.destroy()

        save_btn = create_button(header, self.app.t("shared_save"), lambda: save_and_close(False), self.colors, filled=True)
        save_btn.pack(side=tk.RIGHT, padx=(8, 14))
        close_btn = create_button(header, self.app.t("shared_save_close"), lambda: save_and_close(True), self.colors)
        close_btn.pack(side=tk.RIGHT)

        if doc.get("type") == "sheet":
            sheet_host = tk.Frame(body, bg=self.colors["card"])
            sheet_host.pack(fill=tk.BOTH, expand=True)
            canvas = tk.Canvas(sheet_host, bg=self.colors["card"], highlightthickness=1, highlightbackground=self.colors["border"])
            sy = tk.Scrollbar(sheet_host, orient="vertical", command=canvas.yview)
            sx = tk.Scrollbar(sheet_host, orient="horizontal", command=canvas.xview)
            canvas.configure(yscrollcommand=sy.set, xscrollcommand=sx.set)
            self._bind_sheet_scroll(canvas)
            canvas.grid(row=0, column=0, sticky="nsew")
            sy.grid(row=0, column=1, sticky="ns")
            sx.grid(row=1, column=0, sticky="ew")
            sheet_host.grid_rowconfigure(0, weight=1)
            sheet_host.grid_columnconfigure(0, weight=1)
            inner = tk.Frame(canvas, bg=self.colors["card"])
            win_id = canvas.create_window((0, 0), window=inner, anchor="nw")
            inner.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
            canvas.bind("<Configure>", lambda e: canvas.itemconfig(win_id, width=max(e.width - 4, inner.winfo_reqwidth())))

            data = doc.get("sheet") or [["" for _ in range(10)] for _ in range(20)]
            rows = max(len(data), 20)
            cols = max([len(r) for r in data] + [10])
            for r in range(rows):
                src = data[r] if r < len(data) else []
                local_sheet_raw.append([str(src[c]) if c < len(src) else "" for c in range(cols)])

            def display(rr, cc):
                old_raw = getattr(self, "sheet_raw", None)
                self.sheet_raw = local_sheet_raw
                try:
                    return self._display_sheet_value(rr, cc)
                finally:
                    if old_raw is not None:
                        self.sheet_raw = old_raw

            def refresh_popup():
                for rr, row_vars in enumerate(local_cells):
                    for cc, vv in enumerate(row_vars):
                        cell = active_popup_cell.get("cell")
                        if cell and cell[0] == rr and cell[1] == cc:
                            continue
                        vv.set(display(rr, cc))

            tk.Label(inner, text="", bg=self.colors["primary_light"], width=5, height=2).grid(row=0, column=0, sticky="nsew", padx=1, pady=1)
            for c in range(cols):
                tk.Label(inner, text=self._excel_col(c), bg=self.colors["primary_light"], fg=self.colors["primary"], font=("Microsoft YaHei UI", 9, "bold"), width=16, pady=7).grid(row=0, column=c + 1, sticky="nsew", padx=1, pady=1)

            for r in range(rows):
                row_cells = []
                tk.Label(inner, text=str(r + 1), bg=self.colors["primary_light"], fg=self.colors["primary"], font=("Microsoft YaHei UI", 9, "bold"), width=5).grid(row=r + 1, column=0, sticky="nsew", padx=1, pady=1)
                for c in range(cols):
                    var = tk.StringVar(value=display(r, c))
                    entry = tk.Entry(inner, textvariable=var, bg=self.colors["card_soft"], fg=self.colors["text"], relief=tk.FLAT, bd=0, width=16, font=("Microsoft YaHei UI", 10))
                    entry.grid(row=r + 1, column=c + 1, sticky="nsew", padx=1, pady=1, ipady=7)

                    def focus_in(event, rr=r, cc=c, vv=var):
                        active_popup_cell["cell"] = (rr, cc, vv)
                        vv.set(local_sheet_raw[rr][cc])

                    def commit(event=None, rr=r, cc=c, vv=var):
                        local_sheet_raw[rr][cc] = vv.get()
                        active_popup_cell["cell"] = None
                        refresh_popup()
                        return "break" if event and getattr(event, "keysym", "") == "Return" else None

                    entry.bind("<FocusIn>", focus_in)
                    entry.bind("<FocusOut>", commit)
                    entry.bind("<Return>", commit)
                    row_cells.append(var)
                local_cells.append(row_cells)
        else:
            tool = tk.Frame(body, bg=self.colors["bg"])
            tool.pack(fill=tk.X, pady=(0, 10))
            tk.Label(tool, text=self.app.t("shared_large_editor_hint"), bg=self.colors["bg"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9)).pack(side=tk.LEFT)
            text_editor = tk.Text(body, wrap=tk.WORD, relief=tk.FLAT, bd=0, bg=self.colors["card"], fg=self.colors["text"], insertbackground=self.colors["primary"], font=("Microsoft YaHei UI", 13), padx=22, pady=22)
            text_editor.pack(fill=tk.BOTH, expand=True)
            text_editor.insert("1.0", str(doc.get("content", "")))

        title_entry.bind("<Return>", lambda e: save_and_close(False))
        win.protocol("WM_DELETE_WINDOW", lambda: save_and_close(True))

    def open_doc(self, doc_id):
        self.open_file(doc_id)

    def _schedule_open_metadata_save(self):
        try:
            if self._open_meta_save_job:
                self.frame.after_cancel(self._open_meta_save_job)
        except Exception:
            pass
        self._open_meta_save_job = self.frame.after(750, self._save_open_metadata)

    def _save_open_metadata(self):
        self._open_meta_save_job = None
        try:
            save_documents(self.app.documents)
        except Exception:
            pass

    def _mark_doc_opened(self, doc):
        if not isinstance(doc, dict):
            return
        opened = now_text()
        if doc.get("last_opened") != opened:
            doc["last_opened"] = opened
            self._schedule_open_metadata_save()

    def open_file(self, doc_id):
        if doc_id not in self.app.documents:
            return
        self.save_current(silent=True)
        self.current_file_id = doc_id
        doc = self.app.documents[doc_id]
        self._mark_doc_opened(doc)
        self.title_var.set(self.app.display_doc_title(doc.get("title", "")))
        if doc.get("type") == "sheet":
            self.type_badge_box.config(bg=self.colors["warning_light"])
            self.type_badge.config(text="SHEET", bg=self.colors["warning_light"], fg=self.colors["warning"])
            self._render_sheet(doc)
        else:
            self.type_badge_box.config(bg=self.colors["primary_light"])
            self.type_badge.config(text="DOC", bg=self.colors["primary_light"], fg=self.colors["primary"])
            self._render_doc(doc)
        self.share_state.config(text=self.app.t("shared_status_saved"), bg=self.colors["success_light"], fg=self.colors["success"])
        self._update_file_footer(doc, status_key="shared_status_saved")
        self._update_favorite_button(doc)
        self._update_archive_button(doc)
        self._refresh_file_card_states()

    def _clear_tools(self):
        for child in self.tool_area.winfo_children():
            child.destroy()
        try:
            if hasattr(self, "tool_canvas"):
                self.tool_canvas.xview_moveto(0)
                self.tool_canvas.after_idle(self._update_tool_scroll)
        except Exception:
            pass
        for child in self.editor_host.winfo_children():
            child.destroy()

    def _render_doc(self, doc):
        self._clear_tools()
        self.doc_color = self.colors["primary"]
        self.font_size_var = tk.StringVar(value="12")

        # v2026.06.20.81: keep the toolbar stable but polish the alignment
        # actions so placeholder keys never leak into the UI and the editor
        # remains clearer on smaller screens.
        toolbar_items = [
            {"label": self.app.t("save_now_btn") if hasattr(self.app, "t") else "Save", "command": lambda: self.save_current(silent=False)},
            {"label": "B", "command": self.toggle_bold},
            {"label": "I", "command": self.toggle_italic},
            {"label": "U", "command": self.toggle_underline},
            {"label": self.app.t("heading_btn"), "command": self.apply_heading},
            {"label": self.app.t("normal_btn"), "command": self.apply_normal},
            {"label": "≡←", "command": self.align_left, "tooltip": self.app.t("align_left_btn"), "compact": True},
            {"label": "≡↔", "command": self.align_center, "tooltip": self.app.t("align_center_btn"), "compact": True},
            {"label": "≡→", "command": self.align_right, "tooltip": self.app.t("align_right_btn"), "compact": True},
            {"label": self.app.t("shared_insert_table"), "command": self.insert_table_template},
            {"label": self.app.t("shared_find_text"), "command": self.find_in_doc},
            {"label": self.app.t("shared_replace_text"), "command": self.replace_in_doc},
            {"label": self.app.t("shared_copy_content"), "command": self.copy_doc_content},
            {"label": self.app.t("shared_insert_divider"), "command": self.insert_divider},
            {"label": self.app.t("shared_insert_meeting_template"), "command": self.insert_meeting_template},
            {"label": "•", "command": self.insert_bullet},
            {"label": "☑", "command": self.insert_checklist},
            {"label": "@", "command": self.insert_mention},
            {"label": "🕒", "command": self.insert_datetime},
            {"label": self.app.t("clear_format_btn"), "command": self.clear_format},
            {"label": "↶", "command": self.undo_action},
            {"label": "↷", "command": self.redo_action},
        ]
        for item in toolbar_items:
            btn = create_button(self.tool_area, item["label"], item["command"], self.colors)
            if item.get("compact"):
                try:
                    btn.configure(width=4, padx=8)
                except Exception:
                    pass
            btn.pack(side=tk.LEFT, padx=(0, 8))
            if item.get("tooltip"):
                ToolTip(btn, lambda text=item["tooltip"]: text)

        tk.Label(self.tool_area, text=self.app.t("font_size"), bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9, "bold")).pack(side=tk.LEFT, padx=(8, 6))
        self.size_spin = tk.Spinbox(self.tool_area, from_=10, to=36, width=4, textvariable=self.font_size_var, command=self.apply_size, relief=tk.FLAT, bg=self.colors["card_soft"], font=("Microsoft YaHei UI", 10))
        self.size_spin.pack(side=tk.LEFT, padx=(0, 8))

        self.color_btn = create_button(self.tool_area, self.app.t("color_btn"), self.apply_color, self.colors)
        self.color_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.brush_btn = create_button(self.tool_area, self.app.t("brush_btn"), self.toggle_eraser_off, self.colors)
        self.brush_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.eraser_btn = create_button(self.tool_area, self.app.t("eraser_btn"), self.toggle_eraser, self.colors)
        self.eraser_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.clear_draw_btn = create_button(self.tool_area, self.app.t("clear_canvas_btn"), self.clear_canvas, self.colors)
        self.clear_draw_btn.pack(side=tk.LEFT, padx=(0, 8))
        self.doc_stats_label = tk.Label(self.tool_area, text="", bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9, "bold"))
        self.doc_stats_label.pack(side=tk.RIGHT)

        self.doc_scroll_wrap = tk.Frame(self.editor_host, bg=self.colors["card"])
        self.doc_scroll_wrap.pack(fill=tk.BOTH, expand=True)
        self.doc_canvas = tk.Canvas(self.doc_scroll_wrap, bg=self.colors["card"], highlightthickness=0)
        self.doc_ybar = tk.Scrollbar(self.doc_scroll_wrap, orient="vertical", command=self.doc_canvas.yview)
        self.doc_canvas.configure(yscrollcommand=self.doc_ybar.set)
        self.doc_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.doc_ybar.pack(side=tk.RIGHT, fill=tk.Y)
        self.doc_scroll_inner = tk.Frame(self.doc_canvas, bg=self.colors["card"])
        self.doc_canvas_window = self.doc_canvas.create_window((0, 0), window=self.doc_scroll_inner, anchor="nw")
        self.doc_canvas.bind("<MouseWheel>", lambda e: self._mousewheel(self.doc_canvas, e))
        self.doc_scroll_inner.bind("<MouseWheel>", lambda e: self._mousewheel(self.doc_canvas, e))

        self.text_editor = tk.Text(
            self.doc_scroll_inner,
            wrap=tk.WORD,
            relief=tk.FLAT,
            bd=0,
            undo=True,
            autoseparators=True,
            maxundo=-1,
            bg=self.colors["card_soft"],
            fg=self.colors["text"],
            insertbackground=self.colors["primary"],
            font=("Microsoft YaHei UI", 12),
            padx=18,
            pady=18,
            height=18,
        )
        self.text_editor.pack(fill=tk.X, expand=False, padx=0, pady=(0, 12))
        self.text_editor.tag_configure("bold", font=("Microsoft YaHei UI", 12, "bold"))
        self.text_editor.tag_configure("italic", font=("Microsoft YaHei UI", 12, "italic"))
        self.text_editor.tag_configure("underline", font=("Microsoft YaHei UI", 12, "underline"))
        self.text_editor.tag_configure("heading", font=("Microsoft YaHei UI", 20, "bold"))
        self.text_editor.tag_configure("align_left", justify=tk.LEFT)
        self.text_editor.tag_configure("align_center", justify=tk.CENTER)
        self.text_editor.tag_configure("align_right", justify=tk.RIGHT)
        self.text_editor.insert("1.0", self.app.display_doc_content(doc.get("content", "")))
        self._restore_doc_formatting(doc)
        self.text_editor.bind("<KeyRelease>", lambda e: [self._mark_unsaved(), self._schedule_doc_autosave(), self._update_doc_stats()])
        self.text_editor.bind("<ButtonRelease-1>", lambda e: self._update_doc_stats())
        # Make editing feel natural: wheel over the editor scrolls the document
        # area, not just the text widget, so the drawing area and lower content
        # remain reachable on small screens.
        self.text_editor.bind("<MouseWheel>", lambda e: self._mousewheel(self.doc_canvas, e), add="+")
        self.text_editor.bind("<Control-b>", lambda e: [self.toggle_bold(), "break"][1])
        self.text_editor.bind("<Control-B>", lambda e: [self.toggle_bold(), "break"][1])
        self.text_editor.bind("<Control-i>", lambda e: [self.toggle_italic(), "break"][1])
        self.text_editor.bind("<Control-I>", lambda e: [self.toggle_italic(), "break"][1])
        self.text_editor.bind("<Control-u>", lambda e: [self.toggle_underline(), "break"][1])
        self.text_editor.bind("<Control-U>", lambda e: [self.toggle_underline(), "break"][1])
        self.text_editor.bind("<Control-f>", lambda e: [self.find_in_doc(), "break"][1])
        self.text_editor.bind("<Control-F>", lambda e: [self.find_in_doc(), "break"][1])

        draw_wrap = tk.Frame(self.doc_scroll_inner, bg=self.colors["card_soft"], highlightthickness=1, highlightbackground=self.colors["border"])
        draw_wrap.pack(fill=tk.X, pady=(0, 10))
        tk.Label(draw_wrap, text=self.app.t("draw_area_title"), font=("Microsoft YaHei UI", 11, "bold"), bg=self.colors["card_soft"], fg=self.colors["text"]).pack(anchor="w", padx=12, pady=(10, 4))
        self.draw_canvas = tk.Canvas(draw_wrap, bg="white", height=230, highlightthickness=0, cursor="pencil")
        self.draw_canvas.pack(fill=tk.X, padx=12, pady=(0, 12))
        self.draw_canvas.bind("<ButtonPress-1>", self._draw_start)
        self.draw_canvas.bind("<B1-Motion>", self._draw_move)
        self.draw_canvas.bind("<ButtonRelease-1>", self._draw_end)

        self.doc_scroll_inner.bind("<Configure>", lambda e: self.doc_canvas.configure(scrollregion=self.doc_canvas.bbox("all")))
        self.doc_canvas.bind("<Configure>", lambda e: self.doc_canvas.itemconfig(self.doc_canvas_window, width=max(e.width - 4, 500)))
        self._update_doc_stats()

    def _xwheel(self, canvas, event):
        delta = getattr(event, "delta", 0)
        if delta == 0:
            return
        step = -1 * int(delta / 120) if abs(delta) >= 120 else (-1 if delta > 0 else 1)
        canvas.xview_scroll(step, "units")
        return "break"

    def _bind_sheet_scroll(self, canvas):
        canvas.bind("<Shift-MouseWheel>", lambda e, c=canvas: self._xwheel(c, e))
        # Button-6 / Button-7 are not safe on some Windows Tcl/Tk builds.
        # Keep horizontal scrolling through the visible scrollbar and Shift + wheel.

    def _render_sheet(self, doc):
        """Stable spreadsheet renderer.

        The previous table could crash on some Windows/Tk builds because of
        platform-specific horizontal wheel bindings and an over-aggressive
        canvas width resize. This version keeps the table simple and stable:
        - normal Canvas + visible X/Y scrollbars
        - formulas are displayed automatically after Enter / focus-out
        - raw formulas are shown again on focus
        - Ctrl+V supports Excel/WPS multi-cell paste
        """
        self._clear_tools()
        create_button(self.tool_area, self.app.t("save_now_btn"), lambda: self.save_current(silent=False), self.colors, filled=True).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_add_row"), self.add_sheet_row, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_add_col"), self.add_sheet_col, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_delete_row"), self.delete_sheet_row, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_delete_col"), self.delete_sheet_col, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_duplicate_row"), self.duplicate_sheet_row, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_duplicate_col"), self.duplicate_sheet_col, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_fill_down"), self.fill_down, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_fill_right"), self.fill_right, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_sort_col"), self.sort_active_column, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_insert_sum"), self.insert_formula_sum, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_insert_avg"), self.insert_formula_average, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_insert_max"), self.insert_formula_max, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_insert_min"), self.insert_formula_min, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_insert_count"), self.insert_formula_count, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_sheet_help"), self.show_sheet_help, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        create_button(self.tool_area, self.app.t("shared_clear_sheet"), self.clear_sheet, self.colors).pack(side=tk.LEFT, padx=(0, 8))
        tk.Label(
            self.tool_area,
            text=self.app.t("shared_formula_hint"),
            bg=self.colors["card"],
            fg=self.colors["text_secondary"],
            font=("Microsoft YaHei UI", 9),
        ).pack(side=tk.LEFT, padx=(8, 0))
        self.sheet_active_label = tk.Label(self.tool_area, text=self.app.t("shared_active_cell").format(cell="A1"), bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9, "bold"))
        self.sheet_active_label.pack(side=tk.RIGHT)

        formula_row = tk.Frame(self.editor_host, bg=self.colors["card"], highlightthickness=1, highlightbackground=self.colors["border"])
        formula_row.pack(fill=tk.X, pady=(0, 8))
        tk.Label(formula_row, text=self.app.t("shared_formula_bar"), bg=self.colors["card"], fg=self.colors["primary"], font=("Microsoft YaHei UI", 9, "bold"), padx=10).pack(side=tk.LEFT)
        self.sheet_formula_var = tk.StringVar()
        self.sheet_formula_entry = tk.Entry(formula_row, textvariable=self.sheet_formula_var, relief=tk.FLAT, bd=0, bg=self.colors["card_soft"], fg=self.colors["text"], insertbackground=self.colors["primary"], font=("Microsoft YaHei UI", 10))
        self.sheet_formula_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 8), pady=7, ipady=5)
        self.sheet_formula_entry.bind("<Return>", self.apply_formula_bar)
        create_button(formula_row, self.app.t("shared_apply_formula"), self.apply_formula_bar, self.colors, filled=True).pack(side=tk.LEFT, padx=(0, 8), pady=6)

        container = tk.Frame(self.editor_host, bg=self.colors["card"])
        container.pack(fill=tk.BOTH, expand=True)

        canvas = tk.Canvas(container, bg=self.colors["card"], highlightthickness=1, highlightbackground=self.colors["border"])
        ybar = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
        xbar = tk.Scrollbar(container, orient="horizontal", command=canvas.xview)
        canvas.configure(yscrollcommand=ybar.set, xscrollcommand=xbar.set)

        canvas.grid(row=0, column=0, sticky="nsew")
        ybar.grid(row=0, column=1, sticky="ns")
        xbar.grid(row=1, column=0, sticky="ew")
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        sheet_frame = tk.Frame(canvas, bg=self.colors["card"])
        self.sheet_canvas = canvas
        self.sheet_window = canvas.create_window((0, 0), window=sheet_frame, anchor="nw")

        sheet_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
        canvas.bind("<MouseWheel>", lambda e, c=canvas: self._mousewheel(c, e))
        sheet_frame.bind("<MouseWheel>", lambda e, c=canvas: self._mousewheel(c, e))
        canvas.bind("<Shift-MouseWheel>", lambda e, c=canvas: self._xwheel(c, e))
        # Do not bind Button-6 / Button-7 here. Some Windows Tcl/Tk builds crash
        # with "bad button number 6" even when wrapped in try/except.

        raw = doc.get("sheet")
        if not isinstance(raw, list) or not raw:
            raw = [["" for _ in range(10)] for _ in range(20)]
        rows = max(len(raw), 20)
        cols = max([len(row) if isinstance(row, list) else 1 for row in raw] + [10])

        self.sheet_raw = []
        for r in range(rows):
            src = raw[r] if r < len(raw) and isinstance(raw[r], list) else []
            self.sheet_raw.append([str(src[c]) if c < len(src) else "" for c in range(cols)])

        self.sheet_cells = []
        self._active_sheet_cell = None

        tk.Label(
            sheet_frame,
            text="",
            bg=self.colors["primary_light"],
            width=5,
            height=2,
            relief=tk.FLAT,
        ).grid(row=0, column=0, sticky="nsew", padx=1, pady=1)

        for c in range(cols):
            tk.Label(
                sheet_frame,
                text=self._excel_col(c),
                bg=self.colors["primary_light"],
                fg=self.colors["primary"],
                font=("Microsoft YaHei UI", 9, "bold"),
                width=16,
                pady=7,
            ).grid(row=0, column=c + 1, sticky="nsew", padx=1, pady=1)

        for r in range(rows):
            row_cells = []
            tk.Label(
                sheet_frame,
                text=str(r + 1),
                bg=self.colors["primary_light"],
                fg=self.colors["primary"],
                font=("Microsoft YaHei UI", 9, "bold"),
                width=5,
                relief=tk.FLAT,
            ).grid(row=r + 1, column=0, sticky="nsew", padx=1, pady=1)

            for c in range(cols):
                var = tk.StringVar(value=self._display_sheet_value(r, c))
                entry = tk.Entry(
                    sheet_frame,
                    textvariable=var,
                    bg=self.colors["card_soft"],
                    fg=self.colors["text"],
                    relief=tk.FLAT,
                    bd=0,
                    width=16,
                    font=("Microsoft YaHei UI", 10),
                )
                entry.grid(row=r + 1, column=c + 1, sticky="nsew", padx=1, pady=1, ipady=7)

                def focus_in(event, rr=r, cc=c, vv=var):
                    self._active_sheet_cell = (rr, cc, vv)
                    self._update_sheet_stats()
                    if hasattr(self, "sheet_formula_var"):
                        self.sheet_formula_var.set(self.sheet_raw[rr][cc])
                    vv.set(self.sheet_raw[rr][cc])

                def commit(event=None, rr=r, cc=c, vv=var):
                    try:
                        self.sheet_raw[rr][cc] = vv.get()
                        if hasattr(self, "sheet_formula_var") and getattr(self, "_active_sheet_cell", None) and self._active_sheet_cell[0] == rr and self._active_sheet_cell[1] == cc:
                            self.sheet_formula_var.set(vv.get())
                    except Exception:
                        pass
                    self._active_sheet_cell = (rr, cc, vv)
                    self._mark_unsaved()
                    self._refresh_sheet_display()
                    self._update_sheet_stats()
                    # v2026.06.20.21: sheet edits should autosave like document
                    # edits, but still use the debounced saver to avoid disk churn.
                    self._schedule_autosave()
                    return "break" if event and getattr(event, "keysym", "") == "Return" else None

                entry.bind("<FocusIn>", focus_in)
                entry.bind("<FocusOut>", commit)
                entry.bind("<Return>", commit)
                entry.bind("<Control-v>", lambda e, rr=r, cc=c: self._paste_sheet_data(rr, cc))
                entry.bind("<Control-V>", lambda e, rr=r, cc=c: self._paste_sheet_data(rr, cc))
                row_cells.append(var)
            self.sheet_cells.append(row_cells)
        self._update_sheet_stats()

    def _doc_format_tags(self):
        if not hasattr(self, "text_editor"):
            return []
        tags = []
        for tag in self.text_editor.tag_names():
            if tag in {"sel"}:
                continue
            if tag in {"bold", "italic", "underline", "heading", "align_left", "align_center", "align_right"} or tag.startswith("color_") or tag.startswith("size_"):
                tags.append(tag)
        return tags

    def _capture_doc_formatting(self):
        if not hasattr(self, "text_editor"):
            return {}
        data = {}
        try:
            for tag in self._doc_format_tags():
                ranges = self.text_editor.tag_ranges(tag)
                if not ranges:
                    continue
                pairs = []
                for i in range(0, len(ranges), 2):
                    pairs.append([str(ranges[i]), str(ranges[i + 1])])
                if pairs:
                    data[tag] = pairs
        except Exception:
            return {}
        return data

    def _ensure_dynamic_doc_tag(self, tag):
        if not hasattr(self, "text_editor"):
            return
        try:
            if tag.startswith("color_"):
                color = "#" + tag.split("_", 1)[1]
                if len(color) == 7:
                    self.text_editor.tag_configure(tag, foreground=color)
            elif tag.startswith("size_"):
                size = int(tag.split("_", 1)[1])
                self.text_editor.tag_configure(tag, font=("Microsoft YaHei UI", max(8, min(48, size))))
        except Exception:
            pass

    def _restore_doc_formatting(self, doc):
        if not hasattr(self, "text_editor"):
            return
        formatting = doc.get("formatting", {}) if isinstance(doc, dict) else {}
        if not isinstance(formatting, dict):
            return
        try:
            for tag, ranges in formatting.items():
                self._ensure_dynamic_doc_tag(tag)
                for pair in ranges or []:
                    if not isinstance(pair, (list, tuple)) or len(pair) != 2:
                        continue
                    self.text_editor.tag_add(str(tag), str(pair[0]), str(pair[1]))
        except Exception:
            pass

    def clear_format(self):
        if not hasattr(self, "text_editor"):
            return
        start, end = self._selection_range()
        if not start:
            return
        for tag in self._doc_format_tags():
            try:
                self.text_editor.tag_remove(tag, start, end)
            except Exception:
                pass
        self._mark_unsaved()
        self._schedule_doc_autosave()

    def _apply_alignment(self, tag):
        if not hasattr(self, "text_editor"):
            return
        start, end = self._selection_range()
        if not start:
            start = self.text_editor.index("insert linestart")
            end = self.text_editor.index("insert lineend +1c")
        for old in ("align_left", "align_center", "align_right"):
            self.text_editor.tag_remove(old, start, end)
        self.text_editor.tag_add(tag, start, end)
        self._mark_unsaved()
        self._schedule_doc_autosave()

    def align_left(self):
        self._apply_alignment("align_left")

    def align_center(self):
        self._apply_alignment("align_center")

    def align_right(self):
        self._apply_alignment("align_right")

    def insert_table_template(self):
        if not hasattr(self, "text_editor"):
            return
        template = "\n| Header 1 | Header 2 | Header 3 |\n| --- | --- | --- |\n|  |  |  |\n"
        self.text_editor.insert(tk.INSERT, template)
        self._mark_unsaved()
        self._schedule_doc_autosave()
        self._update_doc_stats()

    def find_in_doc(self):
        if not hasattr(self, "text_editor"):
            return
        keyword = prompt_text_dialog(
            self.app.root,
            self.app.t("shared_find_text"),
            self.app.t("shared_find_hint"),
            self.colors,
            confirm_text=self.app.t("search_btn") if "search_btn" in self.app.lang else "OK",
            cancel_text=self.app.t("cancel_btn"),
            width=520,
            height=230,
        )
        if not keyword:
            return
        try:
            self.text_editor.tag_remove("search_hit", "1.0", tk.END)
            self.text_editor.tag_configure("search_hit", background=self.colors["warning_light"], foreground=self.colors["text"])
            start = "1.0"
            first = None
            while True:
                idx = self.text_editor.search(keyword, start, stopindex=tk.END, nocase=True)
                if not idx:
                    break
                end = f"{idx}+{len(keyword)}c"
                self.text_editor.tag_add("search_hit", idx, end)
                first = first or idx
                start = end
            if first:
                self.text_editor.see(first)
                self.text_editor.mark_set(tk.INSERT, first)
        except Exception:
            pass

    def replace_in_doc(self):
        """Simple Word-style find/replace dialog for the current document."""
        if not hasattr(self, "text_editor"):
            return
        win = create_dialog_window(
            self.app.root,
            self.app.t("shared_replace_text"),
            self.colors,
            width=560,
            height=300,
            modal=False,
            resizable=False,
            min_width=520,
            min_height=280,
        )
        header = tk.Frame(win, bg=self.colors["card"])
        header.pack(fill=tk.X, padx=22, pady=(18, 8))
        tk.Label(header, text=self.app.t("shared_replace_text"), bg=self.colors["card"], fg=self.colors["text"], font=("Microsoft YaHei UI", 16, "bold")).pack(anchor="w")
        tk.Label(header, text=self.app.t("shared_replace_hint"), bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 10), wraplength=500, justify=tk.LEFT).pack(anchor="w", pady=(6, 0))

        body = tk.Frame(win, bg=self.colors["card"])
        body.pack(fill=tk.BOTH, expand=True, padx=22, pady=(0, 12))
        find_var = tk.StringVar()
        replace_var = tk.StringVar()

        def field(label_key, var):
            tk.Label(body, text=self.app.t(label_key), bg=self.colors["card"], fg=self.colors["text_secondary"], font=("Microsoft YaHei UI", 9, "bold")).pack(anchor="w", pady=(8, 4))
            box = tk.Frame(body, bg=self.colors["card_soft"], highlightthickness=1, highlightbackground=self.colors["border"])
            box.pack(fill=tk.X)
            entry = tk.Entry(box, textvariable=var, relief=tk.FLAT, bd=0, bg=self.colors["card_soft"], fg=self.colors["text"], insertbackground=self.colors["primary"], font=("Microsoft YaHei UI", 11))
            entry.pack(fill=tk.X, padx=12, pady=9, ipady=3)
            return entry

        find_entry = field("shared_find_what", find_var)
        field("shared_replace_with", replace_var)

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

        def replace_all(event=None):
            find_text = find_var.get()
            if not find_text:
                return "break"
            replace_text = replace_var.get()
            content = self.text_editor.get("1.0", tk.END)
            count = content.count(find_text)
            if count <= 0:
                messagebox.showinfo(self.app.t("shared_replace_text"), self.app.t("shared_replace_none"), parent=win)
                return "break"
            self.text_editor.delete("1.0", tk.END)
            self.text_editor.insert("1.0", content.replace(find_text, replace_text))
            self._mark_unsaved()
            self._schedule_doc_autosave()
            self._update_doc_stats()
            messagebox.showinfo(self.app.t("shared_replace_text"), self.app.t("shared_replace_done").format(count=count), parent=win)
            return "break"

        create_button(footer, self.app.t("schedule_cancel"), win.destroy, self.colors).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
        create_button(footer, self.app.t("shared_replace_all"), replace_all, self.colors, filled=True).pack(side=tk.LEFT, fill=tk.X, expand=True)
        win.bind("<Return>", replace_all)
        win.bind("<Escape>", lambda _e: [win.destroy(), "break"][-1])
        find_entry.focus_set()

    def copy_doc_content(self):
        if not hasattr(self, "text_editor"):
            return
        try:
            content = self.text_editor.get("1.0", tk.END).strip()
            self.app.root.clipboard_clear()
            self.app.root.clipboard_append(content)
            self.app.set_status("shared_copy_done")
        except Exception:
            pass

    def insert_divider(self):
        if not hasattr(self, "text_editor"):
            return
        self.text_editor.insert(tk.INSERT, "\n────────────────────────\n")
        self._mark_unsaved()
        self._schedule_doc_autosave()
        self._update_doc_stats()

    def insert_meeting_template(self):
        if not hasattr(self, "text_editor"):
            return
        template = self.app.t("shared_meeting_template")
        self.text_editor.insert(tk.INSERT, "\n" + template + "\n")
        self._mark_unsaved()
        self._schedule_doc_autosave()
        self._update_doc_stats()

    def _cycle_color(self):
        colors = [self.colors["primary"], self.colors["danger"], self.colors["warning"], self.colors["solar"], "#111827", "#7C3AED"]
        try:
            idx = colors.index(self.doc_color)
            self.doc_color = colors[(idx + 1) % len(colors)]
        except ValueError:
            self.doc_color = colors[0]
        if hasattr(self, "color_btn"):
            self.color_btn.config(bg=self.doc_color, fg="white")
        return self.doc_color

    def apply_color(self):
        color = self._cycle_color()
        if not hasattr(self, "text_editor"):
            return
        start, end = self._selection_range()
        if start:
            tag = f"color_{color.replace('#', '')}"
            self.text_editor.tag_configure(tag, foreground=color)
            self.text_editor.tag_add(tag, start, end)
            self._schedule_doc_autosave()

    def apply_size(self):
        if not hasattr(self, "text_editor"):
            return
        start, end = self._selection_range()
        if not start:
            return
        try:
            size = int(self.font_size_var.get())
        except Exception:
            size = 12
        tag = f"size_{size}"
        self.text_editor.tag_configure(tag, font=("Microsoft YaHei UI", size))
        self.text_editor.tag_add(tag, start, end)
        self._schedule_doc_autosave()

    def toggle_eraser_off(self):
        self.draw_eraser = False
        if hasattr(self, "brush_btn"):
            self.brush_btn.config(bg=self.colors["primary_light"], fg=self.colors["primary"])
        if hasattr(self, "eraser_btn"):
            self.eraser_btn.config(bg=self.colors["card"], fg=self.colors["text"])

    def toggle_eraser(self):
        self.draw_eraser = not self.draw_eraser
        if hasattr(self, "eraser_btn"):
            self.eraser_btn.config(bg=self.colors["danger_light"] if self.draw_eraser else self.colors["card"], fg=self.colors["danger"] if self.draw_eraser else self.colors["text"])

    def clear_canvas(self):
        if hasattr(self, "draw_canvas"):
            self.draw_canvas.delete("all")

    def _draw_start(self, event):
        self.last_draw = (event.x, event.y)

    def _draw_move(self, event):
        if not hasattr(self, "draw_canvas"):
            return
        if not self.last_draw:
            self.last_draw = (event.x, event.y)
            return
        x0, y0 = self.last_draw
        color = "white" if self.draw_eraser else self.doc_color
        width = 15 if self.draw_eraser else 3
        self.draw_canvas.create_line(x0, y0, event.x, event.y, fill=color, width=width, capstyle=tk.ROUND, smooth=True)
        self.last_draw = (event.x, event.y)

    def _draw_end(self, event):
        self.last_draw = None

    def _selection_range(self):
        try:
            return self.text_editor.index("sel.first"), self.text_editor.index("sel.last")
        except Exception:
            return None, None

    def toggle_bold(self):
        if not hasattr(self, "text_editor"):
            return
        start, end = self._selection_range()
        if start:
            if "bold" in self.text_editor.tag_names("sel.first"):
                self.text_editor.tag_remove("bold", start, end)
            else:
                self.text_editor.tag_add("bold", start, end)
            self._schedule_doc_autosave()

    def apply_heading(self):
        if hasattr(self, "text_editor"):
            start, end = self._selection_range()
            if start:
                self.text_editor.tag_add("heading", start, end)
                self._schedule_doc_autosave()

    def apply_normal(self):
        if hasattr(self, "text_editor"):
            start, end = self._selection_range()
            if start:
                for tag in ("bold", "heading", "italic", "underline"):
                    self.text_editor.tag_remove(tag, start, end)
                self._schedule_doc_autosave()

    def insert_bullet(self):
        if hasattr(self, "text_editor"):
            self.text_editor.insert(tk.INSERT, "• ")
            self._schedule_doc_autosave()

    def toggle_italic(self):
        if not hasattr(self, "text_editor"):
            return
        start, end = self._selection_range()
        if start:
            if "italic" in self.text_editor.tag_names("sel.first"):
                self.text_editor.tag_remove("italic", start, end)
            else:
                self.text_editor.tag_add("italic", start, end)
            self._schedule_doc_autosave()

    def toggle_underline(self):
        if not hasattr(self, "text_editor"):
            return
        start, end = self._selection_range()
        if start:
            if "underline" in self.text_editor.tag_names("sel.first"):
                self.text_editor.tag_remove("underline", start, end)
            else:
                self.text_editor.tag_add("underline", start, end)
            self._schedule_doc_autosave()

    def insert_checklist(self):
        if hasattr(self, "text_editor"):
            self.text_editor.insert(tk.INSERT, "☐ ")
            self._schedule_doc_autosave()

    def insert_mention(self):
        if hasattr(self, "text_editor"):
            self.text_editor.insert(tk.INSERT, "@")
            self._schedule_doc_autosave()

    def insert_datetime(self):
        if hasattr(self, "text_editor"):
            self.text_editor.insert(tk.INSERT, time.strftime("%Y-%m-%d %H:%M "))
            self._schedule_doc_autosave()

    def undo_action(self):
        if hasattr(self, "text_editor"):
            try:
                self.text_editor.edit_undo()
            except Exception:
                pass
            self._schedule_doc_autosave()
            self._update_doc_stats()

    def redo_action(self):
        if hasattr(self, "text_editor"):
            try:
                self.text_editor.edit_redo()
            except Exception:
                pass
            self._schedule_doc_autosave()
            self._update_doc_stats()

    def _schedule_doc_autosave(self):
        if not hasattr(self, "text_editor"):
            return
        try:
            if getattr(self, "_doc_autosave_job", None):
                self.frame.after_cancel(self._doc_autosave_job)
        except Exception:
            pass
        delay = 1400 if getattr(self.app, "is_low_power_mode", lambda: False)() else 900
        self._doc_autosave_job = self.frame.after(delay, lambda: self.save_current(silent=True))

    def _doc_stats_text(self, content):
        content = str(content or "").strip()
        chars = len(content)
        words = len(content.split()) if content else 0
        return self.app.t("shared_doc_stats").format(words=words, chars=chars)

    def _sheet_stats_text(self, doc_or_sheet=None):
        sheet = doc_or_sheet.get("sheet") if isinstance(doc_or_sheet, dict) else doc_or_sheet
        if sheet is None:
            sheet = getattr(self, "sheet_raw", [])
        if not isinstance(sheet, list):
            sheet = []
        rows = len(sheet)
        cols = max([len(row) for row in sheet if isinstance(row, list)] + [0])
        filled = 0
        formulas = 0
        for row in sheet:
            if not isinstance(row, list):
                continue
            for cell in row:
                value = str(cell or "").strip()
                if value:
                    filled += 1
                if value.startswith("="):
                    formulas += 1
        return self.app.t("shared_sheet_stats").format(rows=rows, cols=cols, filled=filled, formulas=formulas)

    def _update_doc_stats(self):
        if not hasattr(self, "text_editor") or not hasattr(self, "doc_stats_label"):
            return
        content = self.text_editor.get("1.0", tk.END).strip()
        self.doc_stats_label.config(text=self._doc_stats_text(content))

    def _update_sheet_stats(self):
        if hasattr(self, "sheet_active_label"):
            cell = "A1"
            if getattr(self, "_active_sheet_cell", None):
                rr, cc, _ = self._active_sheet_cell
                cell = f"{self._excel_col(cc)}{rr + 1}"
            self.sheet_active_label.config(text=f"{self.app.t('shared_active_cell').format(cell=cell)} · {self._sheet_stats_text()}")

    def add_sheet_row(self):
        if not self.current_file_id:
            return
        self.save_current(silent=True)
        doc = self.app.documents[self.current_file_id]
        sheet = doc.setdefault("sheet", [])
        cols = max([len(r) for r in sheet] + [8])
        sheet.append(["" for _ in range(cols)])
        self.open_file(self.current_file_id)

    def add_sheet_col(self):
        if not self.current_file_id:
            return
        self.save_current(silent=True)
        doc = self.app.documents[self.current_file_id]
        sheet = doc.setdefault("sheet", [["" for _ in range(8)] for _ in range(12)])
        for row in sheet:
            row.append("")
        self.open_file(self.current_file_id)

    def delete_sheet_row(self):
        if not self.current_file_id:
            return
        self.save_current(silent=True)
        doc = self.app.documents[self.current_file_id]
        sheet = doc.setdefault("sheet", [])
        if len(sheet) > 1:
            sheet.pop()
            self.open_file(self.current_file_id)

    def delete_sheet_col(self):
        if not self.current_file_id:
            return
        self.save_current(silent=True)
        doc = self.app.documents[self.current_file_id]
        sheet = doc.setdefault("sheet", [[""]])
        if sheet and max(len(row) for row in sheet) > 1:
            for row in sheet:
                if row:
                    row.pop()
            self.open_file(self.current_file_id)

    def _active_sheet_pos(self):
        active = getattr(self, "_active_sheet_cell", None)
        if active:
            return active[0], active[1]
        return 0, 0

    def duplicate_sheet_row(self):
        if not self.current_file_id:
            return
        self.save_current(silent=True)
        doc = self.app.documents[self.current_file_id]
        sheet = doc.setdefault("sheet", [[""]])
        rr, _ = self._active_sheet_pos()
        rr = min(max(rr, 0), len(sheet) - 1)
        sheet.insert(rr + 1, list(sheet[rr]))
        self.open_file(self.current_file_id)

    def duplicate_sheet_col(self):
        if not self.current_file_id:
            return
        self.save_current(silent=True)
        doc = self.app.documents[self.current_file_id]
        sheet = doc.setdefault("sheet", [[""]])
        _, cc = self._active_sheet_pos()
        for row in sheet:
            if not row:
                row.append("")
            src = min(max(cc, 0), len(row) - 1)
            row.insert(src + 1, row[src])
        self.open_file(self.current_file_id)

    def fill_down(self):
        if not self.current_file_id or not getattr(self, "sheet_raw", None):
            return
        rr, cc = self._active_sheet_pos()
        if rr + 1 >= len(self.sheet_raw):
            self.sheet_raw.append(["" for _ in range(max(len(self.sheet_raw[0]), cc + 1))])
        while len(self.sheet_raw[rr + 1]) <= cc:
            self.sheet_raw[rr + 1].append("")
        self.sheet_raw[rr + 1][cc] = self.sheet_raw[rr][cc] if rr < len(self.sheet_raw) and cc < len(self.sheet_raw[rr]) else ""
        self._mark_unsaved()
        self._refresh_sheet_display()
        self._schedule_autosave()

    def fill_right(self):
        if not self.current_file_id or not getattr(self, "sheet_raw", None):
            return
        rr, cc = self._active_sheet_pos()
        while len(self.sheet_raw[rr]) <= cc + 1:
            self.sheet_raw[rr].append("")
        self.sheet_raw[rr][cc + 1] = self.sheet_raw[rr][cc] if cc < len(self.sheet_raw[rr]) else ""
        self._mark_unsaved()
        self._refresh_sheet_display()
        self._schedule_autosave()

    def sort_active_column(self):
        if not self.current_file_id or not getattr(self, "sheet_raw", None):
            return
        _, cc = self._active_sheet_pos()
        try:
            for row in self.sheet_raw:
                while len(row) <= cc:
                    row.append("")
            header = self.sheet_raw[0] if self.sheet_raw else []
            body = self.sheet_raw[1:] if len(self.sheet_raw) > 1 else []

            def sort_key(row):
                value = str(row[cc]).strip()
                try:
                    return (0, float(value))
                except Exception:
                    return (1, value.lower())

            body.sort(key=sort_key)
            self.sheet_raw = [header] + body if header else body
            self._mark_unsaved()
            if self.current_file_id:
                self.app.documents[self.current_file_id]["sheet"] = self.sheet_raw
                save_documents(self.app.documents)
                self.open_file(self.current_file_id)
        except Exception as exc:
            messagebox.showwarning(self.app.t("warning_title"), str(exc), parent=self.app.root)

    def show_sheet_help(self):
        win = create_dialog_window(
            self.app.root,
            self.app.t("shared_sheet_help"),
            self.colors,
            width=620,
            height=420,
            modal=False,
            resizable=True,
            min_width=520,
            min_height=340,
        )
        body = tk.Frame(win, bg=self.colors["card"])
        body.pack(fill=tk.BOTH, expand=True, padx=22, pady=18)
        tk.Label(body, text=self.app.t("shared_sheet_help"), bg=self.colors["card"], fg=self.colors["text"], font=("Microsoft YaHei UI", 16, "bold")).pack(anchor="w")
        text = tk.Text(body, wrap=tk.WORD, relief=tk.FLAT, bd=0, bg=self.colors["card_soft"], fg=self.colors["text"], font=("Microsoft YaHei UI", 10), padx=12, pady=12)
        text.pack(fill=tk.BOTH, expand=True, pady=(12, 0))
        text.insert("1.0", self.app.t("shared_sheet_help_text"))
        text.configure(state=tk.DISABLED)
        create_button(body, self.app.t("ok_btn") if "ok_btn" in self.app.lang else "OK", win.destroy, self.colors, filled=True).pack(anchor="e", pady=(12, 0))

    def insert_formula_sum(self):
        self._insert_formula_template("SUM")

    def insert_formula_average(self):
        self._insert_formula_template("AVERAGE")

    def insert_formula_max(self):
        self._insert_formula_template("MAX")

    def insert_formula_min(self):
        self._insert_formula_template("MIN")

    def insert_formula_count(self):
        self._insert_formula_template("COUNT")

    def apply_formula_bar(self, event=None):
        if not self.current_file_id or not getattr(self, "_active_sheet_cell", None):
            return "break"
        rr, cc, var = self._active_sheet_cell
        value = self.sheet_formula_var.get() if hasattr(self, "sheet_formula_var") else var.get()
        try:
            self.sheet_raw[rr][cc] = value
            var.set(value)
            self._mark_unsaved()
            self._refresh_sheet_display()
            self._schedule_autosave()
        except Exception:
            pass
        return "break"

    def _insert_formula_template(self, func_name):
        if not self.current_file_id or not getattr(self, "_active_sheet_cell", None):
            return
        rr, cc, var = self._active_sheet_cell
        end_row = max(rr, 0) + 1
        col_name = self._excel_col(cc)
        template = f"={func_name}({col_name}1:{col_name}{end_row})"
        var.set(template)
        if hasattr(self, "sheet_formula_var"):
            self.sheet_formula_var.set(template)
        self.sheet_raw[rr][cc] = template
        self._mark_unsaved()
        self._refresh_sheet_display()
        self._schedule_autosave()

    def clear_sheet(self):
        if not self.current_file_id:
            return
        if messagebox.askyesno(self.app.t("confirm_title"), self.app.t("shared_clear_sheet_confirm")):
            doc = self.app.documents[self.current_file_id]
            rows = len(doc.get("sheet", [])) or 12
            cols = max([len(r) for r in doc.get("sheet", [])] + [8])
            doc["sheet"] = [["" for _ in range(cols)] for _ in range(rows)]
            self.open_file(self.current_file_id)

    def _paste_sheet_data(self, start_row, start_col):
        try:
            raw = self.app.root.clipboard_get()
        except Exception:
            return None
        if "\t" not in raw and "\n" not in raw:
            return None
        rows = [line.split("\t") for line in raw.replace("\r\n", "\n").replace("\r", "\n").split("\n") if line != ""]
        if not rows:
            return "break"
        max_r = start_row + len(rows)
        max_c = start_col + max(len(r) for r in rows)
        while len(self.sheet_raw) < max_r:
            self.sheet_raw.append(["" for _ in range(max(len(self.sheet_raw[0]) if self.sheet_raw else 0, max_c))])
        for row in self.sheet_raw:
            while len(row) < max_c:
                row.append("")
        for r, row_values in enumerate(rows):
            for c, value in enumerate(row_values):
                self.sheet_raw[start_row + r][start_col + c] = value
        # Re-render to add any newly-created rows/columns and recalculate formulas.
        if self.current_file_id:
            self.app.documents[self.current_file_id]["sheet"] = self.sheet_raw
            save_documents(self.app.documents)
            self.open_file(self.current_file_id)
        return "break"

    def export_current_file(self):
        if not self.current_file_id or self.current_file_id not in self.app.documents:
            return
        self.save_current(silent=True)
        doc = self.app.documents[self.current_file_id]
        title = self.app.display_doc_title(doc.get("title", "")).replace("/", "_").replace("\\\\", "_")
        if doc.get("type") == "sheet":
            path = filedialog.asksaveasfilename(
                title=self.app.t("shared_export"),
                defaultextension=".xlsx",
                initialfile=f"{title}.xlsx",
                filetypes=[("Excel Workbook", "*.xlsx"), ("CSV", "*.csv")],
            )
            if not path:
                return
            if str(path).lower().endswith(".xlsx"):
                try:
                    from openpyxl import Workbook
                    wb = Workbook()
                    ws = wb.active
                    ws.title = self.app.display_doc_title(doc.get("title", "Sheet"))[:31] or "Sheet"
                    old_raw = getattr(self, "sheet_raw", None)
                    self.sheet_raw = doc.get("sheet", [])
                    for r, row in enumerate(doc.get("sheet", []), start=1):
                        for c, _value in enumerate(row, start=1):
                            ws.cell(row=r, column=c, value=self._display_sheet_value(r - 1, c - 1))
                    if old_raw is not None:
                        self.sheet_raw = old_raw
                    wb.save(path)
                except Exception:
                    messagebox.showwarning(self.app.t("warning_title"), self.app.t("shared_export_xlsx_unavailable"), parent=self.app.root)
                    return
            else:
                with open(path, "w", encoding="utf-8-sig", newline="") as f:
                    writer = csv.writer(f)
                    for r, row in enumerate(doc.get("sheet", [])):
                        writer.writerow([self._display_sheet_value(r, c) for c in range(len(row))])
        else:
            path = filedialog.asksaveasfilename(
                title=self.app.t("shared_export"),
                defaultextension=".docx",
                initialfile=f"{title}.docx",
                filetypes=[("Word Document", "*.docx"), ("Text", "*.txt"), ("Markdown", "*.md")],
            )
            if not path:
                return
            if str(path).lower().endswith(".docx"):
                try:
                    from docx import Document
                    document = Document()
                    document.add_heading(self.app.display_doc_title(doc.get("title", "")), level=1)
                    for line in str(doc.get("content", "")).splitlines():
                        document.add_paragraph(line)
                    document.save(path)
                except Exception:
                    messagebox.showwarning(self.app.t("warning_title"), self.app.t("shared_export_docx_unavailable"), parent=self.app.root)
                    return
            else:
                Path(path).write_text(str(doc.get("content", "")), encoding="utf-8")
        self.app.set_status("shared_export_done")
        messagebox.showinfo(self.app.t("success_title"), self.app.t("shared_export_done"))

    def _excel_col(self, idx):
        result = ""
        idx += 1
        while idx:
            idx, rem = divmod(idx - 1, 26)
            result = chr(65 + rem) + result
        return result

    def _cell_ref_to_pos(self, ref):
        m = re.match(r"^([A-Z]+)(\d+)$", ref.upper().strip())
        if not m:
            return None
        col = 0
        for ch in m.group(1):
            col = col * 26 + ord(ch) - 64
        return int(m.group(2)) - 1, col - 1

    def _raw_cell(self, r, c):
        try:
            return self.sheet_raw[r][c]
        except Exception:
            return ""

    def _number(self, value):
        try:
            if value is None or value == "":
                return 0.0
            return float(str(value).strip())
        except Exception:
            return 0.0

    def _eval_cell(self, r, c, seen=None):
        seen = seen or set()
        if (r, c) in seen:
            return "#CYCLE"
        raw = self._raw_cell(r, c)
        if isinstance(raw, str) and raw.strip().startswith("="):
            return self._eval_formula(raw, seen | {(r, c)})
        return raw

    def _range_values(self, start_ref, end_ref, seen=None):
        a = self._cell_ref_to_pos(start_ref)
        b = self._cell_ref_to_pos(end_ref)
        if not a or not b:
            return []
        r1, c1 = a
        r2, c2 = b
        vals = []
        for r in range(min(r1, r2), max(r1, r2) + 1):
            for c in range(min(c1, c2), max(c1, c2) + 1):
                vals.append(self._number(self._eval_cell(r, c, seen or set())))
        return vals

    def _range_non_empty_count(self, start_ref, end_ref, seen=None):
        a = self._cell_ref_to_pos(start_ref)
        b = self._cell_ref_to_pos(end_ref)
        if not a or not b:
            return 0
        r1, c1 = a
        r2, c2 = b
        count = 0
        for r in range(min(r1, r2), max(r1, r2) + 1):
            for c in range(min(c1, c2), max(c1, c2) + 1):
                raw = self._eval_cell(r, c, seen or set())
                if str(raw).strip() != "":
                    count += 1
        return count

    def _safe_formula_number(self, expr):
        expr = str(expr or "").strip()
        if not re.match(r"^[0-9\.\+\-\*/\(\) ]+$", expr):
            raise ValueError("unsafe formula")
        return eval(expr, {"__builtins__": {}}, {})

    def _eval_formula(self, formula, seen=None):
        expr = str(formula).strip()
        if not expr.startswith("="):
            return expr
        expr = expr[1:].strip().upper()
        try:
            def agg_repl(match):
                func, start_ref, end_ref = match.group(1), match.group(2), match.group(3)
                vals = self._range_values(start_ref, end_ref, seen or set())
                if not vals:
                    return "0"
                if func == "SUM":
                    val = sum(vals)
                elif func == "AVERAGE":
                    val = sum(vals) / len(vals)
                elif func == "MAX":
                    val = max(vals)
                elif func == "MIN":
                    val = min(vals)
                elif func == "COUNT":
                    val = self._range_non_empty_count(start_ref, end_ref, seen or set())
                else:
                    val = 0
                return str(val)

            expr = re.sub(r"\b(SUM|AVERAGE|MAX|MIN|COUNT)\(([A-Z]+\d+):([A-Z]+\d+)\)", agg_repl, expr)

            def cell_repl(match):
                pos = self._cell_ref_to_pos(match.group(0))
                if not pos:
                    return "0"
                value = self._eval_cell(pos[0], pos[1], seen or set())
                return str(self._number(value))

            expr = re.sub(r"\b[A-Z]+\d+\b", cell_repl, expr)

            # v2026.06.20.24: keep extending common formulas while still
            # evaluating only a sanitized numeric expression.
            for _ in range(5):
                before = expr

                def abs_repl(match):
                    return str(abs(float(self._safe_formula_number(match.group(1)))))

                def round_repl(match):
                    digits = max(0, min(int(match.group(2)), 8))
                    return str(round(float(self._safe_formula_number(match.group(1))), digits))

                def sqrt_repl(match):
                    value = float(self._safe_formula_number(match.group(1)))
                    if value < 0:
                        raise ValueError("negative sqrt")
                    return str(value ** 0.5)

                def power_repl(match):
                    base = float(self._safe_formula_number(match.group(1)))
                    exponent = float(self._safe_formula_number(match.group(2)))
                    return str(base ** exponent)

                expr = re.sub(r"\bABS\(([0-9\.\+\-\*/\(\) ]+)\)", abs_repl, expr)
                expr = re.sub(r"\bROUND\(([0-9\.\+\-\*/\(\) ]+),\s*([0-9]+)\)", round_repl, expr)
                expr = re.sub(r"\bSQRT\(([0-9\.\+\-\*/\(\) ]+)\)", sqrt_repl, expr)
                expr = re.sub(r"\bPOWER\(([0-9\.\+\-\*/\(\) ]+),\s*([0-9\.\+\-\*/\(\) ]+)\)", power_repl, expr)
                if expr == before:
                    break

            if not re.match(r"^[0-9\.\+\-\*/\(\) ]+$", expr):
                return "#ERR"
            val = self._safe_formula_number(expr)
            if isinstance(val, float) and val.is_integer():
                return str(int(val))
            return str(round(val, 8)).rstrip("0").rstrip(".")
        except Exception:
            return "#ERR"

    def _display_sheet_value(self, r, c):
        raw = self._raw_cell(r, c)
        if isinstance(raw, str) and raw.strip().startswith("="):
            return self._eval_formula(raw)
        return raw

    def _refresh_sheet_display(self):
        if not getattr(self, "sheet_cells", None):
            return
        active = getattr(self, "_active_sheet_cell", None)
        for r, row in enumerate(self.sheet_cells):
            for c, var in enumerate(row):
                if active and active[0] == r and active[1] == c:
                    continue
                try:
                    var.set(self._display_sheet_value(r, c))
                except Exception:
                    pass

    def _push_file_history(self, doc, old_title=None, old_content=None, old_sheet=None, old_formatting=None):
        """Store a lightweight local version snapshot before overwriting a file."""
        if not isinstance(doc, dict):
            return
        try:
            snapshot = {
                "saved_at": now_text(),
                "title": old_title if old_title is not None else doc.get("title", ""),
                "type": doc.get("type", "doc"),
                "content": old_content if old_content is not None else doc.get("content", ""),
                "sheet": copy.deepcopy(old_sheet if old_sheet is not None else doc.get("sheet", [])),
                "formatting": copy.deepcopy(old_formatting if old_formatting is not None else doc.get("formatting", {})),
            }
            history = list(doc.get("history", []) or [])
            if history:
                last = history[0]
                if (
                    last.get("content") == snapshot.get("content")
                    and last.get("sheet") == snapshot.get("sheet")
                    and last.get("formatting") == snapshot.get("formatting")
                ):
                    return
            history.insert(0, snapshot)
            doc["history"] = history[:5]
        except Exception:
            pass

    def show_file_history(self):
        if not self.current_file_id or self.current_file_id not in self.app.documents:
            return
        self.save_current(silent=True)
        doc = self.app.documents.get(self.current_file_id, {})
        history = list(doc.get("history", []) or [])
        win = create_dialog_window(
            self.app.root,
            self.app.t("shared_version_history"),
            self.colors,
            width=660,
            height=520,
            modal=False,
            resizable=True,
            min_width=560,
            min_height=420,
        )
        body = tk.Frame(win, bg=self.colors["card"])
        body.pack(fill=tk.BOTH, expand=True, padx=22, pady=18)
        tk.Label(
            body,
            text=self.app.t("shared_version_history"),
            bg=self.colors["card"],
            fg=self.colors["text"],
            font=("Microsoft YaHei UI", 16, "bold"),
        ).pack(anchor="w")
        header_row = tk.Frame(body, bg=self.colors["card"])
        header_row.pack(fill=tk.X, pady=(4, 14))
        tk.Label(
            header_row,
            text=f"{self.app.t('shared_history_count')}: {len(history)}",
            bg=self.colors["card"],
            fg=self.colors["text_secondary"],
            font=("Microsoft YaHei UI", 9, "bold"),
        ).pack(side=tk.LEFT)

        def clear_history():
            if not self.current_file_id or self.current_file_id not in self.app.documents:
                return
            if not messagebox.askyesno(self.app.t("confirm_title"), self.app.t("shared_clear_history_confirm"), parent=win):
                return
            self.app.documents[self.current_file_id]["history"] = []
            save_documents(self.app.documents)
            try:
                win.destroy()
            except Exception:
                pass
            self.app.set_status("shared_history_cleared")
            self.show_file_history()

        if history:
            create_button(header_row, self.app.t("shared_clear_history"), clear_history, self.colors).pack(side=tk.RIGHT)

        list_wrap = tk.Frame(body, bg=self.colors["card"])
        list_wrap.pack(fill=tk.BOTH, expand=True)
        canvas = tk.Canvas(list_wrap, bg=self.colors["card"], highlightthickness=0)
        ybar = tk.Scrollbar(list_wrap, orient="vertical", command=canvas.yview)
        inner = tk.Frame(canvas, bg=self.colors["card"])
        win_id = canvas.create_window((0, 0), window=inner, anchor="nw")
        canvas.configure(yscrollcommand=ybar.set)
        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        ybar.pack(side=tk.RIGHT, fill=tk.Y)

        def sync(event=None):
            try:
                canvas.configure(scrollregion=canvas.bbox("all"))
                canvas.itemconfig(win_id, width=max(canvas.winfo_width() - 4, 420))
            except Exception:
                pass
        inner.bind("<Configure>", sync)
        canvas.bind("<Configure>", sync)

        def restore_snapshot(snapshot):
            if not self.current_file_id or self.current_file_id not in self.app.documents:
                return
            current = self.app.documents[self.current_file_id]
            self._push_file_history(
                current,
                current.get("title", ""),
                current.get("content", ""),
                current.get("sheet") if current.get("type") == "sheet" else None,
                current.get("formatting", {}),
            )
            current["title"] = snapshot.get("title", current.get("title", ""))
            if current.get("type") == "sheet":
                current["sheet"] = copy.deepcopy(snapshot.get("sheet", current.get("sheet", [])))
                current["content"] = snapshot.get("content", current.get("content", ""))
            else:
                current["content"] = snapshot.get("content", current.get("content", ""))
                current["formatting"] = copy.deepcopy(snapshot.get("formatting", {}))
            current["updated"] = now_text()
            save_documents(self.app.documents)
            try:
                win.destroy()
            except Exception:
                pass
            self.open_file(self.current_file_id)
            self.app.set_status("shared_history_restored")

        if not history:
            show_empty(inner, self.app.t("shared_no_version_history"), self.colors)
        for idx, snap in enumerate(history):
            card = tk.Frame(inner, bg=self.colors["card_soft"], highlightthickness=1, highlightbackground=self.colors["border"])
            card.pack(fill=tk.X, pady=(0, 10))
            top = tk.Frame(card, bg=self.colors["card_soft"])
            top.pack(fill=tk.X, padx=12, pady=(10, 6))
            tk.Label(
                top,
                text=f"{idx + 1}. {snap.get('saved_at', '-')}",
                bg=self.colors["card_soft"],
                fg=self.colors["text"],
                font=("Microsoft YaHei UI", 10, "bold"),
            ).pack(side=tk.LEFT)
            create_button(top, self.app.t("shared_restore_version"), lambda s=snap: restore_snapshot(s), self.colors).pack(side=tk.RIGHT)
            if snap.get("type") == "sheet":
                rows = snap.get("sheet", []) or []
                cells = []
                for row in rows[:3]:
                    if isinstance(row, list):
                        cells.extend([str(x) for x in row[:4] if str(x or "").strip()])
                preview = f"{self.app.t('shared_sheet_snapshot')} · " + (" | ".join(cells[:8]) if cells else f"{len(rows)} rows")
            else:
                preview = str(snap.get("content", "") or "").replace("\n", " ")
                if not preview:
                    preview = self.app.t("shared_doc_snapshot")
            if len(preview) > 160:
                preview = preview[:160] + "…"
            tk.Label(
                card,
                text=preview or "-",
                bg=self.colors["card_soft"],
                fg=self.colors["text_secondary"],
                font=("Microsoft YaHei UI", 9),
                justify=tk.LEFT,
                wraplength=560,
            ).pack(anchor="w", padx=12, pady=(0, 10))
        sync()

    def save_current(self, silent=False):
        if not self.current_file_id or self.current_file_id not in self.app.documents:
            return False
        doc = self.app.documents[self.current_file_id]
        old_title = doc.get("title", "")
        old_content = doc.get("content", "")
        old_sheet = doc.get("sheet") if doc.get("type") == "sheet" else None

        title = self.title_var.get().strip() or self.app.t("shared_untitled_doc")
        if title != doc.get("title"):
            title = self._ensure_unique_title(title, exclude_id=self.current_file_id)
            self.title_var.set(title)

        new_content = old_content
        new_sheet = old_sheet
        if doc.get("type") == "sheet":
            active = getattr(self, "_active_sheet_cell", None)
            if active:
                r, c, var = active
                try:
                    self.sheet_raw[r][c] = var.get()
                except Exception:
                    pass
            if getattr(self, "sheet_raw", None):
                new_sheet = [list(row) for row in self.sheet_raw]
                new_content = " ".join(" ".join(str(self._display_sheet_value(r, c)) for c in range(len(row))) for r, row in enumerate(self.sheet_raw))
        else:
            if hasattr(self, "text_editor"):
                new_content = self.text_editor.get("1.0", tk.END).strip()
        new_formatting = self._capture_doc_formatting() if doc.get("type") != "sheet" and hasattr(self, "text_editor") else doc.get("formatting", {})
        old_formatting = doc.get("formatting", {}) if doc.get("type") != "sheet" else {}

        changed = (
            title != old_title
            or new_content != old_content
            or (doc.get("type") != "sheet" and new_formatting != old_formatting)
            or (doc.get("type") == "sheet" and new_sheet != old_sheet)
            or doc.get("shared") is not True
        )

        if not changed:
            if not silent:
                self.app.set_status("status_saved")
            if hasattr(self, "share_state"):
                self.share_state.config(text=self.app.t("shared_status_saved"), bg=self.colors["success_light"], fg=self.colors["success"])
            self._update_file_footer(doc, status_key="shared_status_saved")
            return False

        self._push_file_history(doc, old_title, old_content, old_sheet, old_formatting)
        doc["title"] = title
        if doc.get("type") == "sheet":
            doc["sheet"] = new_sheet
            doc["content"] = new_content
        else:
            doc["content"] = new_content
            doc["formatting"] = new_formatting
        doc["updated"] = now_text()
        doc["shared"] = True
        self._notify_mentions(doc)
        save_documents(self.app.documents)
        if hasattr(self, "share_state"):
            self.share_state.config(text=self.app.t("shared_status_saved"), bg=self.colors["success_light"], fg=self.colors["success"])
        self._update_file_footer(doc, status_key="shared_status_saved")
        if not silent:
            self.app.set_status("status_saved")
            self.refresh_file_list()
            self.app.refresh_home()
        return True

    def _notify_mentions(self, doc):
        text = f"{doc.get('title','')} {doc.get('content','')}"
        matched = []
        for emp in self.app.enterprise.get("employees", []):
            name = emp.get("name", "").strip()
            if name and f"@{name}" in text:
                matched.append(name)
        if not matched:
            return
        title = self.app.t("shared_mention_title")
        content = self.app.t("shared_mention_content").format(file=doc.get("title", ""), users=", ".join(matched))
        # Avoid duplicate mention notification on every silent save.
        stamp = f"{doc.get('title')}|{','.join(matched)}|{doc.get('updated')}"
        if doc.get("_last_mention_stamp") == stamp:
            return
        doc["_last_mention_stamp"] = stamp
        self.app.enterprise.setdefault("notifications", []).append({
            "id": f"mention-{int(time.time() * 1000)}",
            "title": title,
            "content": content,
            "audience": "selected",
            "audience_label": ", ".join(matched),
            "time": now_text(),
        })
        try:
            from core.storage import save_enterprise
            save_enterprise(self.app.enterprise)
        except Exception:
            pass

    def delete_file(self, doc_id):
        if doc_id not in self.app.documents:
            return
        if not messagebox.askyesno(self.app.t("confirm_title"), self.app.t("shared_delete_confirm")):
            return
        was_current = doc_id == self.current_file_id
        self.app.documents.pop(doc_id, None)
        save_documents(self.app.documents)
        if was_current:
            remaining = list(self._active_docs().keys())
            self.current_file_id = remaining[0] if remaining else None
            if self.current_file_id:
                self.open_file(self.current_file_id)
            else:
                self._render_empty_editor()
        self.refresh_file_list()
        self.app.refresh_home()

    def update_language(self):
        self.page_title.config(text=self.app.t("shared_files_title"))
        if hasattr(self, "scope_badge"):
            self.scope_badge.config(text=self._scope_label())
        self.page_desc.config(text=self.app.t("shared_files_desc"))
        if hasattr(self, "folder_label"):
            self.folder_label.config(text=self.app.t("shared_folder_label"))
            self._update_folder_menu()
        if hasattr(self, "personal_scope_btn"):
            self.personal_scope_btn.config(text=self.app.t("mode_personal"))
        if hasattr(self, "enterprise_scope_btn"):
            self.enterprise_scope_btn.config(text=self.app.t("mode_enterprise"))
        self._update_scope_switch()
        self.new_doc_btn.config(text=self.app.t("shared_new_doc"))
        self.new_sheet_btn.config(text=self.app.t("shared_new_sheet"))
        if hasattr(self, "batch_create_btn"):
            self.batch_create_btn.config(text=self.app.t("shared_batch_create"))
        self.upload_btn.config(text=self.app.t("shared_upload"))
        self.save_btn.config(text=self.app.t("shared_save"))
        if hasattr(self, "duplicate_btn"):
            self.duplicate_btn.config(text=self.app.t("shared_duplicate_file"))
        if hasattr(self, "archive_btn"):
            self._update_archive_button()
        if hasattr(self, "properties_btn"):
            self.properties_btn.config(text=self.app.t("shared_file_properties"))
        if hasattr(self, "full_editor_btn"):
            self.full_editor_btn.config(text=self.app.t("shared_open_editor"))
        if hasattr(self, "export_btn"):
            self.export_btn.config(text=self.app.t("shared_export"))
        for key, label_key in [("all", "shared_filter_all"), ("doc", "shared_filter_docs"), ("sheet", "shared_filter_sheets"), ("favorite", "shared_filter_favorites"), ("recent", "shared_filter_recent"), ("archive", "shared_filter_archive")]:
            if key in self.filter_buttons:
                self.filter_buttons[key].config(text=self.app.t(label_key))
        self._update_favorite_button()
        for key, label_key in [("updated", "shared_sort_recent"), ("opened", "shared_sort_opened"), ("name", "shared_sort_name"), ("type", "shared_sort_type"), ("folder", "shared_sort_folder")]:
            if hasattr(self, "sort_buttons") and key in self.sort_buttons:
                self.sort_buttons[key].config(text=self.app.t(label_key))
        self.set_sort(getattr(self, "sort_mode", "updated"))
        self.refresh_file_list()
