import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type React from "react";
import { Link, useParams, useNavigate } from "react-router";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  getProject,
  getStructure,
  listLanguages,
  type Project,
  type ProjectStructure,
  getLanguageTranslations,
  upsertLanguageTranslations,
  upsertStructure,
  deleteEntryFromAllLanguages,
  renameEntryInAllLanguages,
  updateProject,
  deleteProjectDeep,
} from "@/lib/db";
import { ArrowBigDownDash, ArrowBigUpDash, ArrowLeft, Brackets, CaseSensitive, Download, Filter, Languages, LocateFixed, MoreVertical, PencilLine, Reply, Save, Settings, Trash2 } from "lucide-react";
import { useTranslationInlineEdit } from "@/hooks/biz/use-translation-inline-edit";
import { flattenEntries, type FlatEntry, insertEntrySibling, removeEntryAtPath, renameEntryAtPath } from "@/lib/i18n-structure";
import { ImportLanguageModal } from "@/components/biz/import-language-modal";
import { ExportLanguageModal } from "@/components/biz/export-language-modal";
import { EntryNameModal } from "@/components/biz/entry-name-modal";
import { ProjectSettingsModal } from "@/components/biz/project-settings-modal";
import { AiTranslateModal } from "@/components/biz/ai-translate-modal";
import { TableVirtuoso, type TableVirtuosoHandle } from "react-virtuoso";
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { useClipboard } from "@/hooks/use-clipboard";
import { toast } from "sonner";
import { Checkbox } from "@/components/ui/checkbox";
import { Textarea } from "@/components/ui/textarea";
import { clearAllConnections, useFileConnections, writeLanguageToConnectedFile } from "@/store/file-connection";
import { generateLanguageJson } from "@/lib/utils";
import { HeaderConnectionIndicator } from "@/components/biz/header-connection-indicator";

export default function Editor() {
  const { id: projectId } = useParams();
  const navigate = useNavigate();
  const [project, setProject] = useState<Project | null>(null);
  const [structure, setStructure] = useState<ProjectStructure | null>(null);
  const [languages, setLanguages] = useState<string[]>([]);
  const [loading, setLoading] = useState(true);
  const [pageError, setPageError] = useState<string | null>(null);
  const [importOpen, setImportOpen] = useState(false);
  const [exportOpen, setExportOpen] = useState(false);
  const [addModal, setAddModal] = useState<{ open: boolean; path: string; position: "above" | "below" } | null>(null);
  const [renameModal, setRenameModal] = useState<{ open: boolean; path: string } | null>(null);
  const virtuosoRef = useRef<TableVirtuosoHandle | null>(null);
  const scrollerRootRef = useRef<HTMLElement | Window | null>(null);
  const [query, setQuery] = useState("");
  const [caseSensitive, setCaseSensitive] = useState(false);
  const [fullMatch, setFullMatch] = useState(false);
  const [aiModal, setAiModal] = useState<{ open: boolean; path: string } | null>(null);
  const [aiBulkOpen, setAiBulkOpen] = useState(false);
  const [settingsOpen, setSettingsOpen] = useState(false);
  const [selected, setSelected] = useState<Set<string>>(new Set());
  const [visibleLangs, setVisibleLangs] = useState<Set<string>>(new Set());
  const [savingAll, setSavingAll] = useState(false);

  const { copy } = useClipboard();
  const connSnap = useFileConnections(projectId ?? "");

  function highlightRow(index: number) {
    const tryFindAndAnimate = (attempt = 0) => {
      const root = scrollerRootRef.current as HTMLElement | null;
      if (!root) return;
      const row = root.querySelector(`tr[data-item-index="${index}"]`) as HTMLTableRowElement | null;
      if (row) {
        row.animate(
          [
            { backgroundColor: "rgba(250, 204, 21, 0.6)" },
            { backgroundColor: "transparent" },
            { backgroundColor: "rgba(250, 204, 21, 0.6)" },
            { backgroundColor: "transparent" },
          ],
          { duration: 1200, easing: "ease-in-out" }
        );
        return;
      }
      if (attempt < 10) {
        setTimeout(() => tryFindAndAnimate(attempt + 1), 50);
      }
    };
    // 等一帧，确保滚动定位后的 DOM 稳定
    requestAnimationFrame(() => tryFindAndAnimate(0));
  }

  function scrollToQuery(ev: React.FormEvent<HTMLFormElement>) {
    ev.preventDefault();
    ev.stopPropagation();

    if (!query) return;
    const idx = entries.findIndex((e) => {
      const hay = caseSensitive ? e.path : e.path.toLowerCase();
      const needle = caseSensitive ? query : query.toLowerCase();
      return fullMatch ? hay === needle : hay.includes(needle);
    });
    if (idx >= 0) {
      virtuosoRef.current?.scrollIntoView({ index: idx, align: "center", done: () => highlightRow(idx) });
    }
  }

  async function refresh() {
    if (!projectId) return;
    setLoading(true);
    setPageError(null);
    try {
      const p = await getProject(projectId);
      if (!p) throw new Error("项目不存在");
      setProject(p);
      const s = await getStructure(projectId);
      if (s) setStructure(s);
      const langs = await listLanguages(projectId);
      setLanguages(langs);
    } catch (e) {
      setPageError((e as Error)?.message ?? "加载失败");
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    void refresh();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [projectId]);

  const entries: FlatEntry[] = useMemo(() => {
    if (!structure) return [];
    return flattenEntries(structure.root);
  }, [structure]);

  const [valuesByLang, setValuesByLang] = useState<Record<string, Record<string, string>>>({});
  const inline = useTranslationInlineEdit({
    projectId,
    valuesByLang,
    setValuesByLang,
    onError: (m) => setPageError(m),
  });

  // 同步可见语言集合
  useEffect(() => {
    setVisibleLangs(new Set(languages));
  }, [languages]);

  const displayedLanguages = useMemo(() => languages.filter((l) => visibleLangs.has(l)), [languages, visibleLangs]);

  const tableWidth = useMemo(() => (displayedLanguages.length * 200) + 36 + 60 + 300, [displayedLanguages.length]);

  const handleSaveAllConnected = useCallback(async () => {
    if (!projectId || !structure) return;
    const connectionEntries = Object.entries(connSnap.connections);
    if (connectionEntries.length === 0) {
      toast.info("没有已连接的语言");
      return;
    }
    setSavingAll(true);
    try {
      const orderedPaths = flattenEntries(structure.root).map((e) => e.path);
      const results = await Promise.allSettled(
        connectionEntries.map(async ([lang]) => {
          const jsonText = generateLanguageJson(valuesByLang, lang, orderedPaths);
          const ok = await writeLanguageToConnectedFile(projectId, lang, jsonText);
          if (!ok) throw new Error(lang);
          return lang;
        })
      );
      const failed: string[] = [];
      let success = 0;
      for (const r of results) {
        if (r.status === "fulfilled") success += 1;
        else failed.push((r.reason as Error)?.message || "未知语言");
      }
      if (failed.length === 0) {
        toast.success(`全部保存成功（${success}）`);
      } else if (success === 0) {
        toast.error(`保存失败（${failed.length}）：${failed.join(", ")}`);
      } else {
        toast.warning(`部分成功（成功 ${success}，失败 ${failed.length}）：${failed.join(", ")}`);
      }
    } finally {
      setSavingAll(false);
    }
  }, [projectId, structure, connSnap.connections, valuesByLang]);

  function computeSuggestedLanguages(pathsInput: string[]): string[] {
    if (languages.length === 0 || pathsInput.length === 0) return [];

    const missing = new Set<string>();

    for (const lang of languages) {
      const langMap = valuesByLang[lang] || {};
      let hasMissing = false;

      for (const p of pathsInput) {
        const val = langMap[p];
        if (typeof val !== "string" || val.trim() === "") {
          hasMissing = true;
          break;
        }
      }

      if (hasMissing) missing.add(lang);
    }

    const arr = Array.from(missing);

    return arr.length > 0 ? arr : languages; // 如无缺失则默认全选
  }

  const getExistingByPath = useCallback((path: string) => {
    const result: Record<string, string> = {};
    for (const lang of languages) {
      const text = valuesByLang[lang]?.[path];
      if (typeof text === "string" && text.trim() !== "") {
        result[lang] = text;
      }
    }

    return result;
  }, [languages, valuesByLang]);

  const virtuosoComponents = useMemo(() => ({
    Table: ({ style, ...props }: React.TableHTMLAttributes<HTMLTableElement>) => <table className="min-w-full text-sm table-fixed" {...props} style={{ ...style, width: tableWidth }} />,
    TableHead: (props: React.HTMLAttributes<HTMLTableSectionElement>) => <thead className="bg-muted" {...props} />,
    TableRow: (props: React.HTMLAttributes<HTMLTableRowElement>) => <tr className="border-t" {...props} />,
    TableBody: (props: React.HTMLAttributes<HTMLTableSectionElement>) => <tbody {...props} />,
    TableFoot: (props: React.HTMLAttributes<HTMLTableSectionElement>) => <tfoot {...props} />,
  }), [tableWidth]);

  const allSelected = useMemo(() => entries.length > 0 && selected.size === entries.length, [entries, selected]);
  const MAX_AI_ITEMS = 50;

  const headerContent = useCallback(() => (
    <tr>
      <th style={{ width: 36 }} className="sticky left-0 text-left px-3 py-2 bg-muted">
        <Checkbox
          checked={allSelected}
          onCheckedChange={(checked) => {
            const next = new Set<string>();
            if (checked) {
              for (const en of entries) next.add(en.path);
            }
            setSelected(next);
          }}
        />
      </th>
      <th style={{ width: 300 }} className="text-left px-3 py-2">翻译条目名称</th>
      {displayedLanguages.map((lang) => (
        <th key={lang} style={{ width: 200 }} className="text-left px-3 py-2">{lang}</th>
      ))}
      <th style={{ width: 60 }} className="sticky right-0 text-left px-3 py-2 bg-muted">操作</th>
    </tr>
  ), [displayedLanguages, allSelected, entries]);

  const renderItemContent = useCallback((_idx: number, entry: FlatEntry) => {
    const handleCopy = () => {
      copy(entry.path);
      toast.success("复制成功");
    };

    return (
      <>
        <td className="sticky left-0 px-3 py-2 bg-white">
          <Checkbox
            checked={selected.has(entry.path)}
            onCheckedChange={(checked) => {
              setSelected((prev) => {
                const next = new Set(prev);
                if (checked) next.add(entry.path); else next.delete(entry.path);
                return next;
              });
            }}
          />
        </td>
        <td className="px-3 py-2 font-mono wrap-break-word">
          <button type="button" className="w-full text-left min-h-8 leading-normal px-2 rounded hover:bg-accent" onClick={handleCopy} title="点击复制">
            {entry.path}
          </button>
        </td>
        {displayedLanguages.map((lang) => {
          const isEditing = inline.isEditingCell(entry.path, lang);
          const isSaving = inline.isSavingCell(entry.path, lang);
          const displayValue = inline.getDisplayValue(entry.path, lang);

          return (
            <td key={`${entry.path}:${lang}`} className="px-3 py-2 text-foreground/90 align-top">
              {isEditing ? (
                <Textarea
                  ref={inline.inputRef}
                  value={inline.editingValue}
                  onChange={(e) => inline.setEditingValue(e.target.value)}
                  onBlur={inline.saveEdit}
                  onKeyDown={inline.handleKeyDown}
                  disabled={isSaving}
                  className="leading-normal px-2 py-0"
                />
              ) : (
                <button
                  type="button"
                  className="w-full text-left min-h-8 leading-normal px-2 rounded hover:bg-accent"
                  onClick={() => inline.startEdit(entry.path, lang)}
                  title="点击编辑"
                >
                  {displayValue || <span className="text-destructive">点击编辑</span>}
                </button>
              )}
            </td>
          );
        })}
        <td className="sticky right-0 px-3 py-2 align-top bg-white">
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button size="sm" variant="outline">
                <MoreVertical />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end" className="w-44">
              <DropdownMenuItem onClick={() => setAddModal({ open: true, path: entry.path, position: "below" })}>
                <ArrowBigDownDash />
                在下面新增
              </DropdownMenuItem>
              <DropdownMenuItem onClick={() => setAddModal({ open: true, path: entry.path, position: "above" })}>
                <ArrowBigUpDash />
                在上面新增
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem onClick={() => setAiModal({ open: true, path: entry.path })}>
                <Languages />
                AI 翻译
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem onClick={() => setRenameModal({ open: true, path: entry.path })}>
                <PencilLine />
                重命名
              </DropdownMenuItem>
              <DropdownMenuItem
                variant="destructive"
                onClick={async () => {
                  if (!projectId || !structure) return;
                  if (!confirm("确认删除该条目？此操作会移除所有语言下的该键")) return;
                  try {
                    const nextRoot = removeEntryAtPath(structure.root, entry.path);
                    await upsertStructure({ projectId, root: nextRoot });
                    await deleteEntryFromAllLanguages(projectId, entry.path);
                    setStructure({ projectId, root: nextRoot });
                    setValuesByLang((old) => {
                      const copy: typeof old = {};
                      for (const [langKey, vals] of Object.entries(old)) {
                        const rest = { ...vals } as Record<string, string>;
                        delete rest[entry.path];
                        copy[langKey] = rest;
                      }
                      return copy;
                    });
                  } catch (e) {
                    setPageError((e as Error)?.message ?? "删除失败");
                  }
                }}
              >
                <Trash2 />
                删除
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </td>
      </>
    );
  }, [displayedLanguages, inline, projectId, structure, setStructure, setValuesByLang, copy, selected]);

  useEffect(() => {
    if (!projectId || languages.length === 0) return;
    let cancelled = false;
    (async () => {
      const all: Record<string, Record<string, string>> = {};
      for (const lang of languages) {
        const rec = await getLanguageTranslations(projectId, lang);
        all[lang] = rec?.values ?? {};
      }
      if (!cancelled) setValuesByLang(all);
    })();
    return () => {
      cancelled = true;
    };
  }, [projectId, languages]);

  useEffect(() => {
    return () => {
      if (projectId) {
        clearAllConnections(projectId);
      }
    };
  }, [projectId]);

  return (
    <div className="h-screen flex flex-col">
      <header className="h-14 px-2 md:px-4 from-blue-400 to-cyan-400 bg-linear-to-r text-white">
        <div className="grid grid-cols-3 h-full items-center">
          <div className="flex items-center gap-2">
            <Button asChild size="icon" variant="ghost" aria-label="返回">
              <Link to="/">
                <ArrowLeft className="size-5" />
              </Link>
            </Button>
          </div>
          <div className="text-center font-medium truncate">
            {project?.name ?? "编辑器"}
          </div>
          <div className="flex items-center justify-end gap-2 text-foreground">
            <HeaderConnectionIndicator projectId={projectId ?? ""} />
            {!structure ? (
              <Button onClick={() => { setImportOpen(true); }}>构建翻译结构</Button>
            ) : (
              <Button variant="outline" onClick={() => { setImportOpen(true); }}>
                <Reply />
                导入
              </Button>
            )}
            {languages.length > 0 && (
              <Button variant="outline" onClick={() => setExportOpen(true)}>
                <Download />
                导出
              </Button>
            )}
            {projectId && (
              <Button
                variant="outline"
                onClick={handleSaveAllConnected}
                disabled={savingAll || !structure || Object.keys(connSnap.connections).length === 0}
                title={Object.keys(connSnap.connections).length === 0 ? "暂无已连接的语言" : "将所有已连接语言写入各自文件"}
              >
                <Save />
                {savingAll ? "保存中..." : "一键保存"}
              </Button>
            )}
            {project && (
              <Button variant="outline" onClick={() => setSettingsOpen(true)}>
                <Settings />
                设置
              </Button>
            )}
          </div>
        </div>
      </header>

      <main className="flex-1 overflow-auto p-4 md:p-6">
        {pageError && (
          <div className="mb-4 text-sm text-red-600" role="alert">{pageError}</div>
        )}

        {loading ? (
          <div className="text-sm text-muted-foreground">加载中...</div>
        ) : !structure ? (
          <div className="rounded-md border border-dashed p-6">
            <div className="text-sm text-muted-foreground">
              该项目还没有翻译结构。请导入一份 JSON 文件来构建结构，并同时录入该语言的翻译内容。
            </div>
            <div className="mt-3">
              <Button onClick={() => { setImportOpen(true); }}>构建翻译结构</Button>
            </div>
          </div>
        ) : (
          <div className="h-full flex flex-col">
            <form className="mb-3 flex items-center gap-2" onSubmit={scrollToQuery}>
              <Input placeholder="搜索翻译条目名称" value={query} onChange={(e) => setQuery(e.target.value)} />
              <Button
                variant={fullMatch ? "default" : "outline"}
                onClick={() => setFullMatch((v) => !v)}
                title="切换全量匹配/模糊匹配"
              >
                <Brackets />
                {fullMatch ? "全量匹配" : "模糊匹配"}
              </Button>
              <Button
                variant={caseSensitive ? "default" : "outline"}
                onClick={() => setCaseSensitive((v) => !v)}
                title="切换大小写敏感"
              >
                <CaseSensitive />
                {caseSensitive ? "区分大小写" : "忽略大小写"}
              </Button>
              <Button type="submit">
                <LocateFixed />
                定位
              </Button>
            </form>
            <div className="mb-2 flex items-center gap-2">
              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <Button variant="outline" title="过滤显示的语言列">
                    <Filter />
                    语言过滤
                  </Button>
                </DropdownMenuTrigger>
                <DropdownMenuContent align="start" className="w-48">
                  <DropdownMenuItem onClick={() => setVisibleLangs(new Set(languages))}>
                    全部显示
                  </DropdownMenuItem>
                  <DropdownMenuSeparator />
                  {languages.map((lang) => {
                    const checked = visibleLangs.has(lang);
                    return (
                      <DropdownMenuCheckboxItem
                        key={lang}
                        onSelect={(e) => e.preventDefault()}
                        checked={checked}
                        onCheckedChange={(v) => {
                          setVisibleLangs((prev) => {
                            const next = new Set(prev);
                            if (v) next.add(lang); else next.delete(lang);
                            return next;
                          });
                        }}
                      >
                        {lang}
                      </DropdownMenuCheckboxItem>
                    );
                  })}
                </DropdownMenuContent>
              </DropdownMenu>
              <Button
                variant="outline"
                disabled={selected.size === 0 || selected.size > MAX_AI_ITEMS}
                onClick={() => setAiBulkOpen(true)}
                title={selected.size > MAX_AI_ITEMS ? `最多支持 ${MAX_AI_ITEMS} 条` : "对所选条目进行 AI 翻译"}
              >
                <Languages />
                AI 翻译所选（{selected.size}）
              </Button>
              {selected.size > MAX_AI_ITEMS && (
                <span className="text-xs text-red-600">最多支持 {MAX_AI_ITEMS} 条</span>
              )}
              <Button
                variant="outline-destructive"
                disabled={selected.size === 0}
                onClick={async () => {
                  if (!projectId || !structure) return;
                  if (selected.size === 0) return;
                  if (!confirm(`确认删除所选 ${selected.size} 个条目？此操作会移除所有语言下的这些键`)) return;
                  try {
                    let nextRoot = structure.root;
                    for (const p of selected) {
                      nextRoot = removeEntryAtPath(nextRoot, p);
                    }
                    await upsertStructure({ projectId, root: nextRoot });
                    // 从所有语言删除这些键
                    await Promise.all(Array.from(selected).map((p) => deleteEntryFromAllLanguages(projectId, p)));
                    setStructure({ projectId, root: nextRoot });
                    setValuesByLang((old) => {
                      const copy: typeof old = {};
                      for (const [langKey, vals] of Object.entries(old)) {
                        const next = { ...vals } as Record<string, string>;
                        for (const p of selected) delete next[p];
                        copy[langKey] = next;
                      }
                      return copy;
                    });
                    setSelected(new Set());
                  } catch (e) {
                    setPageError((e as Error)?.message ?? "批量删除失败");
                  }
                }}
                title="删除所选条目"
              >
                <Trash2 />
                删除所选
              </Button>
            </div>
            <div className="flex-1 border rounded-md">
              <TableVirtuoso
                ref={virtuosoRef}
                data={entries}
                fixedHeaderContent={headerContent}
                itemContent={renderItemContent}
                components={virtuosoComponents}
                computeItemKey={(_index, entry) => entry.path}
                scrollerRef={(el) => { scrollerRootRef.current = el; }}
              />
            </div>
          </div>
        )}
      </main>

      <ImportLanguageModal
        open={importOpen}
        onOpenChange={setImportOpen}
        projectId={projectId ?? ""}
        hasStructure={!!structure}
        onImported={refresh}
        languages={languages}
      />
      <ExportLanguageModal
        open={exportOpen}
        onOpenChange={setExportOpen}
        projectId={projectId ?? ""}
        languages={languages}
        valuesByLang={valuesByLang}
        orderedPaths={entries.map((e) => e.path)}
      />

      <ProjectSettingsModal
        open={settingsOpen}
        onOpenChange={setSettingsOpen}
        project={project}
        onSave={async (update) => {
          if (!projectId) return;
          const next = await updateProject({ id: projectId, name: update.name, preferences: update.preferences });
          setProject(next);
        }}
        onDelete={async () => {
          if (!projectId) return;
          await deleteProjectDeep(projectId);
          navigate("/");
        }}
      />

      <AiTranslateModal
        open={!!aiModal?.open}
        onOpenChange={(v) => setAiModal((cur) => (cur ? { ...cur, open: v } : cur))}
        languages={languages}
        paths={aiModal?.path ? [aiModal.path] : []}
        initialSelectedLanguages={aiModal?.path ? computeSuggestedLanguages([aiModal.path]) : []}
        getExistingByPath={getExistingByPath}
        prompt={project?.preferences?.aiPrompt}
        model={project?.preferences?.aiModel}
        onConfirm={async (translations, options) => {
          if (!projectId || !aiModal) return;
          const updates: Record<string, Record<string, string>> = {};
          try {
            const targetLangs = options?.selectedLanguages ?? languages;
            await Promise.all(
              targetLangs.map(async (lang) => {
                const prev = valuesByLang[lang] ?? {};
                const next = { ...prev } as Record<string, string>;
                const langMap = translations[lang] || {};
                for (const [k, v] of Object.entries(langMap)) {
                  const existing = prev[k];
                  const isFilled = typeof existing === "string" && existing.trim() !== "";
                  if (!options?.overwrite && isFilled) continue; // 仅填充缺失
                  next[k] = v as string;
                }
                updates[lang] = next;
                await upsertLanguageTranslations(projectId, lang, next);
              })
            );
            setValuesByLang((old) => ({ ...old, ...updates }));
          } catch (e) {
            const msg = (e as Error)?.message ?? "保存翻译失败";
            setPageError(msg);
            throw e;
          }
        }}
      />

      <AiTranslateModal
        open={aiBulkOpen}
        onOpenChange={setAiBulkOpen}
        languages={languages}
        paths={Array.from(selected)}
        initialSelectedLanguages={computeSuggestedLanguages(Array.from(selected))}
        getExistingByPath={getExistingByPath}
        prompt={project?.preferences?.aiPrompt}
        model={project?.preferences?.aiModel}
        onConfirm={async (translations, options) => {
          if (!projectId || selected.size === 0) return;
          try {
            const updatesByLang: Record<string, Record<string, string>> = {};
            const targetLangs = options?.selectedLanguages ?? languages;
            await Promise.all(
              targetLangs.map(async (lang) => {
                const prev = valuesByLang[lang] ?? {};
                const next = { ...prev } as Record<string, string>;
                const langMap = translations[lang] || {};
                for (const [k, v] of Object.entries(langMap)) {
                  const existing = prev[k];
                  const isFilled = typeof existing === "string" && existing.trim() !== "";
                  if (!options?.overwrite && isFilled) continue; // 仅填充缺失
                  next[k] = v as string;
                }
                updatesByLang[lang] = next;
                await upsertLanguageTranslations(projectId, lang, next);
              })
            );
            setValuesByLang((old) => ({ ...old, ...updatesByLang }));
          } catch (e) {
            setPageError((e as Error)?.message ?? "批量 AI 翻译保存失败");
            throw e;
          }
        }}
      />

      <EntryNameModal
        open={!!addModal?.open}
        onOpenChange={(v) => setAddModal((cur) => (cur ? { ...cur, open: v } : cur))}
        title={addModal?.position === "above" ? "在上方新增条目" : "在下方新增条目"}
        placeholder="请输入条目名称（不含点）"
        onConfirm={async (name) => {
          if (!projectId || !structure || !addModal) return;
          const nextRoot = insertEntrySibling(structure.root, addModal.path, addModal.position, name);
          console.log("nextRoot", addModal, nextRoot);

          await upsertStructure({ projectId, root: nextRoot });
          setStructure({ projectId, root: nextRoot });
        }}
        validate={(name) => {
          // 同级重名校验在结构函数中也会做，这里做基础提示即可
          if (name.includes('.')) return "名称不能包含 '.'";
          return null;
        }}
      />

      <EntryNameModal
        open={!!renameModal?.open}
        onOpenChange={(v) => setRenameModal((cur) => (cur ? { ...cur, open: v } : cur))}
        title="重命名条目"
        placeholder="请输入新名称（不含点）"
        defaultValue={renameModal?.path ? renameModal.path.split('.').pop() : ''}
        onConfirm={async (newName) => {
          if (!projectId || !structure || !renameModal) return;
          const { root: nextRoot, newPath } = renameEntryAtPath(structure.root, renameModal.path, newName);
          await upsertStructure({ projectId, root: nextRoot });
          await renameEntryInAllLanguages(projectId, renameModal.path, newPath);
          setStructure({ projectId, root: nextRoot });
          setValuesByLang((old) => {
            const copy: typeof old = {};
            for (const [langKey, vals] of Object.entries(old)) {
              if (Object.prototype.hasOwnProperty.call(vals, renameModal.path)) {
                const { [renameModal.path]: val, ...rest } = vals;
                copy[langKey] = { ...rest, [newPath]: val };
              } else {
                copy[langKey] = vals;
              }
            }
            return copy;
          });
        }}
        validate={(name) => {
          if (name.includes('.')) return "名称不能包含 '.'";
          return null;
        }}
      />
    </div>
  );
}
