<script setup lang="ts">
import { onMounted, onUnmounted, ref, computed, nextTick, watch } from 'vue';
import { usePromptStore } from '../stores/promptStore';
import type { LangCode } from '../types';
import NotificationToast from './NotificationToast.vue';
import PresetDropdown from './PresetDropdown.vue';

const store = usePromptStore();
const draggingIndex = ref<number | null>(null);
const overIndex = ref<number | null>(null);
const dragPreview = ref<HTMLElement | null>(null);
const isDragging = ref(false);
// 指针拖拽新增状态
const insertSide = ref<'before' | 'after' | null>(null);
const pointerId = ref<number | null>(null);
const startX = ref(0);
const startY = ref(0);
const lastX = ref(0);
const lastY = ref(0);
const dragStarted = ref(false);
const DRAG_THRESHOLD = 3; // 像素阈值，避免误触
const editingIndex = ref<number | null>(null);
const editingValue = ref('');
const addingMapIndex = ref<number | null>(null);
const addingMapValue = ref('');
const presetName = ref('');
const viewMode = ref<'compact' | 'detail'>('compact');
const showPresetDropdown = ref(false);
const notification = ref<{ message: string; type: 'success' | 'error' | 'info'; show: boolean }>({ 
  message: '', 
  type: 'info', 
  show: false 
});

function showNotification(message: string, type: 'success' | 'error' | 'info' = 'info') {
  notification.value = { message, type, show: true };
  setTimeout(() => {
    notification.value.show = false;
  }, 3000);
}

// 点击外部关闭下拉菜单
function handleClickOutside(event: Event) {
  const target = event.target as HTMLElement;
  // 检查点击是否在预设下拉区域内，包括重命名输入框和按钮
  if (!target.closest('.pe-presets') && !target.closest('.pd-dropdown')) {
    showPresetDropdown.value = false;
  }
}

onMounted(() => {
  document.addEventListener('click', handleClickOutside);
});

// 清理事件监听器
onUnmounted(() => {
  document.removeEventListener('click', handleClickOutside);
});

const selectedLang = computed({
  get: () => store.selectedLang,
  set: (v: LangCode) => store.setLanguage(v),
});

const tokens = computed(() => store.tokens);

const suggestions = ref<string[]>([]);
const editSuggestions = ref<string[]>([]);
const inputEl = ref<HTMLTextAreaElement | null>(null);
// 注意：ref 在 v-for 中可能成为数组，这里做统一归一化处理
const editEl = ref<HTMLInputElement | HTMLInputElement[] | null>(null);

function currentEditEl(): HTMLInputElement | null {
  const raw = editEl.value as any;
  if (!raw) return null;
  return Array.isArray(raw) ? (raw[0] ?? null) : raw;
}
const priorityStyle = ref<'{}' | '()' | '[]' | '<>' | 'suffix'>('{}');
const priorityStep = ref(1);
function splitTokensLocal(txt: string): string[] {
  return txt.split(/[，,]/).map(s => s.trim()).filter(s => s.length > 0);
}
function normalizeToken(t: string): string { return t.trim(); }
function normalizePromptLocal(txt: string): string { return splitTokensLocal(txt).join(', '); }
function applyFullPrompt(newText: string) {
  const el = inputEl.value;
  if (!el) { text.value = newText; return; }
  el.focus();
  applyTextReplacement(el, 0, text.value.length, newText);
}
function getTokenWeight(token: string): number {
  const { core } = store.parseTokenWrappers(token);
  const idx = core.lastIndexOf(':');
  if (idx > -1) {
    const w = parseFloat(core.slice(idx + 1).trim());
    return isNaN(w) ? 1 : w;
  }
  return 1;
}
function hasWeightSuffix(token: string): boolean {
  const { core } = store.parseTokenWrappers(token);
  return /:\s*\d+(?:\.\d+)?$/.test(core);
}
function roundToDecimals(v: number, decimals: number): number {
  const m = Math.pow(10, decimals);
  return Math.round(v * m) / m;
}
function adjustWeight(core: string, delta: number): string {
  const idx = core.lastIndexOf(':');
  let base = core;
  let w: number | null = null;
  if (idx > -1) {
    const num = parseFloat(core.slice(idx + 1).trim());
    if (!isNaN(num)) { base = core.slice(0, idx); w = num; }
  }
  const stepStr = String(priorityStep.value);
  const decimals = stepStr.includes('.') ? stepStr.split('.')[1]!.length : 0;
  const cur = w == null ? 1.0 : w;
  let nw = cur + delta;
  if (delta < 0 && nw <= 1.0) return base;
  nw = Math.min(2.0, Math.max(0.1, nw));
  nw = roundToDecimals(nw, decimals);
  return base + ':' + nw;
}
const text = ref('');

watch(text, (val) => {
  store.setPromptTextRaw(val);
  updateSuggestions();
});

// 当 store.promptText 发生变化（例如点击右侧预设加载）时，主动同步到左侧输入
watch(() => store.promptText, (v) => {
  if (text.value !== v) text.value = v;
}, { immediate: true });

function updateSuggestions() {
  const el = inputEl.value;
  const txt = store.promptText;
  let pos = txt.length;
  if (el && typeof el.selectionStart === 'number') {
    pos = el.selectionStart ?? txt.length;
  }
  // 计算当前光标所在片段：左右最近的逗号之间
  const leftCommaEn = txt.lastIndexOf(',', pos - 1);
  const leftCommaCn = txt.lastIndexOf('，', pos - 1);
  const left = Math.max(leftCommaEn, leftCommaCn);
  const rightCommaEn = txt.indexOf(',', pos);
  const rightCommaCn = txt.indexOf('，', pos);
  const rightCandidates = [rightCommaEn, rightCommaCn].filter(i => i !== -1);
  const right = rightCandidates.length ? Math.min(...rightCandidates) : txt.length;
  const segment = txt.slice(left < 0 ? 0 : left + 1, right).trim();
  suggestions.value = store.getSuggestions(segment, 8);
}

function updateEditSuggestions() {
  const el = currentEditEl();
  const val = editingValue.value || '';
  let pos = val.length;
  if (el && typeof el.selectionStart === 'number') {
    pos = el.selectionStart ?? val.length;
  }
  // 对编辑输入，使用光标左侧内容作为前缀
  const before = val.slice(0, pos);
  const match = before.match(/[^，,]*$/);
  const prefix = (match ? match[0] : before).trim();
  editSuggestions.value = store.getSuggestions(prefix, 8);
}

// 计算左侧输入（textarea）基于光标位置的片段替换范围（修剪前后空格）
function getTextSegmentBounds(txt: string, pos: number) {
  const leftCommaEn = txt.lastIndexOf(',', pos - 1);
  const leftCommaCn = txt.lastIndexOf('，', pos - 1);
  const left = Math.max(leftCommaEn, leftCommaCn);
  const rightCommaEn = txt.indexOf(',', pos);
  const rightCommaCn = txt.indexOf('，', pos);
  const rightCandidates = [rightCommaEn, rightCommaCn].filter(i => i !== -1);
  const right = rightCandidates.length ? Math.min(...rightCandidates) : txt.length;
  let start = left < 0 ? 0 : left + 1;
  let end = right;
  while (start < end && txt[start] && /\s/.test(txt[start]!)) start++;
  while (end > start && txt[end - 1] && /\s/.test(txt[end - 1]!)) end--;
  return { start, end };
}

// 统一的文本替换方法：优先使用原生插入以保留撤回栈，失败时回退
function applyTextReplacement(
  el: HTMLTextAreaElement | HTMLInputElement,
  start: number,
  end: number,
  text: string,
) {
  try {
    if (typeof el.setSelectionRange === 'function') {
      el.setSelectionRange(start, end);
    }
    const ok = (document as any).execCommand && (document as any).execCommand('insertText', false, text);
    if (ok) return;
  } catch {}
  try {
    el.setRangeText(text, start, end, 'end');
    try {
      const ie = new (window as any).InputEvent('input', { bubbles: true, data: text, inputType: 'insertReplacementText' });
      el.dispatchEvent(ie);
    } catch {
      el.dispatchEvent(new Event('input', { bubbles: true }));
    }
  } catch {
    const value = (el as any).value as string;
    (el as any).value = value.slice(0, start) + text + value.slice(end);
    el.dispatchEvent(new Event('input', { bubbles: true }));
  }
}

async function onKeyDown(e: KeyboardEvent) {
  if (e.key === 'Tab') {
    // 在光标位置进行补全，不影响撤回
    const el = inputEl.value;
    if (!el) return;
    const pos = el.selectionStart ?? store.promptText.length;
    const before = store.promptText.slice(0, pos);
    const match = before.match(/[^，,]*$/);
    const prefix = (match ? match[0] : '').trim();
  const { start, end } = getTextSegmentBounds(store.promptText, pos);
  const list = store.getSuggestions(prefix, 8);
  if (list.length > 0) {
    e.preventDefault();
    const s = list[0];
    if (!s) return;
    // 使用原生插入或回退方案，确保撤回可用
    applyTextReplacement(el, start, end, s);
    await nextTick();
    updateSuggestions();
  }
}
}

async function copyLeft() { 
  try {
    await navigator.clipboard.writeText(store.promptText);
    showNotification('提示词已复制到剪贴板', 'success');
  } catch (error) {
    showNotification('复制失败，请手动复制', 'error');
  }
}
function replaceCnComma() { applyFullPrompt(text.value.replace(/，/g, ',')); }
function formatPrompt() { applyFullPrompt(normalizePromptLocal(text.value)); }

// 新增功能方法
function toggleUnderscoreSpace() { 
  const tokens = splitTokensLocal(text.value);
  const newTokens = tokens.map(token => {
    const { core, wrappers } = store.parseTokenWrappers(token);
    let newCore;
    if (core.includes('_')) {
      newCore = core.replace(/_/g, ' ');
    } else if (core.includes(' ')) {
      newCore = core.replace(/ /g, '_');
    } else {
      newCore = core;
    }
    return store.wrapToken(newCore, wrappers);
  });
  applyFullPrompt(newTokens.join(', ')); 
  showNotification('已切换下划线/空格格式', 'success');
}

function addWrapperToToken(index: number) { 
  const tokens = splitTokensLocal(text.value); 
  if (index < 0 || index >= tokens.length) return;
  const token = tokens[index]!;
  const parsed = store.parseTokenWrappers(token);
  const core = parsed?.core ?? token;
  const wrappers = parsed?.wrappers ?? [];
  if (priorityStyle.value === 'suffix') {
    const newCore = adjustWeight(core, +priorityStep.value);
    tokens[index] = store.wrapToken(newCore, wrappers);
  } else {
    const newWrappers = [...wrappers, priorityStyle.value];
    tokens[index] = store.wrapToken(core, newWrappers);
  }
  applyFullPrompt(tokens.join(', ')); 
  showNotification('已添加优先级', 'success');
}

function removeWrapperFromToken(index: number) { 
  const tokens = splitTokensLocal(text.value); 
  if (index < 0 || index >= tokens.length) return;
  const token = tokens[index]!;
  const { core, wrappers } = store.parseTokenWrappers(token);
  if (priorityStyle.value === 'suffix') {
    const newCore = adjustWeight(core, -priorityStep.value);
    tokens[index] = store.wrapToken(newCore, wrappers);
  } else if (wrappers.length > 0) {
    const newWrappers = wrappers.slice(0, -1);
    tokens[index] = store.wrapToken(core, newWrappers);
  }
  applyFullPrompt(tokens.join(', ')); 
  showNotification('已调整优先级', 'success');
}

function getTokenWrapperInfo(token: string) {
  return store.getTokenWrapperInfo(token);
}

// 指针事件版拖拽：更高性能且可自定义插入指示
function onPointerDown(index: number, e: PointerEvent) {
  if (editingIndex.value === index) return;
  draggingIndex.value = index;
  pointerId.value = e.pointerId;
  startX.value = e.clientX;
  startY.value = e.clientY;
  lastX.value = e.clientX;
  lastY.value = e.clientY;
  dragStarted.value = false;
  isDragging.value = false;
  insertSide.value = null;

  // 监听全局移动与抬起
  window.addEventListener('pointermove', handlePointerMove);
  window.addEventListener('pointerup', handlePointerUp, { once: true });
}

function handlePointerMove(e: PointerEvent) {
  lastX.value = e.clientX; 
  lastY.value = e.clientY;
  const dx = e.clientX - startX.value;
  const dy = e.clientY - startY.value;
  if (!dragStarted.value && Math.hypot(dx, dy) > DRAG_THRESHOLD) {
    dragStarted.value = true;
    isDragging.value = true;
    if (draggingIndex.value != null) createPointerPreview(draggingIndex.value);
  }
  if (!isDragging.value) return;
  positionPreview(e.clientX, e.clientY);
  updateOverIndexAndSide(e.clientX, e.clientY);
}

function handlePointerUp(e: PointerEvent) {
  window.removeEventListener('pointermove', handlePointerMove);
  if (!dragStarted.value || draggingIndex.value == null) { 
    cleanupDrag(); 
    return; 
  }
  const from = draggingIndex.value!;
  const j = overIndex.value;
  const side = insertSide.value;
  if (j != null && side) {
    let to = j;
    if (side === 'before') {
      to = j - (from < j ? 1 : 0);
    } else {
      to = j + (from > j ? 1 : 0);
    }
    const list = splitTokensLocal(text.value);
    if (to < 0) to = 0;
    if (to >= list.length) to = list.length - 1;
    const [item] = list.splice(from, 1);
    if (item != null) list.splice(to, 0, item);
    applyFullPrompt(list.join(', '));
    showNotification('已重新排序', 'success');
  }
  cleanupDrag();
}

function cleanupDrag() {
  draggingIndex.value = null;
  overIndex.value = null;
  isDragging.value = false;
  insertSide.value = null;
  pointerId.value = null;
  if (dragPreview.value) {
    document.body.removeChild(dragPreview.value);
    dragPreview.value = null;
  }
}

function createPointerPreview(index: number) {
  const token = tokens.value[index] || '';
  const translation = displayTrans(token);
  const preview = document.createElement('div');
  preview.className = 'drag-preview';
  preview.innerHTML = `
    <div class="drag-preview-content">
      <span class="drag-preview-key">${token}</span>
      <span class="drag-preview-arrow">→</span>
      <span class="drag-preview-trans">${translation}</span>
    </div>
  `;
  preview.style.position = 'fixed';
  preview.style.top = '0';
  preview.style.left = '0';
  preview.style.zIndex = '1000';
  preview.style.pointerEvents = 'none';
  ;(preview.style as any).contain = 'layout style paint';
  preview.style.willChange = 'transform, opacity';
  document.body.appendChild(preview);
  dragPreview.value = preview;
}

function positionPreview(x: number, y: number) {
  if (!dragPreview.value) return;
  dragPreview.value.style.transform = `translate(${x + 12}px, ${y + 12}px)`;
}

function updateOverIndexAndSide(x: number, y: number) {
  insertSide.value = null;
  overIndex.value = null;
  const el = document.elementFromPoint(x, y) as HTMLElement | null;
  if (!el) return;
  const tokenEl = el.closest('.pe-token-compact, .pe-token-detail') as HTMLElement | null;
  if (!tokenEl) return;
  const idxAttr = tokenEl.getAttribute('data-index');
  if (idxAttr == null) return;
  const idx = parseInt(idxAttr, 10);
  if (Number.isNaN(idx)) return;
  if (idx === draggingIndex.value) { overIndex.value = null; insertSide.value = null; return; }
  const rect = tokenEl.getBoundingClientRect();
  const midX = rect.left + rect.width / 2;
  overIndex.value = idx;
  insertSide.value = x < midX ? 'before' : 'after';
}

function beginEdit(i: number) {
  editingIndex.value = i;
  editingValue.value = tokens.value[i] ?? '';
  addingMapIndex.value = null;
  nextTick(() => {
    const el = currentEditEl();
    if (el) {
      el.focus();
      try { el.setSelectionRange(0, editingValue.value.length); } catch {}
    }
  });
}
function commitEdit() {
  if (editingIndex.value == null) return;
  const tokens = splitTokensLocal(text.value);
  const i = editingIndex.value!;
  if (i >= 0 && i < tokens.length) {
    tokens[i] = normalizeToken(editingValue.value);
    applyFullPrompt(tokens.join(', '));
  }
  editingIndex.value = null;
}
function cancelEdit() { editingIndex.value = null; }

function showAddMap(i: number) {
  addingMapIndex.value = i; addingMapValue.value = '';
  editingIndex.value = null;
}
function commitAddMap() {
  if (addingMapIndex.value == null) return;
  const key = tokens.value[addingMapIndex.value];
  if (!key) return;
  store.addMapping(key, selectedLang.value, addingMapValue.value.trim());
  addingMapIndex.value = null; addingMapValue.value = '';
}

function removeToken(i: number) {
  const tokens = splitTokensLocal(text.value);
  if (i < 0 || i >= tokens.length) return;
  tokens.splice(i, 1);
  applyFullPrompt(tokens.join(', '));
}
function addTokenAfter(i: number) {
  const tokens = splitTokensLocal(text.value);
  tokens.splice(i + 1, 0, normalizeToken('new_token'));
  applyFullPrompt(tokens.join(', '));
}

function savePreset() {
  if (!presetName.value.trim()) { 
    showNotification('请输入预设名称', 'error'); 
    return; 
  }
  
  const name = presetName.value.trim();
  
  // 只保存到新的扩展预设系统
  const defaultFolder = store.presetManagement?.settings?.defaultFolder;
  store.createExtendedPreset({
    name: name,
    type: 'positive',
    content: store.promptText,
    description: '从编辑器快速保存',
    folderId: defaultFolder
  });
  
  showNotification(`预设「${name}」已保存到预设管理`, 'success');
  presetName.value = '';
}

// 预设下拉组件的事件处理
function handlePresetLoad(name: string) {
  // 优先从扩展预设中查找
  const extendedPreset = store.extendedPresets.find(p => p.name === name);
  if (extendedPreset) {
    store.setPromptTextRaw(extendedPreset.content);
    text.value = extendedPreset.content;
  } else {
    // 回退到旧预设系统
    store.loadPreset(name);
    text.value = store.promptText;
  }
  showNotification(`已加载预设「${name}」`, 'success');
}

function handlePresetSave(name: string) {
  store.savePreset(name);
  showNotification(`预设「${name}」已保存`, 'success');
}

function handlePresetDelete(name: string) {
  store.deletePreset(name);
  showNotification(`预设「${name}」已删除`, 'info');
}

function handlePresetRename(oldName: string, newName: string) {
  store.renamePreset(oldName, newName);
  showNotification(`预设已重命名为「${newName}」`, 'success');
}

async function applySuggestion(s: string) {
  const el = inputEl.value;
  if (!el) return;
  // 确保输入框获得焦点，避免点击建议导致焦点丢失
  el.focus();
  const pos = el.selectionStart ?? store.promptText.length;
  const { start, end } = getTextSegmentBounds(store.promptText, pos);
  // 使用原生插入或回退方式替换片段，确保撤回可用
  applyTextReplacement(el, start, end, s);
  await nextTick();
  updateSuggestions();
}

function onEditKeyDown(e: KeyboardEvent) {
  if (e.key !== 'Tab') return;
  const el = currentEditEl();
  if (!el) return;
  const val = editingValue.value || '';
  const pos = el.selectionStart ?? val.length;
  const before = val.slice(0, pos);
  const match = before.match(/[^，,]*$/);
  const prefix = (match ? match[0] : '').trim();
  const list = store.getSuggestions(prefix, 8);
  if (list.length > 0) {
    e.preventDefault();
    const s = list[0];
    if (s) applyTextReplacement(el, 0, val.length, s);
    updateEditSuggestions();
  }
}

function applyEditSuggestion(s: string) {
  const el = currentEditEl();
  if (!el) return;
  // 保持焦点在编辑输入上
  el.focus();
  const val = editingValue.value || '';
  // 直接替换整个输入为建议，保证撤回可用
  applyTextReplacement(el, 0, val.length, s);
  updateEditSuggestions();
}

function displayTrans(key: string): string {
  const { core, wrappers } = store.parseTokenWrappers(key);
  const m = core.match(/:(\d+(?:\.\d+)?)$/);
  const base = m ? core.slice(0, core.lastIndexOf(':')) : core;
  const suffix = m ? ':' + m[1]! : '';
  const tag = store.getTagByKey(base);
  const translatedCore = tag?.translation?.[selectedLang.value] ?? tag?.key ?? base;
  return store.wrapToken(translatedCore + suffix, wrappers);
}

function isRemoveDisabled(token: string): boolean {
  const info = getTokenWrapperInfo(token);
  return info.wrapperCount === 0 && !hasWeightSuffix(token);
}
</script>

<template>
  <div class="pe-root">
    <header class="pe-toolbar">
      <div class="pe-left">
        <label>语言</label>
        <select v-model="selectedLang">
          <option v-for="l in store.languages" :key="l" :value="l">{{ l }}</option>
        </select>
        <button @click="copyLeft" title="复制提示词到剪贴板">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
            <rect x="9" y="9" width="13" height="13" rx="2" ry="2" stroke="currentColor" stroke-width="2"/>
            <path d="m5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" stroke="currentColor" stroke-width="2"/>
          </svg>
          复制提示词
        </button>
      </div>
      <div class="pe-right">
        <input class="pe-preset-name" placeholder="保存为预设名称" v-model="presetName" />
        <button @click="savePreset" title="保存当前提示词为预设">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" stroke="currentColor" stroke-width="2"/>
            <polyline points="17,21 17,13 7,13 7,21" stroke="currentColor" stroke-width="2"/>
            <polyline points="7,3 7,8 15,8" stroke="currentColor" stroke-width="2"/>
          </svg>
          保存预设
        </button>
        <div class="pe-presets">
          <button 
            class="pe-preset-toggle" 
            @click="showPresetDropdown = !showPresetDropdown"
            title="快速预设"
          >
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
              <path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z" fill="currentColor"/>
            </svg>
            快速预设
            <svg 
              width="12" height="12" 
              viewBox="0 0 24 24" 
              fill="none" 
              xmlns="http://www.w3.org/2000/svg"
              :class="{ 'rotate-180': showPresetDropdown }"
              class="dropdown-arrow"
            >
              <polyline points="6,9 12,15 18,9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
          </button>
          
          <PresetDropdown 
            :show="showPresetDropdown"
            @close="showPresetDropdown = false"
            @load="handlePresetLoad"
            @save="handlePresetSave"
            @delete="handlePresetDelete"
            @rename="handlePresetRename"
          />
        </div>
      </div>
    </header>

    <main class="pe-main">
      <section class="pe-left-pane">
        <div class="pe-section-title">提示词输入（逗号分隔）</div>
        <textarea 
          ref="inputEl" 
          class="pe-input" 
          v-model="text" 
          @keydown="onKeyDown" 
          @click="updateSuggestions" 
          @keyup="updateSuggestions" 
          placeholder="例如：1girl, aaa, bbb, ccc"
        ></textarea>
        <div class="pe-input-actions">
          <button @click="replaceCnComma" title="将中文逗号替换为英文逗号">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
              <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke="currentColor" stroke-width="2"/>
              <polyline points="14,2 14,8 20,8" stroke="currentColor" stroke-width="2"/>
              <line x1="16" y1="13" x2="8" y2="13" stroke="currentColor" stroke-width="2"/>
              <line x1="16" y1="17" x2="8" y2="17" stroke="currentColor" stroke-width="2"/>
            </svg>
            替换中文逗号
          </button>
          <button @click="formatPrompt" title="格式化提示词为标准格式">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
              <polyline points="4 7 4 4 20 4 20 7" stroke="currentColor" stroke-width="2"/>
              <line x1="9" y1="20" x2="15" y2="20" stroke="currentColor" stroke-width="2"/>
              <line x1="12" y1="4" x2="12" y2="20" stroke="currentColor" stroke-width="2"/>
            </svg>
            格式化提示词
          </button>
          <button @click="toggleUnderscoreSpace" title="切换下划线和空格格式">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
              <path d="M3 12h18" stroke="currentColor" stroke-width="2"/>
              <path d="M8 8l4-4 4 4" stroke="currentColor" stroke-width="2"/>
              <path d="M8 16l4 4 4-4" stroke="currentColor" stroke-width="2"/>
            </svg>
            切换 _/空格
          </button>
          <div class="pe-priority-group">
            <label class="pe-priority-label">优先级样式</label>
            <select class="pe-priority-select" v-model="priorityStyle" title="选择新增优先级的样式">
              <option value="{}">{}</option>
              <option value="()">()</option>
              <option value="[]">[]</option>
              <option value="<>">&lt;&gt;</option>
              <option value="suffix">后缀数字</option>
            </select>
            <label class="pe-priority-label">后缀数字间隔</label>
            <input 
              type="number" 
              class="pe-priority-step" 
              v-model.number="priorityStep" 
              title="设置增减间隔" 
              min="0.01" 
              step="0.01" 
              placeholder="1" 
            />
          </div>
        </div>
        <ul class="pe-suggest" v-if="suggestions.length">
          <li 
            v-for="s in suggestions" 
            :key="s" 
            @mousedown.prevent 
            @click="applySuggestion(s)"
          >{{ s }}</li>
        </ul>
      </section>
      
      <section class="pe-right-pane">
        <div class="pe-section-title mode">
          <span>提示词映射（双击修改）</span>
          <div class="pe-mode-switch">
            <button :class="{ active: viewMode==='compact' }" @click="viewMode='compact'">精简视图</button>
            <button :class="{ active: viewMode==='detail' }" @click="viewMode='detail'">详细视图</button>
          </div>
        </div>
        
        <div class="pe-drag-container" :class="{ 'is-dragging': isDragging }">
        <div class="pe-tokens-compact" v-if="viewMode === 'compact'">
          <div
            v-for="(k,i) in tokens"
            :key="k + '_' + i"
            :data-index="i"
            :class="{ 
              'dragging': draggingIndex === i, 
              'insert-before': overIndex === i && insertSide === 'before' && draggingIndex !== i,
              'insert-after': overIndex === i && insertSide === 'after' && draggingIndex !== i,
              'editing': editingIndex === i
            }"
            class="pe-token-compact"
            @pointerdown="onPointerDown(i, $event)"
            @dblclick="beginEdit(i)"
            :title="`${k} → ${displayTrans(k)}`"
          >
            <span class="pe-handle-compact">⋮⋮</span>
            <div v-if="editingIndex === i" class="pe-edit-inline">
              <input
                ref="editEl"
                class="pe-edit-input"
                v-model="editingValue"
                @keydown="onEditKeyDown"
                @keydown.enter.stop.prevent="commitEdit"
                @keydown.esc.stop.prevent="cancelEdit"
                @click="updateEditSuggestions"
                @keyup="updateEditSuggestions"
                placeholder="编辑提示词"
              />
              <ul class="pe-edit-suggest" v-if="editSuggestions.length">
                <li 
                  v-for="s in editSuggestions" 
                  :key="'e_'+s" 
                  @mousedown.prevent 
                  @click="applyEditSuggestion(s)"
                >{{ s }}</li>
              </ul>
              <div class="pe-edit-actions">
                <button @click="commitEdit" class="pe-edit-save-btn" title="保存">
                  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <polyline points="20 6 9 17 4 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                  </svg>
                </button>
                <button @click="cancelEdit" class="pe-edit-cancel-btn" title="取消">
                  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <line x1="18" y1="6" x2="6" y2="18" stroke="currentColor" stroke-width="2"/>
                    <line x1="6" y1="6" x2="18" y2="18" stroke="currentColor" stroke-width="2"/>
                  </svg>
                </button>
              </div>
            </div>
            <div v-else class="pe-token-content">
              <span class="pe-key-compact">{{ k }}</span>
              <span class="pe-arrow-compact">→</span>
              <span class="pe-trans-compact" :class="{ unmapped: displayTrans(k) === k }">
                {{ displayTrans(k) }}
              </span>
            </div>
            <div class="pe-token-controls-compact">
              <button @click="addWrapperToToken(i)" class="pe-add-wrapper-btn" :title="`添加优先级（样式：${priorityStyle}）`">
                <svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
                  <line x1="12" y1="8" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
                  <line x1="8" y1="12" x2="16" y2="12" stroke="currentColor" stroke-width="2"/>
                </svg>
              </button>
              <button 
                @click="removeWrapperFromToken(i)" 
                class="pe-remove-wrapper-btn" 
                title="移除优先级"
                :disabled="isRemoveDisabled(k)"
              >
                <svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
                  <line x1="8" y1="12" x2="16" y2="12" stroke="currentColor" stroke-width="2"/>
                </svg>
              </button>
              <button @click="removeToken(i)" class="pe-remove-btn" title="删除此词">
                <svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <polyline points="3,6 5,6 21,6" stroke="currentColor" stroke-width="2"/>
                  <path d="m19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" stroke="currentColor" stroke-width="2"/>
                </svg>
              </button>
            </div>
          </div>
        </div>

        <div class="pe-tokens-detail" v-else>
          <div
            v-for="(k,i) in tokens"
            :key="k + '_' + i"
            :data-index="i"
            :class="{ 
              'dragging': draggingIndex === i, 
              'insert-before': overIndex === i && insertSide === 'before' && draggingIndex !== i,
              'insert-after': overIndex === i && insertSide === 'after' && draggingIndex !== i,
              'editing': editingIndex === i || addingMapIndex === i
            }"
            class="pe-token-detail"
            @pointerdown="onPointerDown(i, $event)"
          >
            <div class="pe-token-header">
              <span class="pe-handle-detail">⋮⋮</span>
              <div class="pe-token-main" @dblclick="beginEdit(i)">
                <span class="pe-key-detail">{{ k }}</span>
                <span class="pe-arrow-detail">→</span>
                <span class="pe-trans-detail" :class="{ unmapped: displayTrans(k) === k }">{{ displayTrans(k) }}</span>
              </div>
              <div class="pe-token-controls">
                <button v-if="displayTrans(k) === k" class="pe-add-map-btn" @click="showAddMap(i)" title="添加映射">
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <line x1="12" y1="5" x2="12" y2="19" stroke="currentColor" stroke-width="2"/>
                    <line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2"/>
                  </svg>
                </button>
                <button @click="addWrapperToToken(i)" class="pe-add-wrapper-detail-btn" :title="`添加优先级（样式：${priorityStyle}）`">
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
                    <line x1="12" y1="8" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
                    <line x1="8" y1="12" x2="16" y2="12" stroke="currentColor" stroke-width="2"/>
                  </svg>
                </button>
                <button 
                  @click="removeWrapperFromToken(i)" 
                  class="pe-remove-wrapper-detail-btn" 
                  title="移除优先级"
                  :disabled="isRemoveDisabled(k)"
                >
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
                    <line x1="8" y1="12" x2="16" y2="12" stroke="currentColor" stroke-width="2"/>
                  </svg>
                </button>
                <button class="pe-add-after-btn" @click="addTokenAfter(i)" title="在后添加">
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
                    <line x1="12" y1="8" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
                    <line x1="8" y1="12" x2="16" y2="12" stroke="currentColor" stroke-width="2"/>
                  </svg>
                </button>
                <button class="pe-remove-detail-btn" @click="removeToken(i)" title="删除">
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <polyline points="3,6 5,6 21,6" stroke="currentColor" stroke-width="2"/>
                    <path d="m19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" stroke="currentColor" stroke-width="2"/>
                  </svg>
                </button>
              </div>
            </div>
            
            <div v-if="editingIndex === i" class="pe-edit-panel">
              <input 
                ref="editEl" 
                v-model="editingValue" 
                @keydown="onEditKeyDown" 
                @keyup.enter="commitEdit" 
                @click="updateEditSuggestions" 
                @keyup="updateEditSuggestions" 
                placeholder="编辑词条..." 
              />
              <ul class="pe-edit-suggest" v-if="editSuggestions.length">
                <li 
                  v-for="s in editSuggestions" 
                  :key="'p_'+s" 
                  @mousedown.prevent 
                  @click="applyEditSuggestion(s)"
                >{{ s }}</li>
              </ul>
              <div class="pe-edit-actions">
                <button @click="commitEdit" class="pe-confirm-btn">确定</button>
                <button @click="cancelEdit" class="pe-cancel-btn">取消</button>
              </div>
            </div>
            
            <div v-if="addingMapIndex === i" class="pe-add-panel">
              <input v-model="addingMapValue" :placeholder="`请输入 ${selectedLang} 的翻译`" @keyup.enter="commitAddMap" />
              <div class="pe-add-actions">
                <button @click="commitAddMap" class="pe-confirm-btn">添加</button>
                <button @click="() => { addingMapIndex = null; addingMapValue = ''; }" class="pe-cancel-btn">取消</button>
              </div>
            </div>
          </div>
        </div>
        </div>
      </section>
    </main>
    
    <!-- 通知组件 -->
    <NotificationToast 
      :message="notification.message"
      :type="notification.type"
      :show="notification.show"
    />
  </div>
</template>

<style scoped>
.pe-root {
  display: flex;
  flex-direction: column;
  height: 100%;
  background-color: var(--color-bg-primary);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
}

.pe-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  background-color: var(--color-bg-secondary);
  border-bottom: 1px solid var(--color-border);
  gap: 1rem;
  flex-wrap: wrap;
}

.pe-left, .pe-right {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.pe-left label {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-text-secondary);
}

.pe-left select {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-size: 0.875rem;
  cursor: pointer;
  transition: all 0.2s ease;
}

.pe-left select:hover {
  border-color: var(--color-border-hover);
}

.pe-left select:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-light);
}

.pe-left button, .pe-right button {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  white-space: nowrap;
  line-height: 1;
}

.pe-left button:hover, .pe-right button:hover {
  background-color: var(--color-bg-tertiary);
  border-color: var(--color-border-hover);
}

.pe-preset-name {
  width: 200px;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-size: 0.875rem;
  transition: all 0.2s ease;
}

.pe-preset-name:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-light);
}

.pe-presets {
  position: relative;
}

.pe-preset-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
}

.pe-preset-toggle:hover {
  background-color: var(--color-bg-tertiary);
  border-color: var(--color-border-hover);
}

.dropdown-arrow {
  transition: transform 0.2s ease;
  margin-left: 0.25rem;
}

.dropdown-arrow.rotate-180 {
  transform: rotate(180deg);
}

.pe-preset-dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 50;
  min-width: 320px;
  max-height: 400px;
  background-color: var(--color-bg-primary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  margin-top: 0.5rem;
  overflow: hidden;
}

.pe-preset-search-wrapper {
  padding: 0.75rem;
  border-bottom: 1px solid var(--color-border);
}

.pe-preset-search {
  width: 100%;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-secondary);
  color: var(--color-text-primary);
  font-size: 0.875rem;
  transition: all 0.2s ease;
}

.pe-preset-search:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-light);
}

.pe-preset-list {
  max-height: 300px;
  overflow-y: auto;
}

.pe-preset-empty {
  padding: 2rem;
  text-align: center;
  color: var(--color-text-tertiary);
  font-size: 0.875rem;
}

.pe-preset-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  border-bottom: 1px solid var(--color-border);
}

.pe-preset-item:last-child {
  border-bottom: none;
}

.pe-preset-load {
  flex: 1;
  text-align: left;
  padding: 0.25rem 0.5rem;
  border: none;
  background: transparent;
  color: var(--color-text-primary);
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: all 0.2s ease;
}

.pe-preset-load:hover {
  background-color: var(--color-bg-secondary);
}

.pe-preset-meta {
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}

.pe-preset-rename, .pe-preset-delete {
  padding: 0.25rem;
  border: none;
  background: transparent;
  color: var(--color-text-secondary);
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: all 0.2s ease;
}

.pe-preset-rename:hover {
  background-color: var(--color-bg-secondary);
  color: var(--color-accent);
}

.pe-preset-delete:hover {
  background-color: var(--color-error);
  color: white;
}

.pe-preset-rename-input {
  flex: 1;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-size: 0.875rem;
}

.pe-preset-rename-ok, .pe-preset-rename-cancel {
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-size: 0.75rem;
  cursor: pointer;
  transition: all 0.2s ease;
}

.pe-preset-rename-ok:hover {
  background-color: var(--color-success);
  color: white;
  border-color: var(--color-success);
}

.pe-preset-rename-cancel:hover {
  background-color: var(--color-bg-tertiary);
}

.pe-main {
  display: grid;
  grid-template-columns: 1fr 1fr;
  height: calc(100vh - 8rem);
  gap: 1px;
  background-color: var(--color-border);
}

.pe-left-pane, .pe-right-pane {
  padding: 1.5rem;
  background-color: var(--color-bg-primary);
  overflow: auto;
}

.pe-section-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-text-secondary);
  margin-bottom: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.pe-mode-switch {
  display: flex;
  gap: 0.25rem;
  background-color: var(--color-bg-secondary);
  padding: 0.25rem;
  border-radius: var(--radius-md);
}

.pe-mode-switch button {
  padding: 0.375rem 0.75rem;
  border: none;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: 0.75rem;
  font-weight: 500;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: all 0.2s ease;
}

.pe-mode-switch button.active {
  background-color: var(--color-accent);
  color: white;
  box-shadow: var(--shadow-sm);
}

.pe-input {
  width: 100%;
  height: 200px;
  padding: 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
  font-size: 0.875rem;
  line-height: 1.5;
  resize: vertical;
  transition: all 0.2s ease;
}

.pe-input:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-light);
}

.pe-input-actions {
  display: flex;
  gap: 0.75rem;
  margin-top: 1rem;
  flex-wrap: wrap;
}

.pe-input-actions button {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  white-space: nowrap;
  line-height: 1;
}

.pe-input-actions button:hover {
  background-color: var(--color-bg-secondary);
  border-color: var(--color-border-hover);
}

.pe-priority-group {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.pe-priority-label {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-text-secondary);
}

.pe-priority-select, .pe-priority-step {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-size: 0.875rem;
  cursor: pointer;
  transition: all 0.2s ease;
}

.pe-priority-select:hover, .pe-priority-step:hover {
  border-color: var(--color-border-hover);
}

.pe-priority-select:focus, .pe-priority-step:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-light);
}

.pe-suggest {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.pe-suggest li {
  padding: 0.375rem 0.75rem;
  background-color: var(--color-bg-secondary);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-size: 0.875rem;
  cursor: pointer;
  transition: all 0.2s ease;
}

.pe-suggest li:hover {
  background-color: var(--color-accent);
  color: white;
  border-color: var(--color-accent);
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}

/* 编辑输入建议列表（紧凑尺寸） */
.pe-edit-suggest {
  list-style: none;
  margin: 0.25rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.pe-edit-suggest li {
  padding: 0.25rem 0.5rem;
  background-color: var(--color-bg-secondary);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 0.75rem;
  cursor: pointer;
  transition: all 0.2s ease;
}

.pe-edit-suggest li:hover {
  background-color: var(--color-accent);
  color: white;
  border-color: var(--color-accent);
}

/* 紧凑行视图 */
.pe-tokens-compact {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
}

.pe-token-compact {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background-color: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 0.5rem 0.75rem;
  cursor: grab;
  transition: all 0.2s ease;
  max-width: 100%;
}

.pe-token-compact:hover {
  border-color: var(--color-border-hover);
  box-shadow: var(--shadow-sm);
}

/* 精简视图编辑态美化 */
.pe-token-compact.editing {
  cursor: default;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-light);
  background-color: var(--color-bg-primary);
}

.pe-token-compact[draggable="false"] {
  cursor: default;
}

.pe-token-compact.editing .pe-handle-compact,
.pe-token-compact.editing .pe-token-content,
.pe-token-compact.editing .pe-token-controls-compact {
  display: none;
}

.pe-edit-inline {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
}

.pe-edit-input {
  flex: 1;
  min-width: 0;
  padding: 0.375rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-secondary);
  color: var(--color-text-primary);
  font-size: 0.8125rem;
  transition: all 0.2s ease;
}

.pe-edit-input::placeholder {
  color: var(--color-text-tertiary);
}

.pe-edit-input:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-light);
  background-color: var(--color-bg-primary);
}

.pe-edit-actions {
  display: flex;
  gap: 0.25rem;
}

.pe-edit-save-btn, .pe-edit-cancel-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border: 1px solid var(--color-border);
  background-color: var(--color-bg-primary);
  color: var(--color-text-secondary);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: all 0.2s ease;
}

.pe-edit-save-btn:hover {
  background-color: var(--color-accent);
  color: white;
  border-color: var(--color-accent);
}

.pe-edit-cancel-btn:hover {
  background-color: var(--color-bg-tertiary);
  color: var(--color-text-primary);
}

.pe-token-compact.dragging {
  opacity: 0.5;
  transform: rotate(2deg);
  cursor: grabbing;
}

.pe-token-compact.drag-over {
  border-color: var(--color-accent);
  background-color: var(--color-accent-light);
}

.pe-handle-compact {
  cursor: grab;
  user-select: none;
  color: var(--color-text-tertiary);
  font-size: 0.75rem;
  padding: 0.125rem;
  border-radius: var(--radius-sm);
  transition: all 0.2s ease;
  flex-shrink: 0;
}

.pe-handle-compact:hover {
  color: var(--color-text-secondary);
  background-color: var(--color-bg-tertiary);
}

.pe-handle-compact:active {
  cursor: grabbing;
}

.pe-token-content {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex: 1;
  min-width: 0;
}

.pe-key-compact {
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-text-primary);
  background-color: var(--color-bg-tertiary);
  padding: 0.125rem 0.375rem;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pe-arrow-compact {
  color: var(--color-text-tertiary);
  font-size: 0.75rem;
  flex-shrink: 0;
}

.pe-trans-compact {
  font-size: 0.75rem;
  color: var(--color-text-primary);
  font-weight: 500;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pe-trans-compact.unmapped {
  color: var(--color-error);
  font-style: italic;
  opacity: 0.8;
}

.pe-remove-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  border: 1px solid var(--color-border);
  background-color: var(--color-bg-primary);
  color: var(--color-text-secondary);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: all 0.2s ease;
  flex-shrink: 0;
  opacity: 0.7;
}

.pe-token-compact:hover .pe-remove-btn {
  opacity: 1;
}

.pe-remove-btn:hover {
  background-color: var(--color-error);
  color: white;
  border-color: var(--color-error);
  transform: scale(1.05);
}

/* 精简视图的包裹层控制按钮 */
.pe-token-controls-compact {
  display: flex;
  gap: 0.125rem;
  opacity: 0.7;
  transition: opacity 0.2s ease;
}

.pe-token-compact:hover .pe-token-controls-compact {
  opacity: 1;
}

.pe-add-wrapper-btn, .pe-remove-wrapper-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  border: 1px solid var(--color-border);
  background-color: var(--color-bg-primary);
  color: var(--color-text-secondary);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: all 0.2s ease;
  flex-shrink: 0;
}

.pe-add-wrapper-btn:hover {
  background-color: var(--color-accent);
  color: white;
  border-color: var(--color-accent);
}

.pe-remove-wrapper-btn:hover:not(:disabled) {
  background-color: var(--color-warning);
  color: white;
  border-color: var(--color-warning);
}

.pe-remove-wrapper-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

/* 详细列表视图 */
.pe-tokens-detail {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
}

.pe-token-detail {
  position: relative;
  background-color: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  transition: all 0.2s ease;
  overflow: hidden;
}

.pe-token-detail:hover {
  border-color: var(--color-border-hover);
  box-shadow: var(--shadow-sm);
}

.pe-token-detail.editing {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-light);
}

.pe-token-detail.dragging {
  opacity: 0.5;
  transform: rotate(2deg);
}

.pe-token-detail.drag-over {
  border-color: var(--color-accent);
  background-color: var(--color-accent-light);
}

.pe-token-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem;
}

.pe-handle-detail {
  cursor: grab;
  user-select: none;
  color: var(--color-text-tertiary);
  font-size: 1rem;
  padding: 0.25rem;
  border-radius: var(--radius-sm);
  transition: all 0.2s ease;
  flex-shrink: 0;
}

.pe-handle-detail:hover {
  color: var(--color-text-secondary);
  background-color: var(--color-bg-tertiary);
}

.pe-handle-detail:active {
  cursor: grabbing;
}

.pe-token-main {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
  cursor: pointer;
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-sm);
  transition: all 0.2s ease;
}

.pe-token-main:hover {
  background-color: var(--color-bg-tertiary);
}

.pe-key-detail {
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-text-primary);
  background-color: var(--color-bg-tertiary);
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}

.pe-arrow-detail {
  color: var(--color-text-tertiary);
  font-weight: 500;
  flex-shrink: 0;
}

.pe-trans-detail {
  color: var(--color-text-primary);
  font-weight: 500;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pe-trans-detail.unmapped {
  color: var(--color-error);
  font-style: italic;
}

.pe-token-controls {
  display: flex;
  gap: 0.25rem;
  opacity: 0.7;
  transition: opacity 0.2s ease;
}

.pe-token-detail:hover .pe-token-controls {
  opacity: 1;
}

.pe-add-map-btn, .pe-add-after-btn, .pe-remove-detail-btn, 
.pe-add-wrapper-detail-btn, .pe-remove-wrapper-detail-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--color-border);
  background-color: var(--color-bg-primary);
  color: var(--color-text-secondary);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: all 0.2s ease;
}

.pe-add-map-btn:hover {
  background-color: var(--color-accent);
  color: white;
  border-color: var(--color-accent);
}

.pe-add-after-btn:hover {
  background-color: var(--color-success);
  color: white;
  border-color: var(--color-success);
}

.pe-add-wrapper-detail-btn:hover {
  background-color: var(--color-accent);
  color: white;
  border-color: var(--color-accent);
}

.pe-remove-wrapper-detail-btn:hover:not(:disabled) {
  background-color: var(--color-warning);
  color: white;
  border-color: var(--color-warning);
}

.pe-remove-wrapper-detail-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

.pe-remove-detail-btn:hover {
  background-color: var(--color-error);
  color: white;
  border-color: var(--color-error);
}

.pe-edit-panel, .pe-add-panel {
  padding: 0.75rem;
  background-color: var(--color-bg-primary);
  border-top: 1px solid var(--color-border);
}

.pe-edit-panel input, .pe-add-panel input {
  width: 100%;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-secondary);
  color: var(--color-text-primary);
  font-size: 0.875rem;
  margin-bottom: 0.5rem;
  transition: all 0.2s ease;
}

.pe-edit-panel input:focus, .pe-add-panel input:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-light);
}

.pe-edit-actions, .pe-add-actions {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
}

.pe-confirm-btn, .pe-cancel-btn {
  padding: 0.375rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 0.75rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
}

.pe-confirm-btn {
  background-color: var(--color-accent);
  color: white;
  border-color: var(--color-accent);
}

.pe-confirm-btn:hover {
  background-color: var(--color-accent-hover);
}

.pe-cancel-btn {
  background-color: var(--color-bg-primary);
  color: var(--color-text-secondary);
}

.pe-cancel-btn:hover {
  background-color: var(--color-bg-tertiary);
  color: var(--color-text-primary);
}

/* 响应式设计 */
@media (max-width: 1024px) {
  .pe-main {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
  }
  
  .pe-left-pane {
    border-bottom: 1px solid var(--color-border);
  }
}

@media (max-width: 768px) {
  .pe-toolbar {
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
  }
  
  .pe-left, .pe-right {
    justify-content: center;
  }
  
  .pe-left-pane, .pe-right-pane {
    padding: 1rem;
  }
  
  .pe-input {
    height: 150px;
  }
  
  .pe-preset-name {
    width: 100%;
  }
  
  .pe-preset-dropdown {
    position: static;
    margin-top: 0.5rem;
    box-shadow: none;
    border: 1px solid var(--color-border);
    min-width: auto;
  }
  
  .pe-tokens-compact {
    gap: 0.375rem;
  }
  
  .pe-token-compact {
    padding: 0.375rem 0.5rem;
  }
}

@media (max-width: 640px) {
  .pe-input-actions {
    flex-direction: column;
  }
  
  .pe-input-actions button {
    width: 100%;
    justify-content: center;
  }
  
  .pe-token-header {
    flex-direction: column;
    align-items: stretch;
    gap: 0.5rem;
  }
  
  .pe-token-main {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.375rem;
  }
  
  .pe-token-controls {
    align-self: flex-end;
    opacity: 1;
  }
  
  .pe-remove-btn {
    opacity: 1;
  }
  
  .pe-token-controls-compact {
    opacity: 1;
  }
  
  .pe-edit-actions, .pe-add-actions {
    flex-direction: column;
  }
  
  .pe-confirm-btn, .pe-cancel-btn {
    width: 100%;
  }
  
  .pe-tokens-compact {
    gap: 0.25rem;
  }
  
  .pe-token-compact {
    padding: 0.25rem 0.375rem;
    gap: 0.25rem;
  }
  
  .pe-key-compact {
    font-size: 0.6875rem;
    max-width: 80px;
  }
  
  .pe-trans-compact {
    font-size: 0.6875rem;
  }
  
  .pe-arrow-compact {
    font-size: 0.6875rem;
  }
}

/* 拖拽状态样式 */
.pe-token-compact[draggable="true"]:hover .pe-handle-compact,
.pe-token-detail[draggable="true"]:hover .pe-handle-detail {
  color: var(--color-accent);
}

/* 拖拽预览样式 */
.drag-preview {
  background-color: var(--color-bg-primary);
  border: 2px solid var(--color-accent);
  border-radius: var(--radius-md);
  padding: 0.5rem 0.75rem;
  box-shadow: var(--shadow-md);
  opacity: 0.9;
}

.drag-preview-content {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  white-space: nowrap;
}

.drag-preview-key {
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-text-primary);
  background-color: var(--color-bg-tertiary);
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-sm);
}

.drag-preview-arrow {
  color: var(--color-text-tertiary);
  font-weight: 500;
}

.drag-preview-trans {
  color: var(--color-text-primary);
  font-weight: 500;
}

/* 拖拽占位符样式 */
.pe-token-compact.drag-placeholder,
.pe-token-detail.drag-placeholder {
  position: relative;
}

.pe-token-compact.drag-placeholder::before,
.pe-token-detail.drag-placeholder::before {
  content: '';
  position: absolute;
  top: -2px;
  left: -2px;
  right: -2px;
  bottom: -2px;
  border: 2px dashed var(--color-accent);
  border-radius: var(--radius-md);
  background-color: var(--color-accent-light);
  opacity: 0.3;
  animation: pulse 1s ease-in-out infinite alternate;
}

@keyframes pulse {
  from {
    opacity: 0.2;
  }
  to {
    opacity: 0.5;
  }
}

/* 改进拖拽中的样式 */
.pe-token-compact.dragging,
.pe-token-detail.dragging {
  opacity: 0.3;
  transform: scale(0.95) rotate(2deg);
  cursor: grabbing;
  z-index: 1000;
  box-shadow: var(--shadow-lg);
}

.pe-token-compact.drag-over,
.pe-token-detail.drag-over {
  border-color: var(--color-accent);
  background-color: var(--color-accent-light);
  transform: scale(1.02);
  transition: all 0.2s ease;
}

/* 插入方向指示：目标项向前/后移动并显示清晰插入方向 */
.pe-token-compact.insert-before,
.pe-token-detail.insert-before {
  transform: translateX(10px);
  border-color: var(--color-accent);
}

.pe-token-compact.insert-after,
.pe-token-detail.insert-after {
  transform: translateX(-10px);
  border-color: var(--color-accent);
}

.pe-token-compact.insert-before::before,
.pe-token-detail.insert-before::before {
  content: '';
  position: absolute;
  left: -6px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 60%;
  background-color: var(--color-accent);
  border-radius: 2px;
  opacity: 0.6;
}

.pe-token-compact.insert-after::after,
.pe-token-detail.insert-after::after {
  content: '';
  position: absolute;
  right: -6px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 60%;
  background-color: var(--color-accent);
  border-radius: 2px;
  opacity: 0.6;
}

/* 拖拽容器样式 */
.pe-drag-container {
  position: relative;
  min-height: 200px;
  transition: all 0.1s ease;
}

.pe-drag-container.is-dragging {
  background-color: var(--color-bg-secondary);
  border: 2px dashed var(--color-accent);
  border-radius: var(--radius-lg);
  padding: 0.5rem;
}

.pe-drag-container.is-dragging::after {
  content: '拖拽到此处重新排序';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: var(--color-text-tertiary);
  font-size: 0.875rem;
  pointer-events: none;
  opacity: 0.5;
}

/* 预先启用复合层以提升拖拽流畅度 */
.pe-token-compact,
.pe-token-detail {
  will-change: transform;
}

/* 加载和过渡动画 */
.pe-token-compact, .pe-token-detail {
  animation: slideIn 0.3s ease-out;
}

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 下拉菜单动画 */
.dropdown-enter-active {
  transition: all 0.2s ease-out;
}

.dropdown-leave-active {
  transition: all 0.2s ease-in;
}

.dropdown-enter-from {
  opacity: 0;
  transform: translateY(-10px) scale(0.95);
}

.dropdown-leave-to {
  opacity: 0;
  transform: translateY(-10px) scale(0.95);
}

/* 滚动条样式 */
.pe-left-pane::-webkit-scrollbar,
.pe-right-pane::-webkit-scrollbar,
.pe-preset-list::-webkit-scrollbar {
  width: 6px;
}

.pe-left-pane::-webkit-scrollbar-track,
.pe-right-pane::-webkit-scrollbar-track,
.pe-preset-list::-webkit-scrollbar-track {
  background: var(--color-bg-secondary);
}

.pe-left-pane::-webkit-scrollbar-thumb,
.pe-right-pane::-webkit-scrollbar-thumb,
.pe-preset-list::-webkit-scrollbar-thumb {
  background: var(--color-border-hover);
  border-radius: 3px;
}

.pe-left-pane::-webkit-scrollbar-thumb:hover,
.pe-right-pane::-webkit-scrollbar-thumb:hover,
.pe-preset-list::-webkit-scrollbar-thumb:hover {
  background: var(--color-text-tertiary);
}


/* 保证按钮内图标不压缩文本，提升对齐与可读性 */
.pe-left button svg,
.pe-right button svg,
.pe-input-actions button svg,
.pe-preset-toggle svg {
  flex-shrink: 0;
}
</style>