оптимизируй мой код, сохранив весь потенциал моего приложения.

"""
РЕДАКТОР АТРИБУТОВ v4.9 - СИСТЕМА ДИНАМИЧЕСКИХ ОБНОВЛЕНИЙ
=========================================================

🆕 НОВЫЕ ВОЗМОЖНОСТИ v4.9 - ПОЛНАЯ СИСТЕМА ДИНАМИЧЕСКИХ ОБНОВЛЕНИЙ:
✅ Автоматическое обновление "Результат разделения" при изменении синонимов
✅ Динамические обновления при изменениях в "Объединение параметров" 
✅ Автоматические обновления при изменениях в "Переводы значений"
✅ Динамическое обновление при изменениях в "Правила замен"
✅ Обновление "Новый параметр" при изменении "Таблица сопоставлений"
✅ Система обратных вызовов (callbacks) для связи между вкладками
✅ Мгновенное отражение всех изменений без ручного обновления

🔧 КРИТИЧЕСКИЕ ИСПРАВЛЕНИЯ v4.8.4 - ПОЛНОСТЬЮ РЕШЕНА ПРОБЛЕМА ОБЪЕДИНЕНИЯ:
✅ ДИНАМИЧЕСКОЕ обновление основной таблицы при изменении правил перевода
✅ ЗАЩИТА известных аббревиатур (H.264, G.711, IP67) от неправильного перевода  
✅ МАССОВОЕ управление ТОЛЬКО выбранными правилами перевода
✅ Счетчик выбранных элементов в реальном времени
✅ ПОЛНОСТЬЮ ПЕРЕПИСАНО объединение параметров - простая и надежная логика
✅ ИСПРАВЛЕНО сохранение переводов в скобках "Auto (АТО) / Color (Цв.)"
✅ ИСПРАВЛЕН сбор ВСЕХ значений из ВСЕХ выбранных параметров
✅ НОВАЯ функция _smart_split_values для умного разделения
✅ ГАРАНТИРОВАННОЕ сохранение ВСЕХ уникальных значений
✅ ПРИМЕНЕНИЕ переводов ко всем собранным значениям

🔧 ИСПРАВЛЕНИЯ v4.7 - ЛОГИКА ОБЪЕДИНЕНИЯ ЗНАЧЕНИЙ:
✅ Корректное объединение значений в столбце "Результат разделения"
✅ Правильное применение синонимов из настроек
✅ Удаление некорректных значений типа "/;"
✅ Динамическое обновление при изменении синонимов
✅ Сохранение всех уникальных значений без потерь

🆕 Новые возможности v4.6 + Расширенная система переводов:
🌟 Полнофункциональный GUI редактор переводов вместо простого информационного окна
📝 Управление множественными словарями (основной, пользовательские, специализированные)
🏷️ Система категоризации переводов (16 категорий)
🔍 Мгновенный поиск и фильтрация переводов
📥📤 Импорт/экспорт словарей (JSON, CSV)
📊 Статистика использования переводов
⚙️ Гибкие настройки системы переводов
🔮 Автодополнение при вводе
💾 Автоматическое резервное копирование
🤖 Поддержка машинного перевода (опционально)

Предыдущие возможности v4.5:
🌐 Автоматический перевод параметров и значений EN→RU
🎯 Интеллектуальный поиск переводов с нечетким соответствием
📚 Пользовательские словари переводов
🔄 Настройки системы переводов

Улучшения в версии v4.2:
✅ Компактный эргономичный интерфейс
✅ Логическое группирование элементов в блоки
✅ Сокращение вертикального пространства на 40%
✅ Панель инструментов с иконками
✅ Перенос редко используемых функций в меню
✅ Цветовые индикаторы и эмодзи для навигации
✅ Улучшенная читаемость и удобство использования
🆕 Диалог справки "📚 Справка" с подробной инструкцией (F1)

Исправления в версии v4.1:
✅ Корректная работа кнопки выбора столбцов
✅ Сохранение выбранных столбцов между сессиями
✅ Правильная загрузка сохраненных настроек столбцов
✅ Кнопки "Выбрать все" и "Снять все" в диалоге
✅ Визуальная индикация количества выбранных столбцов
✅ Правильная обработка выбранных столбцов при экспорте

Предыдущие возможности v4.0:
✅ Корректное объединение параметров без потери данных
✅ Правильная дедупликация значений
✅ Сохранение всех уникальных значений при объединении
✅ Детальная отладка процесса объединения
🆕 Цветовое выделение похожих параметров
🆕 Настройки синонимов для объединения значений
🆕 GUI для управления синонимами значений

🔧 ТОЧЕЧНЫЕ ИСПРАВЛЕНИЯ v4.7:
✅ Улучшена функция merge_synonyms_in_list() - корректное применение синонимов
✅ Добавлены функции clean_value_enhanced() и is_valid_value_enhanced()
✅ Исправлена функция split_values() - фильтрация невалидных значений типа "/;"
✅ Улучшена функция get_preferred_value() - поиск синонимов без учета регистра
✅ Сохранен весь оригинальный функционал и GUI без изменений

🆕 НОВЫЕ ВОЗМОЖНОСТИ v4.7:
🌐 Добавлена вкладка "Переводы значений" с полным управлением правилами
📋 Таблица правил перевода значений и единиц измерения (mm→мм, kg→кг, etc.)
🔍 Автоматическое обнаружение единиц измерения при нажатии "Обработать"
💾 Импорт/экспорт правил перевода в JSON формате
⚙️ Интеграция правил в процесс обработки значений

Автор: AI Assistant
Дата: 2025-06-08
Версия: 4.9 СИСТЕМА ДИНАМИЧЕСКИХ ОБНОВЛЕНИЙ (автоматическое обновление всех вкладок + callbacks)
"""

import re
import json
from tkinter import *
from tkinter import ttk, filedialog, messagebox, simpledialog
from collections import defaultdict
import os
import sys

# ===== 🛡️ ЗАЩИЩЕННЫЕ АББРЕВИАТУРЫ v4.8 =====
PROTECTED_ABBREVIATIONS = {
    # Форматы сжатия видео/аудио
    'H.264', 'H.265', 'H.263', 'H.261', 'HEVC', 'AVC', 'MJPEG', 'MPEG-4', 'MPEG-2',
    'G.711', 'G.722', 'G.726', 'G.729', 'AAC', 'MP3', 'PCM', 'ADPCM',
    
    # Степени защиты  
    'IP65', 'IP66', 'IP67', 'IP68', 'IP54', 'IP44', 'IK08', 'IK10',
    
    # Стандарты и протоколы
    'TCP/IP', 'HTTP', 'HTTPS', 'FTP', 'SMTP', 'DNS', 'DHCP', 'NTP',
    'IEEE', 'USB', 'HDMI', 'VGA', 'DVI', 'PoE', 'PoE+', 'UPS',
    'Wi-Fi', 'Bluetooth', 'Zigbee', 'Z-Wave', 'LoRa', 'NFC', 'RFID',
    
    # Технические аббревиатуры
    'LED', 'LCD', 'OLED', 'TFT', 'CRT', 'IR', 'UV', 'RGB', 'CMYK',
    'CPU', 'GPU', 'RAM', 'ROM', 'SSD', 'HDD', 'API', 'SDK', 'IDE',
    'ISO', 'IEC', 'FCC', 'CE', 'RoHS', 'UL', 'CSA', 'ETL'
}

# Добавляем путь к модулям расширенной системы переводов
try:
    current_dir = os.path.dirname(os.path.abspath(__file__))
except NameError:
    # Fallback для случая выполнения через exec
    current_dir = os.getcwd()

code_dir = os.path.join(current_dir, 'code')
if code_dir not in sys.path:
    sys.path.insert(0, code_dir)

# Опциональные импорты
try:
    import pandas as pd
    PANDAS_AVAILABLE = True
except ImportError:
    print("⚠️ pandas не найден - некоторые функции могут быть недоступны")
    PANDAS_AVAILABLE = False

try:
    import pyperclip
    PYPERCLIP_AVAILABLE = True
except ImportError:
    print("⚠️ pyperclip не найден - копирование в буфер недоступно")
    PYPERCLIP_AVAILABLE = False

try:
    from bs4 import BeautifulSoup
    BEAUTIFULSOUP_AVAILABLE = True
except ImportError:
    print("⚠️ BeautifulSoup не найден - парсинг HTML ограничен")
    BEAUTIFULSOUP_AVAILABLE = False

try:
    from rapidfuzz import process, fuzz
    RAPIDFUZZ_AVAILABLE = True
except ImportError:
    print("⚠️ rapidfuzz не найден - нечеткий поиск недоступен")
    RAPIDFUZZ_AVAILABLE = False

# 🆕 ИНТЕГРАЦИЯ РАСШИРЕННОЙ СИСТЕМЫ ПЕРЕВОДОВ
print("🔧 Загрузка расширенной системы переводов...")

# Fallback импорты словарей
try:
    from translation_dict import FULL_TRANSLATION_DICT, TRANSLATION_DICT, ELECTRONICS_DICT, COMPUTER_DICT
except ImportError:
    # Fallback словарь если файл не найден
    FULL_TRANSLATION_DICT = {
        "Camera": "Камера",
        "Lens": "Объектив",
        "Video": "Изображение", 
        "Audio": "Аудио",
        "Network": "Сеть",
        "Power": "Питание",
        "Megapixel": "Мп",
        "Auto": "Авто",
        "Manual": "Ручная",
        "Supported": "Поддерживается"
    }

# Импорт расширенной системы переводов с fallback
try:
    from translation_integration import (
        IntegratedTranslationSystem,
        IntegratedTranslationDialog,
        TRANSLATION_AVAILABLE as ADVANCED_TRANSLATION_AVAILABLE,
        is_advanced_system_available
    )
    
    print("✅ Расширенная система переводов загружена")
    TRANSLATION_INTEGRATION_SUCCESS = True
    
    # Устанавливаем расширенные классы
    TranslationSystem = IntegratedTranslationSystem
    TranslationManagementDialog = IntegratedTranslationDialog
    TRANSLATION_AVAILABLE = ADVANCED_TRANSLATION_AVAILABLE
    
    if is_advanced_system_available():
        print("🌟 Полнофункциональная система переводов активирована")
        TRANSLATION_SYSTEM_TYPE = "advanced"
    else:
        print("⚙️ Базовая интегрированная система переводов активирована")
        TRANSLATION_SYSTEM_TYPE = "basic_integrated"
        
except ImportError as e:
    print(f"⚠️ Расширенная система переводов недоступна: {e}")
    print("🔄 Переключение на встроенную базовую систему...")
    TRANSLATION_INTEGRATION_SUCCESS = False
    TRANSLATION_SYSTEM_TYPE = "builtin"

    # 🌐 ВСТРОЕННАЯ БАЗОВАЯ СИСТЕМА ПЕРЕВОДОВ (FALLBACK)
    class BuiltinTranslationSystem:
        """Встроенная система переводов с основными терминами из ваших таблиц"""
        def __init__(self):
            self.auto_translate = True
            self.fuzzy_match_threshold = 80
            self.translate_values = True
            self.translate_parameters = True
            self.custom_translations = {}
            
            # Переводы на основе ваших таблиц характеристик камеры
            self.translation_dict = {
                # Основные разделы
                "Camera": "Камера",
                "Lens": "Объектив", 
                "Video": "Изображение",
                "Audio": "Аудио",
                "Network": "Сеть",
                "Alarm": "Тревога",
                "Interfaces": "Интерфейсы",
                "Environmental": "Условия эксплуатации",
                "Electrical": "Питание",
                "Mechanical": "Габариты",
                "Infrared": "ИК подсветка",
                
                # Параметры камеры
                "Image Sensor": "Матрица",
                "Pixels": "Количество пикселей",
                "Min. Illumination": "Чувствительность",
                "Shutter Speed": "Скорость затвора",
                "Day / Night": "Режим «День / Ночь»",
                "Wide Dynamic Range": "Динамический диапазон",
                "S/N Ratio": "Соотношение сигнал / шум",
                "White Balance": "Регулировка баланс белого",
                "Gain Control": "Регулировка усиления",
                "Backlight Compensation": "Улучшение изображения",
                
                # Объектив
                "Mount Type": "Крепление объектива",
                "Auto Iris": "Диафрагма",
                "Focal Length": "Фокусное расстояние",
                "Focus Control": "Фокусировка",
                "Angle of View": "Угол обзора",
                "Adjustment Range": "Диапазон поворота",
                
                # ИК подсветка
                "IR Distance": "Дальность ИК подсветки",
                "IR Intensity": "Тип ИК подсветки",
                
                # Видео
                "Compression": "Формат сжатия",
                "Image Resolution": "Разрешение",
                "Frame Rate": "Частота кадров",
                "Bitrate": "Битрейт",
                "Bitrate Control": "Тип битрейта",
                "Multi-stream": "Дополнительный поток",
                "Caption": "Отображение информации",
                "Privacy Masking": "Маска приватности",
                "Motion Detection": "Детектор движения",
                "Video Freeze": "Video Freeze",
                "ROI": "ROI",
                "Digital Noise Reduction": "Цифровое шумоподавление",
                "Digital Defog": "Антитуман",
                "Image Flip": "Режим отображения",
                
                # Аудио
                "Audio Function": "Функции аудио",
                
                # Сеть
                "Network Protocols": "Сетевые протоколы",
                "User Interface": "Поддержка браузеров",
                "Max. User Access": "Количество подключений",
                "Application Programming": "Совместимость",
                "Automatic Network Replenishment (ANR)": "Автоматическая детекция сетевого статуса (ANR)",
                
                # Тревога
                "Alarm Triggers": "Системные оповещения",
                "Intelligent": "Интеллектуальные функции",
                "Alarm Events": "Действия при тревоге",
                
                # Интерфейсы
                "Ethernet": "Сетевой интерфейс",
                "Audio In / Out": "Аудио вход / выход",
                "Control": "Последоват. интерфейс",
                "Alarm In / Out": "Тревожный вход / выход",
                "Video Out": "Видео выход",
                "Memory Slot": "Слот для карты памяти",
                
                # Условия эксплуатации
                "Operating Temperature": "Диапазон температур",
                "Operating Humidity": "Диапазон влажности",
                "Protection Level": "Уровень защиты",
                
                # Питание
                "Power": "Источник питания",
                "Power Consumption": "Потребляемая мощность",
                
                # Габариты
                "Weight": "Вес",
                "Dimensions": "Размеры",
                
                # Значения
                "Auto": "Авто",
                "Manual": "Ручная",
                "Auto / Manual": "Авто / Ручная",
                "Supported": "Поддерживается",
                "Megapixel": "Мп",
                "4.0 Megapixel": "4 Мп",
                "Digital WDR": "Цифровой WDR",
                "Smart IR": "Смарт ИК",
                "VBR / CBR": "VBR / CBR",
                "3D noise reduction": "3D DNR",
                "Bi-directional audio": "Двухсторонняя связь",
                "10/100M": "10M/100M",
                "DC12V": "12В DC",
                "PoE": "PoE",
                "Max.": "До",
                "Up to": "До",
                "fps": "к/с",
                "kbps": "кбит/с", 
                "Mbps": "Мбит/с",
                "mm": "мм",
                "kg": "кг",
                "GB": "Гб"
            }
        
        def translate_text(self, text, is_parameter=True):
            """Перевод текста с английского на русский"""
            if not text or not isinstance(text, str):
                return text
            
            # Прямой поиск
            if text in self.translation_dict:
                return self.translation_dict[text]
            
            # Поиск по частям (для составных терминов)
            words = text.split()
            if len(words) > 1:
                translated_words = []
                for word in words:
                    # Убираем знаки препинания для поиска
                    clean_word = word.strip('.,;:()[]{}/')
                    if clean_word in self.translation_dict:
                        # Сохраняем знаки препинания
                        punctuation = word[len(clean_word):]
                        translated_words.append(self.translation_dict[clean_word] + punctuation)
                    else:
                        translated_words.append(word)
                
                result = " ".join(translated_words)
                if result != text:
                    return result
            
            # 🔧 ИСПРАВЛЕНО: Поиск ЦЕЛЫХ СЛОВ (не подстрок) для предотвращения частичного перевода
            import re
            original_text = text
            for original, translation in self.translation_dict.items():
                # Создаем паттерн для поиска целого слова
                pattern = r'\b' + re.escape(original) + r'\b'
                if re.search(pattern, text, re.IGNORECASE):
                    old_text = text
                    text = re.sub(pattern, translation, text, flags=re.IGNORECASE)
                    if old_text != text:
                        print(f"🔄 Словарный перевод (целое слово): '{original}' → '{translation}' в '{old_text}' → '{text}'")
            
            return text
        
        def save_custom_translations(self):
            """Заглушка для совместимости"""
            pass
        
        def get_translation_count(self):
            """Получение количества переводов"""
            return len(self.translation_dict)

    class BuiltinTranslationDialog:
        """Встроенный диалог настроек переводов с информацией о системе"""
        def __init__(self, parent, translation_system):
            system_info = {
                'advanced': "🌟 Полнофункциональная система переводов",
                'basic_integrated': "⚙️ Базовая интегрированная система переводов", 
                'builtin': "📋 Встроенная система переводов"
            }
            
            count = getattr(translation_system, 'get_translation_count', lambda: len(getattr(translation_system, 'translation_dict', {})))()
            
            info_text = f"""{system_info.get(TRANSLATION_SYSTEM_TYPE, 'Система переводов')}

📚 Доступно переводов: {count}
🎯 Автоперевод: {'Включен' if getattr(translation_system, 'auto_translate', True) else 'Отключен'}
🔍 Нечеткий поиск: {getattr(translation_system, 'fuzzy_match_threshold', 80)}%

Основные переводы:
• Camera → Камера
• Image Sensor → Матрица  
• Motion Detection → Детектор движения
• Power Consumption → Потребляемая мощность
• 4.0 Megapixel → 4 Мп
• Supported → Поддерживается

{
'Полнофункциональный GUI редактор переводов с поиском, категориями, импортом/экспортом и статистикой.' 
if TRANSLATION_SYSTEM_TYPE == 'advanced' else
'Базовая система с автоматическим fallback. Для полного функционала установите модули расширенной системы переводов.'
if TRANSLATION_SYSTEM_TYPE == 'basic_integrated' else
'Автономная версия с базовым набором переводов. Для расширенной функциональности установите полную систему переводов.'
}"""
            
            messagebox.showinfo("🌐 Система переводов", info_text)

    # Устанавливаем fallback классы
    TranslationSystem = BuiltinTranslationSystem
    TranslationManagementDialog = BuiltinTranslationDialog
    TRANSLATION_AVAILABLE = True

# Информация о загруженной системе переводов
system_info = {
    'advanced': "🌟 Полнофункциональная система переводов готова к работе",
    'basic_integrated': "⚙️ Базовая интегрированная система переводов готова к работе", 
    'builtin': "📋 Встроенная система переводов готова к работе"
}
print(system_info.get(TRANSLATION_SYSTEM_TYPE, "✅ Система переводов готова к работе"))

from difflib import SequenceMatcher
class ParameterMergeDialog:
    """Диалог для выбора основного параметра при объединении"""
    def __init__(self, parent, parameters):
        self.result = None
        
        self.dialog = Toplevel(parent)
        self.dialog.title("Выбор основного параметра")
        self.dialog.geometry("400x300")
        self.dialog.transient(parent)
        self.dialog.grab_set()
        
        # Центрируем диалог
        self.dialog.geometry("+%d+%d" % (parent.winfo_rootx() + 50, parent.winfo_rooty() + 50))
        
        Label(self.dialog, text="Выберите основной параметр для объединения:", font=("Arial", 10, "bold")).pack(pady=10)
        
        # Создаем список параметров
        self.listbox = Listbox(self.dialog, height=8)
        self.listbox.pack(fill=BOTH, expand=True, padx=20, pady=10)
        
        for param in parameters:
            self.listbox.insert(END, param)
        
        # Выбираем первый параметр по умолчанию
        if parameters:
            self.listbox.selection_set(0)
        
        # Кнопки
        button_frame = Frame(self.dialog)
        button_frame.pack(pady=10)
        
        Button(button_frame, text="OK", command=self.ok_clicked, width=10).pack(side=LEFT, padx=5)
        Button(button_frame, text="Отмена", command=self.cancel_clicked, width=10).pack(side=LEFT, padx=5)
        
        # Обработка Enter и Escape
        self.dialog.bind('<Return>', lambda e: self.ok_clicked())
        self.dialog.bind('<Escape>', lambda e: self.cancel_clicked())
        
        # Ждем закрытия диалога
        self.dialog.wait_window()
    
    def ok_clicked(self):
        selection = self.listbox.curselection()
        if selection:
            self.result = self.listbox.get(selection[0])
        self.dialog.destroy()
    
    def cancel_clicked(self):
        self.result = None
        self.dialog.destroy()


class ValueSynonymsManager:
    """🆕 УЛУЧШЕННЫЙ Менеджер синонимов с поддержкой callbacks v4.9"""
    def __init__(self):
        self.synonyms = {}  # {synonym: preferred_value}
        self.callbacks = []  # 🆕 Список функций для уведомления об изменениях
        self.load_synonyms()
    
    def add_callback(self, callback_func):
        """🆕 Добавляет функцию обратного вызова для уведомления об изменениях"""
        if callback_func not in self.callbacks:
            self.callbacks.append(callback_func)
            print(f"📞 Добавлен callback для синонимов: {callback_func.__name__}")
    
    def remove_callback(self, callback_func):
        """🆕 Удаляет функцию обратного вызова"""
        if callback_func in self.callbacks:
            self.callbacks.remove(callback_func)
            print(f"📞 Удален callback для синонимов: {callback_func.__name__}")
    
    def notify_callbacks(self):
        """🆕 Уведомляет все зарегистрированные функции об изменениях"""
        print(f"📞 Уведомляем {len(self.callbacks)} callbacks о изменении синонимов")
        for callback in self.callbacks:
            try:
                callback()
                print(f"✅ Callback выполнен: {callback.__name__}")
            except Exception as e:
                print(f"❌ Ошибка в callback {callback.__name__}: {e}")

    def load_synonyms(self):
        """Загружает синонимы из файла"""
        try:
            if os.path.exists("value_synonyms.json"):
                with open("value_synonyms.json", "r", encoding="utf-8") as f:
                    self.synonyms = json.load(f)
            else:
                # Создаем примеры синонимов по умолчанию
                self.synonyms = {
                    "Support": "Supported",
                    "Yes": "Supported", 
                    "Enable": "Supported",
                    "True": "Supported",
                    "No": "Not Supported",
                    "False": "Not Supported",
                    "Disable": "Not Supported"
                }
                self.save_synonyms()
        except Exception as e:
            print(f"Ошибка при загрузке синонимов: {e}")
            self.synonyms = {}
    
    def save_synonyms(self):
        """Сохраняет синонимы в файл"""
        try:
            with open("value_synonyms.json", "w", encoding="utf-8") as f:
                json.dump(self.synonyms, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"Ошибка при сохранении синонимов: {e}")
    
    def add_synonym(self, synonym, preferred):
        """🆕 Добавляет синоним и уведомляет подписчиков"""
        self.synonyms[synonym.strip()] = preferred.strip()
        self.save_synonyms()
        self.notify_callbacks()  # 🆕 Уведомляем об изменении
    
    def remove_synonym(self, synonym):
        """🆕 Удаляет синоним и уведомляет подписчиков"""
        if synonym in self.synonyms:
            del self.synonyms[synonym]
            self.save_synonyms()
            self.notify_callbacks()  # 🆕 Уведомляем об изменении
    
    def get_preferred_value(self, value):
        """🔧 ИСПРАВЛЕНО: Возвращает предпочтительное значение для синонима"""
        if not value:
            return value
        
        value = value.strip()
        
        # Прямой поиск
        if value in self.synonyms:
            result = self.synonyms[value]
            print(f"🔄 Синоним найден: '{value}' → '{result}'")
            return result
        
        # Поиск без учета регистра
        value_lower = value.lower()
        for synonym, preferred in self.synonyms.items():
            if synonym.lower() == value_lower:
                print(f"🔄 Синоним найден (регистр): '{value}' → '{preferred}'")
                return preferred
        
        return value
    
    def merge_synonyms_in_list(self, values):
        """🔧 ИСПРАВЛЕНО: Объединяет синонимы в списке значений"""
        if not values:
            return []
        
        print(f"🔄 Обработка синонимов для списка: {values}")
        
        # Применяем замены синонимов
        processed_values = []
        seen_lower = set()
        
        for value in values:
            # Очищаем значение
            cleaned_value = clean_value_enhanced(value)
            
            # Пропускаем невалидные значения
            if not is_valid_value_enhanced(cleaned_value):
                print(f"⚠️ Пропущено невалидное значение: '{value}'")
                continue
            
            # Получаем предпочтительное значение
            preferred = self.get_preferred_value(cleaned_value)
            preferred_cleaned = clean_value_enhanced(preferred)
            
            # Проверяем дубликаты без учета регистра
            preferred_lower = preferred_cleaned.lower()
            if preferred_lower not in seen_lower:
                seen_lower.add(preferred_lower)
                processed_values.append(preferred_cleaned)
                print(f"✅ Добавлено значение: '{preferred_cleaned}'")
            else:
                print(f"🔄 Дубликат пропущен: '{preferred_cleaned}'")
        
        print(f"✅ Результат обработки синонимов: {processed_values}")
        return processed_values


class ValueSynonymsDialog:
    """Диалог для настройки синонимов значений"""
    def __init__(self, parent, synonyms_manager):
        self.synonyms_manager = synonyms_manager
        self.result = False
        
        self.dialog = Toplevel(parent)
        self.dialog.title("Настройки синонимов значений")
        self.dialog.geometry("600x400")
        self.dialog.transient(parent)
        self.dialog.grab_set()
        
        # Центрируем диалог
        self.dialog.geometry("+%d+%d" % (parent.winfo_rootx() + 50, parent.winfo_rooty() + 50))
        
        self.create_widgets()
        self.refresh_list()
        
        # Ждем закрытия диалога
        self.dialog.wait_window()
    
    def create_widgets(self):
        # Заголовок
        Label(self.dialog, text="🆕 Настройка синонимов значений (с автообновлением)", font=("Arial", 12, "bold")).pack(pady=10)
        Label(self.dialog, text="Синонимы автоматически заменяются на предпочтительные значения. Изменения применяются мгновенно!", font=("Arial", 9), fg="blue").pack(pady=5)
        
        # Основной фрейм
        main_frame = Frame(self.dialog)
        main_frame.pack(fill=BOTH, expand=True, padx=20, pady=10)
        
        # Левая часть - список синонимов
        left_frame = Frame(main_frame)
        left_frame.pack(side=LEFT, fill=BOTH, expand=True, padx=(0, 10))
        
        Label(left_frame, text="Текущие синонимы:", font=("Arial", 10, "bold")).pack(anchor=W)
        
        # Список с прокруткой
        list_frame = Frame(left_frame)
        list_frame.pack(fill=BOTH, expand=True, pady=5)
        
        scrollbar = Scrollbar(list_frame)
        scrollbar.pack(side=RIGHT, fill=Y)
        
        self.synonyms_listbox = Listbox(list_frame, yscrollcommand=scrollbar.set)
        self.synonyms_listbox.pack(side=LEFT, fill=BOTH, expand=True)
        scrollbar.config(command=self.synonyms_listbox.yview)
        
        # Кнопка удаления
        Button(left_frame, text="🗑️ Удалить выбранный", command=self.remove_synonym).pack(pady=5)
        
        # Правая часть - добавление синонимов
        right_frame = Frame(main_frame)
        right_frame.pack(side=RIGHT, fill=Y, padx=(10, 0))
        
        Label(right_frame, text="Добавить синоним:", font=("Arial", 10, "bold")).pack(anchor=W)
        
        Label(right_frame, text="Синоним (будет заменено):").pack(anchor=W, pady=(10, 0))
        self.synonym_entry = Entry(right_frame, width=25)
        self.synonym_entry.pack(fill=X, pady=2)
        
        Label(right_frame, text="Предпочтительное значение:").pack(anchor=W, pady=(10, 0))
        self.preferred_entry = Entry(right_frame, width=25)
        self.preferred_entry.pack(fill=X, pady=2)
        
        Button(right_frame, text="➕ Добавить", command=self.add_synonym).pack(pady=10)
        
        # Примеры
        examples_frame = LabelFrame(right_frame, text="Примеры", padx=5, pady=5)
        examples_frame.pack(fill=X, pady=10)
        
        Label(examples_frame, text="Support → Supported", font=("Arial", 8), fg="gray").pack(anchor=W)
        Label(examples_frame, text="Yes → Supported", font=("Arial", 8), fg="gray").pack(anchor=W)
        Label(examples_frame, text="Enable → Supported", font=("Arial", 8), fg="gray").pack(anchor=W)
        
        # 🆕 Индикатор динамических обновлений
        status_frame = LabelFrame(right_frame, text="🔄 Статус", padx=5, pady=5)
        status_frame.pack(fill=X, pady=5)
        
        Label(status_frame, text="✅ Автообновление включено", font=("Arial", 8), fg="green").pack(anchor=W)
        Label(status_frame, text="Изменения применяются сразу", font=("Arial", 8), fg="gray").pack(anchor=W)
        
        # Кнопки
        button_frame = Frame(self.dialog)
        button_frame.pack(pady=10)
        
        Button(button_frame, text="💾 Сохранить и закрыть", command=self.ok_clicked, width=15).pack(side=LEFT, padx=5)
        Button(button_frame, text="❌ Отмена", command=self.cancel_clicked, width=15).pack(side=LEFT, padx=5)
    
    def refresh_list(self):
        """Обновляет список синонимов"""
        self.synonyms_listbox.delete(0, END)
        for synonym, preferred in sorted(self.synonyms_manager.synonyms.items()):
            self.synonyms_listbox.insert(END, f"{synonym} → {preferred}")
    
    def add_synonym(self):
        """Добавляет новый синоним"""
        synonym = self.synonym_entry.get().strip()
        preferred = self.preferred_entry.get().strip()
        
        if not synonym or not preferred:
            messagebox.showwarning("Внимание", "Заполните оба поля")
            return
        
        if synonym == preferred:
            messagebox.showwarning("Внимание", "Синоним и предпочтительное значение не могут быть одинаковыми")
            return
        
        # 🆕 Добавляем синоним (автоматически триггерит динамическое обновление)
        self.synonyms_manager.add_synonym(synonym, preferred)
        self.refresh_list()
        
        self.synonym_entry.delete(0, END)
        self.preferred_entry.delete(0, END)
        
        messagebox.showinfo("✅ Обновлено", f"Синоним добавлен: {synonym} → {preferred}\n\n🔄 Основная таблица обновлена автоматически!")
    
    def remove_synonym(self):
        """Удаляет выбранный синоним"""
        selection = self.synonyms_listbox.curselection()
        if not selection:
            messagebox.showwarning("Внимание", "Выберите синоним для удаления")
            return
        
        item_text = self.synonyms_listbox.get(selection[0])
        synonym = item_text.split(" → ")[0]
        
        # 🆕 Удаляем синоним (автоматически триггерит динамическое обновление)
        self.synonyms_manager.remove_synonym(synonym)
        self.refresh_list()
        
        messagebox.showinfo("✅ Обновлено", f"Синоним удален: {synonym}\n\n🔄 Основная таблица обновлена автоматически!")
    
    def ok_clicked(self):
        self.result = True
        self.dialog.destroy()
    
    def cancel_clicked(self):
        self.result = False
        self.dialog.destroy()


def normalize_variant(val):
    val = re.sub(r'\s*\(\s*', ' (', val)
    val = re.sub(r'\s*\)\s*', ') ', val)
    val = re.sub(r'\s+', ' ', val).strip()
    return val


def normalize_value_format(val):
    """
    Стандартизирует форматирование значений:
    - Унифицирует пробелы вокруг символов (~, ×, >, <, и др.)
    - Приводит аббревиатуры к верхнему регистру
    - Добавляет пробелы между числами и единицами измерения
    """
    if not val or not isinstance(val, str):
        return val
    
    # Список единиц измерения (расширенный для IP-видеонаблюдения)
    units = [
        # Базовые физические величины
        'mm', 'cm', 'm', 'km', 'in', 'ft', 'yard', 'μm', 'nm', 'pm',  # расстояние
        'mg', 'g', 'kg', 'lb', 'oz',  # вес
        'ml', 'l', 'gal', 'pt', 'qt',  # объем
        
        # Время и частота
        's', 'ms', 'μs', 'ns', 'min', 'h', 'day',  # время
        'hz', 'khz', 'mhz', 'ghz',  # частота
        'fps',  # частота кадров
        
        # Электрические параметры
        'v', 'mv', 'kv', 'a', 'ma', 'μa', 'w', 'mw', 'kw', 'wh', 'kwh',  # электричество
        'ohm', 'ω', 'kohm', 'mohm',  # сопротивление
        'f', 'mf', 'μf', 'nf', 'pf',  # емкость
        
        # Данные и сети
        'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb',  # размер данных
        'bps', 'kbps', 'mbps', 'gbps', 'tbps',  # скорость передачи
        'bit', 'byte',  # основные единицы данных
        
        # Изображения и видео
        'mp', 'px', 'dpi', 'ppi', 'ppc',  # разрешение
        'lpi', 'lpmm',  # линейное разрешение
        
        # Звук и освещение
        'db', 'dba', 'dbc', 'dbi', 'dbm', 'dbw', 'dbfs',  # децибелы
        'lux', 'lm', 'cd', 'cd/m²', 'nit', 'fL',  # освещенность
        
        # Температура (только Цельсий!)
        '°', '°C', 'k',  # температура (убрали °f и исправили °c на °C)
        
        # Давление и влажность
        'pa', 'kpa', 'mpa', 'gpa', 'bar', 'mbar', 'psi', 'atm', 'torr', 'mmhg',  # давление
        'rh', '%rh',  # влажность
        
        # IP-видеонаблюдение специфичные
        'fov', 'aov', 'hfov', 'vfov',  # поле зрения
        'ir', 'uv',  # спектральные диапазоны
        'poe', 'poe+', 'poe++',  # питание по Ethernet
        
        # Прочие единицы
        '%', 'ppm', 'ppb', 'ppt',  # доли
        'rpm', 'rps',  # обороты
        'g-force', 'g'  # ускорение
    ]
    
    # Список распространенных аббревиатур
    abbreviations = [
        'onvif', 'poe', 'ip', 'tcp', 'udp', 'http', 'https', 'ftp', 'smtp',
        'dns', 'dhcp', 'wifi', 'lan', 'wan', 'usb', 'hdmi', 'vga', 'dvi',
        'led', 'lcd', 'oled', 'ccd', 'cmos', 'ir', 'uv', 'rgb', 'yuv',
        'pal', 'ntsc', 'mpeg', 'h264', 'h265', 'mjpeg', 'jpeg', 'png',
        'pdf', 'xml', 'json', 'api', 'sdk', 'gui', 'cli', 'os', 'ios',
        'ptz', 'dvr', 'nvr', 'hdd', 'ssd', 'ram', 'cpu', 'gpu',
        'ac', 'dc', 'rms', 'snr', 'thd', 'agc', 'awb', 'aec', 'roi',
        'rtsp', 'rtmp', 'sip', 'p2p', 'ddns', 'upnp', 'ntp', 'snmp'
    ]
    
    result = val
    
    # 1. Стандартизация пробелов вокруг специальных символов
    symbols = ['~', '×', '>', '<', '≥', '≤', '±', '÷', '−', '-', '+', '=']
    for symbol in symbols:
        # Убираем все пробелы вокруг символа, затем добавляем стандартные
        pattern = rf'\s*{re.escape(symbol)}\s*'
        result = re.sub(pattern, f' {symbol} ', result)
    
    # 2. Специальная обработка для диапазонов (чисел с единицами)
    # Например: "808nm ~ 940nm" -> "808 nm ~ 940 nm"
    for unit in units:
        # Паттерн для числа без пробела перед единицей
        pattern = rf'(\d+(?:\.\d+)?)\s*{re.escape(unit)}(?!\w)'
        replacement = rf'\1 {unit}'
        result = re.sub(pattern, replacement, result, flags=re.IGNORECASE)
    
    # 3. Обработка диапазонов типа "1/30,000s" -> "1/30,000 s"
    # Паттерн для дробей и сложных чисел с единицами
    pattern = r'(\d+(?:[,./]\d+)*)\s*([a-zA-Z]+)(?!\w)'
    def replace_number_unit(match):
        number = match.group(1)
        unit_candidate = match.group(2).lower()
        
        # Проверяем, является ли это единицей измерения
        if unit_candidate in [u.lower() for u in units]:
            return f'{number} {unit_candidate}'
        else:
            return match.group(0)  # Оставляем как есть
    
    result = re.sub(pattern, replace_number_unit, result)
    
    # 4. Приведение аббревиатур к верхнему регистру
    for abbr in abbreviations:
        # Ищем аббревиатуру как отдельное слово (с границами слов)
        pattern = rf'\b{re.escape(abbr)}\b'
        result = re.sub(pattern, abbr.upper(), result, flags=re.IGNORECASE)
    
    # 5. Обработка символа градуса с числами
    # "102,5°" -> "102,5 °", но только если нет буквы после (чтобы не трогать °C)
    result = re.sub(r'(\d+(?:[,.]\d+)?)\s*°(?![a-zA-Z])', r'\1 °', result)
    
    # 6. Убираем лишние пробелы
    result = re.sub(r'\s+', ' ', result).strip()
    
    return result


def clean_value_enhanced(value):
    """🔧 ИСПРАВЛЕНО: Улучшенная очистка значения от мусора"""
    if not value:
        return ""
    
    value = str(value).strip()
    
    # Удаляем HTML теги
    value = re.sub(r'<[^>]+>', '', value)
    
    # Удаляем лишние пробелы
    value = re.sub(r'\s+', ' ', value).strip()
    
    # 🔧 ИСПРАВЛЕНО: Удаляем некорректные значения
    if value in ['/', ';', '/;', '/', '&nbsp;', '-', '–', '—', '...', '.', ',', ':-', ':']:
        return ""
    
    # Удаляем значения состоящие только из символов-разделителей
    if re.match(r'^[/;,\s\-–—\.\:]+$', value):
        return ""
    
    return value


def is_valid_value_enhanced(value):
    """🔧 ИСПРАВЛЕНО: Проверяет, является ли значение валидным"""
    if not value:
        return False
    
    value = clean_value_enhanced(value)
    if not value:
        return False
    
    # Минимальная длина для валидного значения
    if len(value.strip()) < 1:
        return False
    
    return True


class ColumnSelectorDialog:
    def __init__(self, parent, columns, selected_columns=None):
        self.parent = parent
        self.columns = columns
        self.selected_columns = selected_columns or []
        self.result = None
        
        # Создаем модальное окно
        self.dialog = Toplevel(parent)
        self.dialog.title("Выбор дополнительных столбцов")
        self.dialog.geometry("500x600")
        self.dialog.grab_set()
        self.dialog.transient(parent)
        
        # Центрируем окно
        self.dialog.geometry("+%d+%d" % (parent.winfo_rootx() + 50, parent.winfo_rooty() + 50))
        
        # Создаем интерфейс
        self.setup_ui()
        
    def setup_ui(self):
        # Заголовок
        title_frame = Frame(self.dialog)
        title_frame.pack(fill=X, padx=10, pady=10)
        
        Label(title_frame, text="Выбор дополнительных столбцов", font=("Arial", 12, "bold")).pack()
        Label(title_frame, text="Выберите столбцы из исходного файла для включения в результат", 
              font=("Arial", 9)).pack(pady=(5, 0))
        
        # Индикатор выбранных столбцов
        self.status_label = Label(title_frame, text="", font=("Arial", 9), fg="blue")
        self.status_label.pack(pady=(5, 0))
        
        # Фрейм с чекбоксами и скроллбаром
        frame = Frame(self.dialog)
        frame.pack(fill=BOTH, expand=True, padx=10, pady=5)
        
        canvas = Canvas(frame)
        scrollbar = Scrollbar(frame, orient="vertical", command=canvas.yview)
        scrollable_frame = Frame(canvas)
        
        scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )
        
        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)
        
        # Словарь для хранения переменных чекбосов
        self.check_vars = {}
        
        # Создаем чекбоксы для каждого столбца
        for i, col in enumerate(self.columns, 1):
            var = BooleanVar()
            # Устанавливаем состояние чекбокса если столбец был выбран ранее
            if col in self.selected_columns:
                var.set(True)
            
            # Добавляем callback для обновления статуса
            var.trace('w', self.update_status)
            
            self.check_vars[col] = var
            cb = Checkbutton(scrollable_frame, text=f"{i}. {col}", variable=var, anchor=W, justify=LEFT)
            cb.pack(fill=X, padx=5, pady=1)
        
        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
        
        # Кнопки управления
        btn_frame = Frame(self.dialog)
        btn_frame.pack(fill=X, padx=10, pady=5)
        
        Button(btn_frame, text="Выбрать все", command=self.select_all).pack(side=LEFT, padx=5)
        Button(btn_frame, text="Снять все", command=self.deselect_all).pack(side=LEFT, padx=5)
        
        # Кнопки OK/Отмена
        btn_frame2 = Frame(self.dialog)
        btn_frame2.pack(fill=X, padx=10, pady=10)
        
        Button(btn_frame2, text="ОК", command=self.ok_clicked, width=10).pack(side=RIGHT, padx=5)
        Button(btn_frame2, text="Отмена", command=self.cancel_clicked, width=10).pack(side=RIGHT)
        
        # Обновляем статус при запуске
        self.update_status()
        
        # Обработка клавиш
        self.dialog.bind('<Return>', lambda e: self.ok_clicked())
        self.dialog.bind('<Escape>', lambda e: self.cancel_clicked())
        
    def update_status(self, *args):
        """Обновляет индикатор количества выбранных столбцов"""
        selected_count = sum(1 for var in self.check_vars.values() if var.get())
        total_count = len(self.check_vars)
        self.status_label.config(text=f"Выбрано столбцов: {selected_count} из {total_count}")
        
    def select_all(self):
        """Выбрать все столбцы"""
        for var in self.check_vars.values():
            var.set(True)
            
    def deselect_all(self):
        """Снять выбор со всех столбцов"""
        for var in self.check_vars.values():
            var.set(False)
        
    def ok_clicked(self):
        self.selected_columns = [col for col, var in self.check_vars.items() if var.get()]
        self.result = self.selected_columns
        self.dialog.destroy()
        
    def cancel_clicked(self):
        self.result = None
        self.dialog.destroy()


class ToolTip:
    def __init__(self, widget, text):
        self.widget = widget
        self.text = text
        self.tipwindow = None
        self.id = None
        self.x = self.y = 0

        self.widget.bind('<Enter>', self.enter)
        self.widget.bind('<Leave>', self.leave)

    def enter(self, event=None):
        if self.tipwindow:
            return
        x, y, cx, cy = self.widget.bbox("insert")
        x = x + self.widget.winfo_rootx() + 25
        y = y + cy + self.widget.winfo_rooty() + 25
        self.tipwindow = tw = Toplevel(self.widget)
        tw.wm_overrideredirect(1)
        tw.wm_geometry("+%d+%d" % (x, y))
        label = Label(tw, text=self.text, justify=LEFT,
                      background="#ffffe0", relief=SOLID, borderwidth=1,
                      font=("tahoma", "8", "normal"))
        label.pack(ipadx=1)

    def leave(self, event=None):
        tw = self.tipwindow
        self.tipwindow = None
        if tw:
            tw.destroy()


class CaseNormalizationDialog:
    def __init__(self, parent, case_normalization, save_callback):
        self.parent = parent
        self.case_normalization = case_normalization.copy()
        self.save_callback = save_callback
        
        self.top = Toplevel(parent)
        self.top.title("Управление нормализацией регистра")
        self.top.geometry("800x600")
        self.top.grab_set()
        
        # Основной фрейм
        main_frame = Frame(self.top)
        main_frame.pack(fill=BOTH, expand=True, padx=10, pady=10)
        
        # Инструкция
        info_text = """Эта функция позволяет установить предпочтительный регистр для значений.
Варианты в разном регистре (например "Support", "SUPPORT", "support") будут считаться одинаковыми.
Добавьте значение в том регистре, который должен использоваться в итоговой таблице."""
        
        info_label = Label(main_frame, text=info_text, justify=LEFT, wraplength=750)
        info_label.pack(pady=(0, 10))
        
        # Фрейм для добавления новых значений
        add_frame = LabelFrame(main_frame, text="Добавить новое значение")
        add_frame.pack(fill=X, pady=(0, 10))
        
        Label(add_frame, text="Значение:").grid(row=0, column=0, sticky=W, padx=5, pady=5)
        self.new_value_entry = Entry(add_frame, width=40)
        self.new_value_entry.grid(row=0, column=1, sticky=W+E, padx=5, pady=5)
        
        self.add_button = Button(add_frame, text="Добавить", command=self.add_value)
        self.add_button.grid(row=0, column=2, padx=5, pady=5)
        
        add_frame.grid_columnconfigure(1, weight=1)
        
        # Таблица существующих значений
        table_frame = LabelFrame(main_frame, text="Существующие значения")
        table_frame.pack(fill=BOTH, expand=True)
        
        # Создаем Treeview с прокруткой
        tree_frame = Frame(table_frame)
        tree_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)
        
        self.tree = ttk.Treeview(tree_frame, columns=('value',), show='headings')
        self.tree.heading('value', text='Предпочтительный регистр')
        self.tree.column('value', width=300)
        
        vsb = Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=vsb.set)
        
        self.tree.pack(side=LEFT, fill=BOTH, expand=True)
        vsb.pack(side=RIGHT, fill=Y)
        
        # Заполняем таблицу
        self.refresh_tree()
        
        # Привязываем события
        self.tree.bind('<Delete>', self.delete_selected)
        self.tree.bind('<Double-1>', self.edit_selected)
        self.new_value_entry.bind('<Return>', lambda e: self.add_value())
        
        # Кнопки управления
        btn_frame = Frame(table_frame)
        btn_frame.pack(fill=X, padx=5, pady=5)
        
        Button(btn_frame, text="Редактировать", command=self.edit_selected).pack(side=LEFT, padx=5)
        Button(btn_frame, text="Удалить", command=self.delete_selected).pack(side=LEFT, padx=5)
        Button(btn_frame, text="Очистить все", command=self.clear_all).pack(side=LEFT, padx=15)
        
        # Кнопки диалога
        dialog_btn_frame = Frame(self.top)
        dialog_btn_frame.pack(fill=X, pady=10)
        
        Button(dialog_btn_frame, text="Сохранить", command=self.save_and_close).pack(side=RIGHT, padx=5)
        Button(dialog_btn_frame, text="Отмена", command=self.top.destroy).pack(side=RIGHT)
        
    def refresh_tree(self):
        # Очищаем дерево
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        # Заполняем значениями из словаря
        for value in sorted(self.case_normalization.keys()):
            self.tree.insert('', END, values=(value,))
    
    def add_value(self):
        value = self.new_value_entry.get().strip()
        if not value:
            return
        
        # Добавляем значение в словарь (само на себя)
        self.case_normalization[value] = value
        
        # Обновляем таблицу
        self.refresh_tree()
        
        # Очищаем поле ввода
        self.new_value_entry.delete(0, END)
    
    def edit_selected(self, event=None):
        selection = self.tree.selection()
        if not selection:
            return
        
        item = selection[0]
        old_value = self.tree.item(item, 'values')[0]
        
        # Диалог редактирования
        new_value = simpledialog.askstring("Редактирование", 
                                          "Введите новое значение:", 
                                          initialvalue=old_value)
        if new_value and new_value.strip():
            new_value = new_value.strip()
            # Удаляем старое и добавляем новое
            del self.case_normalization[old_value]
            self.case_normalization[new_value] = new_value
            self.refresh_tree()
    
    def delete_selected(self, event=None):
        selection = self.tree.selection()
        if not selection:
            return
        
        for item in selection:
            value = self.tree.item(item, 'values')[0]
            if value in self.case_normalization:
                del self.case_normalization[value]
        
        self.refresh_tree()
    
    def clear_all(self):
        if messagebox.askyesno("Подтверждение", "Удалить все значения?"):
            self.case_normalization.clear()
            self.refresh_tree()
    
    def save_and_close(self):
        self.save_callback(self.case_normalization)
        self.top.destroy()


class ValueTranslationRuleDialog:
    """Диалог для создания/редактирования правила перевода значений"""
    def __init__(self, parent, rule=None):
        self.parent = parent
        self.result = None
        
        # Создаем модальное окно
        self.dialog = Toplevel(parent)
        self.dialog.title("Правило перевода значений" if not rule else "Редактирование правила")
        self.dialog.geometry("600x400")
        self.dialog.transient(parent)
        self.dialog.grab_set()
        
        # Центрируем диалог
        self.dialog.geometry("+%d+%d" % (parent.winfo_rootx() + 100, parent.winfo_rooty() + 100))
        
        # Инициализируем данные
        self.rule_data = rule.copy() if rule else {
            "active": True,
            "original": "",
            "translation": "",
            "category": "Пользовательские",
            "description": "",
            "examples": ""
        }
        
        self.create_widgets()
        self.load_data()
        
        # Ждем закрытия диалога
        self.dialog.wait_window()
    
    def create_widgets(self):
        """Создает виджеты диалога"""
        main_frame = Frame(self.dialog)
        main_frame.pack(fill=BOTH, expand=True, padx=10, pady=10)
        
        # Заголовок
        header_frame = Frame(main_frame)
        header_frame.pack(fill=X, pady=(0, 15))
        
        Label(header_frame, text="🌐 Настройка правила перевода значений", 
              font=("Arial", 12, "bold")).pack()
        Label(header_frame, text="Создайте правило для автоматического перевода значений в столбце 'Результат разделения'",
              font=("Arial", 9), fg="gray").pack()
        
        # Основные поля
        fields_frame = LabelFrame(main_frame, text="📝 Основные параметры", font=("Arial", 10, "bold"))
        fields_frame.pack(fill=X, pady=(0, 10))
        
        # Активность
        active_frame = Frame(fields_frame)
        active_frame.pack(fill=X, padx=10, pady=5)
        
        self.active_var = BooleanVar(value=True)
        Checkbutton(active_frame, text="✓ Правило активно", variable=self.active_var,
                   font=("Arial", 10)).pack(anchor=W)
        
        # Исходное значение
        orig_frame = Frame(fields_frame)
        orig_frame.pack(fill=X, padx=10, pady=5)
        
        Label(orig_frame, text="Исходное значение:", width=20, anchor=W).pack(side=LEFT)
        self.original_entry = Entry(orig_frame, font=("Arial", 10))
        self.original_entry.pack(side=LEFT, fill=X, expand=True, padx=(5, 0))
        
        # Перевод
        trans_frame = Frame(fields_frame)
        trans_frame.pack(fill=X, padx=10, pady=5)
        
        Label(trans_frame, text="Перевод:", width=20, anchor=W).pack(side=LEFT)
        self.translation_entry = Entry(trans_frame, font=("Arial", 10))
        self.translation_entry.pack(side=LEFT, fill=X, expand=True, padx=(5, 0))
        
        # Категория
        cat_frame = Frame(fields_frame)
        cat_frame.pack(fill=X, padx=10, pady=5)
        
        Label(cat_frame, text="Категория:", width=20, anchor=W).pack(side=LEFT)
        self.category_var = StringVar()
        self.category_combo = ttk.Combobox(cat_frame, textvariable=self.category_var, 
                                         values=["Длина", "Вес", "Электричество", "Частота", 
                                               "Освещенность", "Термины", "Логические", "Пользовательские"],
                                         font=("Arial", 10))
        self.category_combo.pack(side=LEFT, fill=X, expand=True, padx=(5, 0))
        
        # Дополнительные поля
        details_frame = LabelFrame(main_frame, text="📋 Дополнительная информация", font=("Arial", 10, "bold"))
        details_frame.pack(fill=BOTH, expand=True, pady=(0, 10))
        
        # Описание
        desc_frame = Frame(details_frame)
        desc_frame.pack(fill=X, padx=10, pady=5)
        
        Label(desc_frame, text="Описание:", anchor=W).pack(anchor=W)
        self.description_entry = Entry(desc_frame, font=("Arial", 10))
        self.description_entry.pack(fill=X, pady=(2, 0))
        
        # Примеры
        examples_frame = Frame(details_frame)
        examples_frame.pack(fill=BOTH, expand=True, padx=10, pady=5)
        
        Label(examples_frame, text="Примеры использования:", anchor=W).pack(anchor=W)
        
        text_frame = Frame(examples_frame)
        text_frame.pack(fill=BOTH, expand=True, pady=(2, 0))
        
        self.examples_text = Text(text_frame, height=4, font=("Arial", 9))
        examples_scroll = Scrollbar(text_frame, orient=VERTICAL, command=self.examples_text.yview)
        self.examples_text.configure(yscrollcommand=examples_scroll.set)
        
        self.examples_text.pack(side=LEFT, fill=BOTH, expand=True)
        examples_scroll.pack(side=RIGHT, fill=Y)
        
        # Кнопки
        buttons_frame = Frame(main_frame)
        buttons_frame.pack(fill=X, pady=(10, 0))
        
        Button(buttons_frame, text="💾 Сохранить", command=self.save_rule,
               bg="#E8F5E8", font=("Arial", 10, "bold")).pack(side=RIGHT, padx=(5, 0))
        
        Button(buttons_frame, text="❌ Отмена", command=self.cancel,
               bg="#FFE6E6", font=("Arial", 10)).pack(side=RIGHT, padx=(5, 5))
        
        # Кнопка помощи
        Button(buttons_frame, text="❓ Помощь", command=self.show_help,
               bg="#FFF4E6", font=("Arial", 10)).pack(side=LEFT)
        
        # Привязки событий
        self.original_entry.bind('<KeyRelease>', self.auto_generate_example)
        self.translation_entry.bind('<KeyRelease>', self.auto_generate_example)
    
    def load_data(self):
        """Загружает данные в поля"""
        self.active_var.set(self.rule_data.get("active", True))
        self.original_entry.insert(0, self.rule_data.get("original", ""))
        self.translation_entry.insert(0, self.rule_data.get("translation", ""))
        self.category_var.set(self.rule_data.get("category", "Пользовательские"))
        self.description_entry.insert(0, self.rule_data.get("description", ""))
        self.examples_text.insert("1.0", self.rule_data.get("examples", ""))
    
    def auto_generate_example(self, event=None):
        """Автоматически генерирует пример при вводе"""
        original = self.original_entry.get().strip()
        translation = self.translation_entry.get().strip()
        
        if original and translation:
            # Генерируем пример
            if re.match(r'^[a-zA-Z]+$', original):  # Если это единица измерения
                example = f"100{original} → 100 {translation}"
            else:
                example = f"{original} → {translation}"
            
            # Обновляем поле примеров если оно пустое
            current_examples = self.examples_text.get("1.0", "end-1c").strip()
            if not current_examples:
                self.examples_text.delete("1.0", END)
                self.examples_text.insert("1.0", example)
    
    def save_rule(self):
        """Сохраняет правило"""
        # Валидация
        original = self.original_entry.get().strip()
        translation = self.translation_entry.get().strip()
        
        if not original:
            messagebox.showerror("Ошибка", "Введите исходное значение")
            self.original_entry.focus()
            return
        
        if not translation:
            messagebox.showerror("Ошибка", "Введите перевод")
            self.translation_entry.focus()
            return
        
        # Сохраняем данные
        self.result = {
            "active": self.active_var.get(),
            "original": original,
            "translation": translation,
            "category": self.category_var.get() or "Пользовательские",
            "description": self.description_entry.get().strip() or f"Перевод '{original}' → '{translation}'",
            "examples": self.examples_text.get("1.0", "end-1c").strip() or f"{original} → {translation}"
        }
        
        self.dialog.destroy()
    
    def cancel(self):
        """Отменяет изменения"""
        self.result = None
        self.dialog.destroy()
    
    def show_help(self):
        """Показывает справку"""
        help_text = """🌐 Справка по правилам перевода значений

📋 Основные поля:
• Исходное значение - текст, который нужно переводить (например: "mm", "Support")
• Перевод - на что заменять (например: "мм", "Поддержка")  
• Категория - группировка правил для удобства

📝 Принципы работы:
• Правила применяются к значениям в столбце "Результат разделения"
• Поиск выполняется точно по тексту (с учетом регистра)
• Неактивные правила игнорируются

💡 Примеры использования:
• Единицы: "mm" → "мм", "kg" → "кг"
• Термины: "Auto" → "Автоматически" 
• Логические: "Yes" → "Да", "No" → "Нет"

🔍 Автопоиск единиц:
Используйте кнопку "Найти единицы" для автоматического обнаружения 
единиц измерения в ваших данных."""
        
        messagebox.showinfo("Справка", help_text)


class ReplacementRulesEditorDialog:
    def __init__(self, parent, replacement_rules, save_callback):
        self.parent = parent
        self.replacement_rules = replacement_rules.copy()
        self.save_callback = save_callback
        
        self.top = Toplevel(parent)
        self.top.title("Редактор правил замены")
        self.top.geometry("900x700")
        self.top.grab_set()
        
        # Основной фрейм
        main_frame = Frame(self.top)
        main_frame.pack(fill=BOTH, expand=True, padx=10, pady=10)
        
        # Инструкция
        info_text = """Правила замены позволяют автоматически изменять значения с помощью регулярных выражений.
Используйте Python-синтаксис для регулярных выражений.
Примеры:
- Удаление скобок: Паттерн: \\([^\\)]*\\)  Замена: (пустая строка)
- Замена "Yes" на "Да": Паттерн: \\bYes\\b  Замена: Да
- Нормализация пробелов: Паттерн: \\s+  Замена: (один пробел)"""
        
        info_label = Label(main_frame, text=info_text, justify=LEFT, wraplength=850)
        info_label.pack(pady=(0, 10))
        
        # Фрейм для добавления нового правила
        add_frame = LabelFrame(main_frame, text="Добавить новое правило")
        add_frame.pack(fill=X, pady=(0, 10))
        
        Label(add_frame, text="Название:").grid(row=0, column=0, sticky=W, padx=5, pady=2)
        self.name_entry = Entry(add_frame, width=30)
        self.name_entry.grid(row=0, column=1, sticky=W+E, padx=5, pady=2)
        
        Label(add_frame, text="Активно:").grid(row=0, column=2, sticky=W, padx=5, pady=2)
        self.active_var = BooleanVar(value=True)
        self.active_check = Checkbutton(add_frame, variable=self.active_var)
        self.active_check.grid(row=0, column=3, sticky=W, padx=5, pady=2)
        
        Label(add_frame, text="Паттерн (regex):").grid(row=1, column=0, sticky=W, padx=5, pady=2)
        self.pattern_entry = Entry(add_frame, width=60)
        self.pattern_entry.grid(row=1, column=1, columnspan=3, sticky=W+E, padx=5, pady=2)
        
        Label(add_frame, text="Замена:").grid(row=2, column=0, sticky=W, padx=5, pady=2)
        self.replacement_entry = Entry(add_frame, width=60)
        self.replacement_entry.grid(row=2, column=1, columnspan=3, sticky=W+E, padx=5, pady=2)
        
        self.add_button = Button(add_frame, text="Добавить правило", command=self.add_rule)
        self.add_button.grid(row=3, column=0, columnspan=4, pady=10)
        
        add_frame.grid_columnconfigure(1, weight=1)
        
        # Таблица существующих правил
        table_frame = LabelFrame(main_frame, text="Существующие правила")
        table_frame.pack(fill=BOTH, expand=True)
        
        # Создаем Treeview с прокруткой
        tree_frame = Frame(table_frame)
        tree_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)
        
        columns = ('active', 'name', 'pattern', 'replacement')
        self.tree = ttk.Treeview(tree_frame, columns=columns, show='headings')
        self.tree.heading('active', text='Активно')
        self.tree.heading('name', text='Название')
        self.tree.heading('pattern', text='Паттерн')
        self.tree.heading('replacement', text='Замена')
        
        self.tree.column('active', width=60, anchor=CENTER)
        self.tree.column('name', width=150)
        self.tree.column('pattern', width=300)
        self.tree.column('replacement', width=200)
        
        vsb = Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=vsb.set)
        
        self.tree.pack(side=LEFT, fill=BOTH, expand=True)
        vsb.pack(side=RIGHT, fill=Y)
        
        # Заполняем таблицу
        self.refresh_tree()
        
        # Привязываем события
        self.tree.bind('<Delete>', self.delete_selected)
        self.tree.bind('<Double-1>', self.edit_selected)
        self.tree.bind('<Button-1>', self.on_click)
        
        # Кнопки управления
        btn_frame = Frame(table_frame)
        btn_frame.pack(fill=X, padx=5, pady=5)
        
        Button(btn_frame, text="Редактировать", command=self.edit_selected).pack(side=LEFT, padx=5)
        Button(btn_frame, text="Удалить", command=self.delete_selected).pack(side=LEFT, padx=5)
        Button(btn_frame, text="Переместить вверх", command=self.move_up).pack(side=LEFT, padx=5)
        Button(btn_frame, text="Переместить вниз", command=self.move_down).pack(side=LEFT, padx=5)
        Button(btn_frame, text="Очистить все", command=self.clear_all).pack(side=LEFT, padx=15)
        
        # 🆕 Дополнительные кнопки
        btn_frame2 = Frame(table_frame)
        btn_frame2.pack(fill=X, padx=5, pady=5)
        
        Button(btn_frame2, text="📥 Импорт правил", command=self.import_rules).pack(side=LEFT, padx=5)
        Button(btn_frame2, text="📤 Экспорт правил", command=self.export_rules).pack(side=LEFT, padx=5)
        
        # Кнопка тестирования
        test_frame = Frame(table_frame)
        test_frame.pack(fill=X, padx=5, pady=5)
        
        Label(test_frame, text="Тестовая строка:").pack(side=LEFT)
        self.test_entry = Entry(test_frame, width=40)
        self.test_entry.pack(side=LEFT, padx=5, fill=X, expand=True)
        Button(test_frame, text="Тестировать", command=self.test_rules).pack(side=LEFT, padx=5)
        
        # Кнопки диалога
        dialog_btn_frame = Frame(self.top)
        dialog_btn_frame.pack(fill=X, pady=10)
        
        Button(dialog_btn_frame, text="Сохранить", command=self.save_and_close).pack(side=RIGHT, padx=5)
        Button(dialog_btn_frame, text="Отмена", command=self.top.destroy).pack(side=RIGHT)
        
    def refresh_tree(self):
        # Очищаем дерево
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        # Заполняем правилами
        for rule in self.replacement_rules:
            active_text = "✓" if rule.get('active', True) else "✗"
            self.tree.insert('', END, values=(
                active_text,
                rule.get('name', ''),
                rule.get('pattern', ''),
                rule.get('replacement', '')
            ))
    
    def on_click(self, event):
        region = self.tree.identify("region", event.x, event.y)
        if region == "cell":
            col = self.tree.identify_column(event.x)
            row = self.tree.identify_row(event.y)
            
            if col == '#1':  # Колонка "Активно"
                self.toggle_active(row)
    
    def toggle_active(self, row_id):
        if not row_id:
            return
        
        # Находим индекс строки
        children = self.tree.get_children()
        try:
            index = children.index(row_id)
            rule = self.replacement_rules[index]
            rule['active'] = not rule.get('active', True)
            self.refresh_tree()
        except (ValueError, IndexError):
            pass
    
    def add_rule(self):
        name = self.name_entry.get().strip()
        pattern = self.pattern_entry.get().strip()
        replacement = self.replacement_entry.get()
        
        if not name or not pattern:
            messagebox.showwarning("Ошибка", "Заполните название и паттерн")
            return
        
        # Проверяем валидность регулярного выражения
        try:
            re.compile(pattern)
        except re.error as e:
            messagebox.showerror("Ошибка", f"Неверное регулярное выражение: {e}")
            return
        
        # Добавляем правило
        rule = {
            'name': name,
            'pattern': pattern,
            'replacement': replacement,
            'active': self.active_var.get()
        }
        self.replacement_rules.append(rule)
        
        # Обновляем таблицу
        self.refresh_tree()
        
        # Очищаем поля ввода
        self.name_entry.delete(0, END)
        self.pattern_entry.delete(0, END)
        self.replacement_entry.delete(0, END)
    
    def edit_selected(self, event=None):
        selection = self.tree.selection()
        if not selection:
            return
        
        # Находим индекс выбранного правила
        children = self.tree.get_children()
        try:
            index = children.index(selection[0])
            rule = self.replacement_rules[index]
        except (ValueError, IndexError):
            return
        
        # Создаем диалог редактирования
        edit_dialog = Toplevel(self.top)
        edit_dialog.title("Редактирование правила")
        edit_dialog.geometry("600x200")
        edit_dialog.grab_set()
        
        Label(edit_dialog, text="Название:").grid(row=0, column=0, sticky=W, padx=5, pady=5)
        name_var = StringVar(value=rule.get('name', ''))
        name_entry = Entry(edit_dialog, textvariable=name_var, width=40)
        name_entry.grid(row=0, column=1, sticky=W+E, padx=5, pady=5)
        
        Label(edit_dialog, text="Паттерн:").grid(row=1, column=0, sticky=W, padx=5, pady=5)
        pattern_var = StringVar(value=rule.get('pattern', ''))
        pattern_entry = Entry(edit_dialog, textvariable=pattern_var, width=40)
        pattern_entry.grid(row=1, column=1, sticky=W+E, padx=5, pady=5)
        
        Label(edit_dialog, text="Замена:").grid(row=2, column=0, sticky=W, padx=5, pady=5)
        replacement_var = StringVar(value=rule.get('replacement', ''))
        replacement_entry = Entry(edit_dialog, textvariable=replacement_var, width=40)
        replacement_entry.grid(row=2, column=1, sticky=W+E, padx=5, pady=5)
        
        active_var = BooleanVar(value=rule.get('active', True))
        active_check = Checkbutton(edit_dialog, text="Активно", variable=active_var)
        active_check.grid(row=3, column=1, sticky=W, padx=5, pady=5)
        
        def save_edit():
            new_name = name_var.get().strip()
            new_pattern = pattern_var.get().strip()
            new_replacement = replacement_var.get()
            
            if not new_name or not new_pattern:
                messagebox.showwarning("Ошибка", "Заполните название и паттерн")
                return
            
            try:
                re.compile(new_pattern)
            except re.error as e:
                messagebox.showerror("Ошибка", f"Неверное регулярное выражение: {e}")
                return
            
            # Обновляем правило
            rule['name'] = new_name
            rule['pattern'] = new_pattern
            rule['replacement'] = new_replacement
            rule['active'] = active_var.get()
            
            self.refresh_tree()
            edit_dialog.destroy()
        
        btn_frame = Frame(edit_dialog)
        btn_frame.grid(row=4, column=0, columnspan=2, pady=10)
        Button(btn_frame, text="Сохранить", command=save_edit).pack(side=RIGHT, padx=5)
        Button(btn_frame, text="Отмена", command=edit_dialog.destroy).pack(side=RIGHT)
        
        edit_dialog.grid_columnconfigure(1, weight=1)
    
    def delete_selected(self, event=None):
        selection = self.tree.selection()
        if not selection:
            return
        
        if messagebox.askyesno("Подтверждение", "Удалить выбранные правила?"):
            # Получаем индексы выбранных элементов
            children = self.tree.get_children()
            indices = []
            for item in selection:
                try:
                    indices.append(children.index(item))
                except ValueError:
                    pass
            
            # Удаляем в обратном порядке, чтобы не сбить индексы
            for index in sorted(indices, reverse=True):
                del self.replacement_rules[index]
            
            self.refresh_tree()
    
    def move_up(self):
        selection = self.tree.selection()
        if not selection:
            return
        
        children = self.tree.get_children()
        try:
            index = children.index(selection[0])
            if index > 0:
                # Меняем местами правила
                self.replacement_rules[index], self.replacement_rules[index-1] = \
                    self.replacement_rules[index-1], self.replacement_rules[index]
                self.refresh_tree()
                # Восстанавливаем выделение
                new_item = self.tree.get_children()[index-1]
                self.tree.selection_set(new_item)
        except (ValueError, IndexError):
            pass
    
    def move_down(self):
        selection = self.tree.selection()
        if not selection:
            return
        
        children = self.tree.get_children()
        try:
            index = children.index(selection[0])
            if index < len(self.replacement_rules) - 1:
                # Меняем местами правила
                self.replacement_rules[index], self.replacement_rules[index+1] = \
                    self.replacement_rules[index+1], self.replacement_rules[index]
                self.refresh_tree()
                # Восстанавливаем выделение
                new_item = self.tree.get_children()[index+1]
                self.tree.selection_set(new_item)
        except (ValueError, IndexError):
            pass
    
    def clear_all(self):
        if messagebox.askyesno("Подтверждение", "Удалить все правила?"):
            self.replacement_rules.clear()
            self.refresh_tree()
    
    def test_rules(self):
        test_string = self.test_entry.get()
        if not test_string:
            return
        
        result = test_string
        applied_rules = []
        
        for rule in self.replacement_rules:
            if not rule.get('active', True):
                continue
            
            try:
                old_result = result
                result = re.sub(rule['pattern'], rule['replacement'], result)
                if result != old_result:
                    applied_rules.append(rule['name'])
            except re.error:
                continue
        
        # Показываем результат
        result_dialog = Toplevel(self.top)
        result_dialog.title("Результат тестирования")
        result_dialog.geometry("500x300")
        
        Label(result_dialog, text="Исходная строка:", font=("Arial", 10, "bold")).pack(anchor=W, padx=10, pady=(10,0))
        Label(result_dialog, text=test_string, wraplength=450, justify=LEFT).pack(anchor=W, padx=20)
        
        Label(result_dialog, text="Результат:", font=("Arial", 10, "bold")).pack(anchor=W, padx=10, pady=(10,0))
        Label(result_dialog, text=result, wraplength=450, justify=LEFT).pack(anchor=W, padx=20)
        
        if applied_rules:
            Label(result_dialog, text="Примененные правила:", font=("Arial", 10, "bold")).pack(anchor=W, padx=10, pady=(10,0))
            for rule_name in applied_rules:
                Label(result_dialog, text=f"• {rule_name}").pack(anchor=W, padx=20)
        else:
            Label(result_dialog, text="Правила не применены", font=("Arial", 10, "bold")).pack(anchor=W, padx=10, pady=(10,0))
        
        Button(result_dialog, text="Закрыть", command=result_dialog.destroy).pack(pady=10)
    
    def import_rules(self):
        """Импорт правил из JSON файла"""
        try:
            from tkinter import filedialog
            
            filename = filedialog.askopenfilename(
                title="Выберите файл с правилами",
                filetypes=[("JSON файлы", "*.json"), ("Все файлы", "*.*")]
            )
            
            if not filename:
                return
            
            with open(filename, 'r', encoding='utf-8') as f:
                imported_rules = json.load(f)
            
            if not isinstance(imported_rules, list):
                messagebox.showerror("Ошибка", "Неверный формат файла. Ожидается список правил.")
                return
            
            # Проверяем структуру правил
            valid_rules = []
            for rule in imported_rules:
                if isinstance(rule, dict) and 'name' in rule and 'pattern' in rule and 'replacement' in rule:
                    # Проверяем валидность regex
                    try:
                        re.compile(rule['pattern'])
                        valid_rules.append({
                            'name': rule['name'],
                            'pattern': rule['pattern'],
                            'replacement': rule['replacement'],
                            'active': rule.get('active', True)
                        })
                    except re.error:
                        print(f"Пропущено невалидное правило: {rule['name']}")
            
            if not valid_rules:
                messagebox.showwarning("Предупреждение", "В файле не найдено валидных правил")
                return
            
            # Показываем диалог выбора правил для импорта
            self.show_import_dialog(valid_rules)
            
        except Exception as e:
            messagebox.showerror("Ошибка", f"Ошибка при импорте правил: {e}")
    
    def show_import_dialog(self, rules_to_import):
        """Показывает диалог выбора правил для импорта"""
        dialog = Toplevel(self.top)
        dialog.title("Импорт правил")
        dialog.geometry("700x500")
        dialog.transient(self.top)
        dialog.grab_set()
        
        # Центрируем диалог
        dialog.update_idletasks()
        x = (dialog.winfo_screenwidth() // 2) - (dialog.winfo_width() // 2)
        y = (dialog.winfo_screenheight() // 2) - (dialog.winfo_height() // 2)
        dialog.geometry(f"+{x}+{y}")
        
        Label(dialog, text=f"Найдено {len(rules_to_import)} правил для импорта", 
              font=("Arial", 12, "bold")).pack(pady=10)
        
        # Фрейм со списком
        list_frame = Frame(dialog)
        list_frame.pack(fill=BOTH, expand=True, padx=10, pady=5)
        
        # Список правил с чекбоксами
        canvas = Canvas(list_frame)
        scrollbar = Scrollbar(list_frame, orient="vertical", command=canvas.yview)
        scrollable_frame = Frame(canvas)
        
        scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )
        
        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)
        
        # Переменные для чекбоксов
        check_vars = []
        for i, rule in enumerate(rules_to_import):
            var = BooleanVar(value=True)
            check_vars.append(var)
            
            frame = Frame(scrollable_frame)
            frame.pack(fill=X, pady=2)
            
            Checkbutton(frame, variable=var).pack(side=LEFT)
            Label(frame, text=f"{rule['name']}: {rule['pattern']} → {rule['replacement']}", 
                  anchor="w").pack(side=LEFT, fill=X, expand=True)
        
        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
        
        # Кнопки
        btn_frame = Frame(dialog)
        btn_frame.pack(fill=X, padx=10, pady=10)
        
        def import_selected():
            imported_count = 0
            for i, var in enumerate(check_vars):
                if var.get():
                    rule = rules_to_import[i]
                    # Проверяем, не существует ли уже такое правило
                    existing = any(r['name'] == rule['name'] for r in self.replacement_rules)
                    if not existing:
                        self.replacement_rules.append(rule)
                        imported_count += 1
            
            if imported_count > 0:
                self.refresh_tree()
                messagebox.showinfo("Успех", f"Импортировано {imported_count} правил")
            else:
                messagebox.showinfo("Информация", "Не импортировано ни одного правила")
            
            dialog.destroy()
        
        Button(btn_frame, text="✓ Выбрать все", 
               command=lambda: [var.set(True) for var in check_vars]).pack(side=LEFT, padx=5)
        Button(btn_frame, text="✗ Снять все", 
               command=lambda: [var.set(False) for var in check_vars]).pack(side=LEFT, padx=5)
        Button(btn_frame, text="📥 Импортировать выбранные", command=import_selected).pack(side=RIGHT, padx=5)
        Button(btn_frame, text="❌ Отмена", command=dialog.destroy).pack(side=RIGHT, padx=5)
    
    def export_rules(self):
        """Экспорт правил в JSON файл"""
        try:
            from tkinter import filedialog
            
            if not self.replacement_rules:
                messagebox.showwarning("Предупреждение", "Нет правил для экспорта")
                return
            
            filename = filedialog.asksaveasfilename(
                title="Сохранить правила",
                defaultextension=".json",
                filetypes=[("JSON файлы", "*.json"), ("Все файлы", "*.*")]
            )
            
            if not filename:
                return
            
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(self.replacement_rules, f, ensure_ascii=False, indent=2)
            
            messagebox.showinfo("Успех", f"Правила экспортированы в файл:\\n{filename}")
            
        except Exception as e:
            messagebox.showerror("Ошибка", f"Ошибка при экспорте правил: {e}")
    
    def save_and_close(self):
        self.save_callback(self.replacement_rules)
        self.top.destroy()


class AttributeEditor:
    MAPPINGS_FILE = 'mappings.json'
    EXCLUSIONS_FILE = 'exclusions.json'
    PROTECTED_VALUES_FILE = 'protected_values.json'  # 🆕 Защищенные значения от разделения
    SETTINGS_FILE = 'settings.json'
    CASE_NORM_FILE = 'case_normalization.json'
    REPLACEMENT_RULES_FILE = 'replacement_rules.json'
    PARAM_MERGE_SETTINGS_FILE = 'param_merge_settings.json'
    VALUE_MERGE_SETTINGS_FILE = 'value_merge_settings.json'

    def __init__(self, root):
        self.root = root
        self.root.title("🆕 Редактор атрибутов товаров v4.9 - Система динамических обновлений")
        self.root.geometry("1200x700")  # Уменьшили размер окна благодаря компактности
        self.original_df = None
        self.param_mapping = {}
        self.original_values = {}
        self.manual_split_values = {}
        self.exclusions = []
        self.mappings = {}
        self.sort_directions = {}
        self.edit_entry = None
        self.edit_undo_stack = []
        self.similar_groups = {}
        self.SIMILARITY_THRESHOLD = 85
        self.df_columns = []

        self.mapping_sort_reverse = False
        self._mapping_items_cache = []

        self.settings = {
            "features_col": "",
            "extra_cols": [],
            "manual_split_values": {},
            "source_file_path": "",
            "output_folder_path": ""
        }

        self.case_normalization = {}
        self.manual_split_results = {}
        self.replacement_rules = []
        
        # 🆕 ЗАЩИЩЕННЫЕ ЗНАЧЕНИЯ: не разделяются, даже если содержат символ разделителя
        self.protected_values = []  # Список значений типа "B/W", "I/O", "AC/DC"

        # Настройки объединения - автосохранение/загрузка
        self.param_merge_settings = {}
        self.value_merge_settings = {}
        
        # 🆕 НОВЫЕ ВОЗМОЖНОСТИ v4.0
        # Менеджер синонимов значений
        self.synonyms_manager = ValueSynonymsManager()
        
        # 🆕 СИСТЕМА ДИНАМИЧЕСКИХ ОБНОВЛЕНИЙ v4.9
        # Регистрируем callback для автоматического обновления при изменении синонимов
        self.synonyms_manager.add_callback(self.dynamic_update_main_table)
        
        # 🆕 НОВЫЕ ВОЗМОЖНОСТИ v4.7
        # Система переводов значений и единиц измерения
        self.value_translation_rules = []
        self.load_value_translation_rules()
        
        # 🌐 РАСШИРЕННАЯ СИСТЕМА ЕДИНИЦ ИЗМЕРЕНИЙ IP-ВИДЕОНАБЛЮДЕНИЯ
        self.ip_surveillance_units = {}
        self.load_ip_surveillance_units()
        
        # 🌐 ИНИЦИАЛИЗАЦИЯ СИСТЕМЫ ПЕРЕВОДОВ
        self.translation_available = TRANSLATION_AVAILABLE  # Сохраняем как атрибут класса
        # DEBUG: Инициализация переводов завершена
        if self.translation_available:
            self.translation_system = TranslationSystem()
            print("✅ Система переводов инициализирована")
        else:
            self.translation_system = None
            print("⚠️ Работа без системы переводов")
        
        # Цвета для выделения похожих параметров
        self.similar_param_colors = [
            "#FFE6E6",  # Светло-розовый
            "#E6F3FF",  # Светло-синий  
            "#E6FFE6",  # Светло-зеленый
            "#FFF0E6",  # Светло-оранжевый
            "#F0E6FF",  # Светло-фиолетовый
            "#FFFFE6",  # Светло-желтый
            "#E6FFFF",  # Светло-бирюзовый
            "#FFE6F0",  # Светло-магента
        ]
        self.param_color_map = {}  # {param_name: color}
        self.similar_param_groups = {}  # {representative: [similar_params]}

        self.context_menu = Menu(self.root, tearoff=0)
        self.context_menu.add_command(label="Копировать", command=self.copy_selected_value)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="Объединить выделенные параметры", command=self.merge_selected_similar_params)
        self.context_menu.add_command(label="Объединить параметры и их значения", command=self.merge_selected_params_and_values)
        
        # 🆕 Контекстное меню для вкладки "Объединение параметров"
        self.params_context_menu = Menu(self.root, tearoff=0)
        self.params_context_menu.add_command(label="📝 Редактировать группу", command=self.edit_param_group)
        self.params_context_menu.add_command(label="🗑️ Удалить из группы", command=self.remove_from_param_group)
        self.params_context_menu.add_separator()
        self.params_context_menu.add_command(label="❌ Удалить всю группу", command=self.delete_param_group)
        
        # Для отслеживания частоты использования параметров
        self.param_frequency = {}

        self.load_settings()
        self.load_mappings()
        self.load_excl()
        self.load_protected_values()  # 🆕 Загрузка защищенных значений
        self.load_case_normalization()
        self.load_replacement_rules()
        self.load_merge_settings()
        self.setup_ui()
        
        # Инициализируем кеш маппингов
        self.refresh_mapping_table()

        self.root.protocol("WM_DELETE_WINDOW", self.on_close)

    def setup_ui(self):
        """Создание компактного и эргономичного интерфейса"""
        # DEBUG: Настройка интерфейса
        # Горячие клавиши
        self.root.bind_all("<Control-c>", self.global_ctrl_c)
        self.root.bind_all("<Control-v>", self.global_ctrl_v)
        self.root.bind_all("<Control-z>", self.global_ctrl_z)
        self.root.bind_all("<Control-a>", self.global_ctrl_a)
        self.root.bind_all("<Delete>", self.global_delete)
        
        # Создаем расширенное меню
        self.create_compact_menu()
        
        # Главный контейнер
        main_frame = Frame(self.root)
        main_frame.pack(fill=BOTH, expand=True, padx=5, pady=2)

        # 📁 ГРУППА 1: Файлы и настройки (компактно в одной строке)
        files_frame = LabelFrame(main_frame, text="📁 Файлы и настройки", font=("Arial", 9, "bold"))
        files_frame.pack(fill=X, pady=(0, 2))
        
        # Строка 1: Исходный файл
        file_row1 = Frame(files_frame)
        file_row1.pack(fill=X, padx=5, pady=2)
        
        Label(file_row1, text="Файл:", width=6).pack(side=LEFT)
        self.source_file_var = StringVar(value=self.settings.get("source_file_path", ""))
        source_entry = Entry(file_row1, textvariable=self.source_file_var, width=35)
        source_entry.pack(side=LEFT, fill=X, expand=True, padx=2)
        source_entry.bind('<FocusOut>', lambda e: self.save_settings())
        Button(file_row1, text="📂", width=3, command=self.browse_source_file).pack(side=LEFT, padx=2)
        ToolTip(source_entry, "Путь к исходному Excel файлу")
        
        # Вторая часть строки: основные настройки
        Label(file_row1, text="Столбец:", width=8).pack(side=LEFT, padx=(15, 2))
        self.features_col = Entry(file_row1, width=4)
        self.features_col.pack(side=LEFT)
        self.features_col.insert(0, self.settings.get("features_col", ""))
        ToolTip(self.features_col, "Номер столбца с HTML-характеристиками")
        
        Label(file_row1, text="Доп.столбцы:", width=10).pack(side=LEFT, padx=(15, 2))
        self.extra_cols_label = Label(file_row1, text="", width=12, anchor=W, relief=SUNKEN, bg="white")
        self.extra_cols_label.pack(side=LEFT, padx=2)
        Button(file_row1, text="⚙️", width=3, command=self.show_extra_cols_dialog).pack(side=LEFT, padx=2)
        ToolTip(self.extra_cols_label, "Дополнительные столбцы для сохранения")
        
        # Строка 2: Папка сохранения (более компактно)
        file_row2 = Frame(files_frame)
        file_row2.pack(fill=X, padx=5, pady=(0, 2))
        
        Label(file_row2, text="Папка:", width=6).pack(side=LEFT)
        self.output_folder_var = StringVar(value=self.settings.get("output_folder_path", ""))
        output_entry = Entry(file_row2, textvariable=self.output_folder_var, width=35)
        output_entry.pack(side=LEFT, fill=X, expand=True, padx=2)
        output_entry.bind('<FocusOut>', lambda e: self.save_settings())
        Button(file_row2, text="📂", width=3, command=self.browse_output_folder).pack(side=LEFT, padx=2)
        ToolTip(output_entry, "Папка для сохранения результатов")
        
        # 🔧 ГРУППА 2: Панель инструментов (компактная)
        toolbar_frame = LabelFrame(main_frame, text="🔧 Действия", font=("Arial", 9, "bold"))
        toolbar_frame.pack(fill=X, pady=(0, 2))
        
        toolbar = Frame(toolbar_frame)
        toolbar.pack(fill=X, padx=5, pady=3)
        
        # Основные действия (крупные кнопки)
        actions_frame = Frame(toolbar)
        actions_frame.pack(side=LEFT)
        
        self.btn_load = Button(actions_frame, text="📥 Загрузить", command=self.load_file, 
                              bg="#E8F5E8", font=("Arial", 9, "bold"), width=12)
        self.btn_load.pack(side=LEFT, padx=2)
        ToolTip(self.btn_load, "Загрузить Excel файл")
        
        self.btn_process = Button(actions_frame, text="⚙️ Обработать", command=self.process_table,
                                 bg="#FFF4E6", font=("Arial", 9, "bold"), width=12)
        self.btn_process.pack(side=LEFT, padx=2)
        ToolTip(self.btn_process, "Обработать таблицу и извлечь параметры")
        
        self.btn_save = Button(actions_frame, text="💾 Сохранить", command=self.save_results,
                              bg="#E6F3FF", font=("Arial", 9, "bold"), width=12)
        self.btn_save.pack(side=LEFT, padx=2)
        ToolTip(self.btn_save, "Сохранить результат в Excel")
        
        # 🌐 КНОПКА ПЕРЕВОДОВ (если доступна)
        if self.translation_available:
            self.btn_translate = Button(actions_frame, text="🌐 Переводы", command=self.open_translation_dialog,
                                      bg="#F0E6FF", font=("Arial", 9, "bold"), width=12)
            self.btn_translate.pack(side=LEFT, padx=2)
            try:
                ToolTip(self.btn_translate, "Функциональное меню переводов:\n• Перевести параметры\n• Перевести значения\n• Настройки переводов\n• Импорт/экспорт")
            except Exception as e:
                print(f"Ошибка создания ToolTip для кнопки переводов: {e}")
        
        # Быстрые настройки
        # Быстрые настройки (мелкие кнопки)
        try:
            settings_frame = Frame(toolbar)
            settings_frame.pack(side=RIGHT)
            print("🔍 DEBUG: settings_frame создан")
        except Exception as e:
            print(f"🔍 DEBUG: Ошибка создания settings_frame: {e}")
        
        try:
            Label(settings_frame, text="Настройки:", font=("Arial", 8)).pack(side=LEFT, padx=(10, 5))
            print("🔍 DEBUG: Label 'Настройки' создан")
        except Exception as e:
            print(f"🔍 DEBUG: Ошибка Label 'Настройки': {e}")
        
        try:
            self.btn_replacement_rules = Button(settings_frame, text="🔄", width=3, command=self.open_replacement_rules_editor,
                   bg="#FFE6E6")
            self.btn_replacement_rules.pack(side=LEFT, padx=1)
            ToolTip(self.btn_replacement_rules, "Правила замен")
            print("🔍 DEBUG: btn_replacement_rules создана")
        except Exception as e:
            print(f"🔍 DEBUG: Ошибка btn_replacement_rules: {e}")
        
        self.btn_case_norm = Button(settings_frame, text="Aa", width=3, command=self.open_case_normalization_dialog,
               bg="#E6FFE6")
        self.btn_case_norm.pack(side=LEFT, padx=1)
        ToolTip(self.btn_case_norm, "Нормализация регистра")
        
        self.btn_synonyms = Button(settings_frame, text="≈", width=3, command=self.open_value_synonyms_dialog,
               bg="#E6F7FF")
        self.btn_synonyms.pack(side=LEFT, padx=1)
        ToolTip(self.btn_synonyms, "Синонимы значений")
        
        self.btn_protected_values = Button(settings_frame, text="🛡️", width=3, command=self.open_protected_values_dialog,
               bg="#FFE6CC")
        self.btn_protected_values.pack(side=LEFT, padx=1)
        ToolTip(self.btn_protected_values, "Защищенные значения")
        
        # Восстанавливаем выбранные столбцы
        if self.settings.get("extra_cols"):
            col_count = len(self.settings['extra_cols'])
            self.extra_cols_label.config(text=f"({col_count})")
            self.extra_cols_label.config(bg="lightgreen")
            ToolTip(self.extra_cols_label, f"Выбрано {col_count} столбцов")

        # 📊 ОСНОВНАЯ РАБОЧАЯ ОБЛАСТЬ (вкладки)
        self.notebook = ttk.Notebook(main_frame)
        self.notebook.pack(fill=BOTH, expand=True, pady=2)

        # Вкладка 1: Основная таблица
        self.main_tab = Frame(self.notebook)
        self.notebook.add(self.main_tab, text="📋 Основная таблица")
        self.setup_main_tab()

        # Вкладка 2: Объединение параметров
        self.params_merge_tab = Frame(self.notebook)
        self.notebook.add(self.params_merge_tab, text="🔄 Объединение параметров")
        self.setup_params_merge_tab()

        # 🆕 Вкладка 3: Переводы значений и единиц измерения
        self.values_translation_tab = Frame(self.notebook)
        self.notebook.add(self.values_translation_tab, text="🌐 Переводы значений")
        self.setup_values_translation_tab()

        # Информационная панель внизу (компактная)
        self.info_label = Label(main_frame, text="✅ Готов к работе", relief=SUNKEN, anchor=W, 
                               font=("Arial", 8), bg="#F0F0F0")
        self.info_label.pack(fill=X, pady=(2, 0))

    def create_compact_menu(self):
        """Создает компактное меню с перенесенными функциями"""
        menubar = Menu(self.root)
        self.root.config(menu=menubar)
        
        # Меню "Файл"
        file_menu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="📁 Файл", menu=file_menu)
        file_menu.add_command(label="📥 Загрузить файл", command=self.load_file, accelerator="Ctrl+O")
        file_menu.add_command(label="⚙️ Обработать таблицу", command=self.process_table, accelerator="Ctrl+P")
        file_menu.add_separator()
        file_menu.add_command(label="💾 Сохранить результат", command=self.save_results, accelerator="Ctrl+S")
        file_menu.add_separator()
        file_menu.add_command(label="🚪 Выход", command=self.on_close, accelerator="Ctrl+Q")
        
        # Меню "Настройки" (перенесенные функции)
        settings_menu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="⚙️ Настройки", menu=settings_menu)
        settings_menu.add_command(label="🔄 Правила замен", command=self.open_replacement_rules_editor)
        settings_menu.add_command(label="Aa Нормализация регистра", command=self.open_case_normalization_dialog)
        settings_menu.add_command(label="≈ Синонимы значений", command=self.open_value_synonyms_dialog)
        settings_menu.add_command(label="🛡️ Защищенные значения", command=self.open_protected_values_dialog)
        settings_menu.add_separator()
        settings_menu.add_command(label="📋 Управление исключениями", command=self.open_exclusions_dialog)
        
        # 🌐 МЕНЮ "ПЕРЕВОДЫ" (если доступна система)
        print(f"🔍 DEBUG: Проверка меню - self.translation_available = {self.translation_available}")
        if self.translation_available:
            print("🔍 DEBUG: СОЗДАЕМ меню переводов!")
            translation_menu = Menu(menubar, tearoff=0)
            menubar.add_cascade(label="🌐 Переводы", menu=translation_menu)
            translation_menu.add_command(label="⚙️ Настройки переводов", command=self.open_translation_dialog)
            translation_menu.add_separator()
            translation_menu.add_command(label="🔄 Перевести текущие параметры", command=self.translate_current_parameters)
            translation_menu.add_command(label="🔄 Перевести текущие значения", command=self.translate_current_values)
            translation_menu.add_separator()
            translation_menu.add_command(label="💾 Экспорт переводов", command=self.export_translations)
            translation_menu.add_command(label="📂 Импорт переводов", command=self.import_translations)
            print("🔍 DEBUG: Меню переводов создано!")
        else:
            print("🔍 DEBUG: НЕ создаем меню - условие False")
        
        # Меню "Инструменты"
        tools_menu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="🔧 Инструменты", menu=tools_menu)
        tools_menu.add_command(label="🔍 Найти похожие параметры", command=self.detect_similar_parameters)
        tools_menu.add_command(label="📤 Экспорт маппингов", command=self.export_mappings)
        tools_menu.add_command(label="📥 Импорт маппингов", command=self.import_mappings)
        tools_menu.add_separator()
        tools_menu.add_command(label="🧹 Нормализация значений", command=self.normalize_all_values_format)
        
        # Меню "Помощь"
        help_menu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="❓ Помощь", menu=help_menu)
        help_menu.add_command(label="📚 Справка", command=self.show_help_dialog, accelerator="F1")
        help_menu.add_separator()
        help_menu.add_command(label="ℹ️ О программе", command=self.show_about_dialog)
        help_menu.add_command(label="⌨️ Горячие клавиши", command=self.show_hotkeys_dialog)

        # Горячие клавиши
        self.root.bind('<Control-o>', lambda e: self.load_file())
        self.root.bind('<Control-p>', lambda e: self.process_table())
        self.root.bind('<Control-s>', lambda e: self.save_results())
        self.root.bind('<Control-q>', lambda e: self.on_close())
        self.root.bind('<F1>', lambda e: self.show_help_dialog())

    def setup_main_tab(self):
        """Настройка основной вкладки с таблицей"""
        tree_frame = Frame(self.main_tab)
        tree_frame.pack(fill=BOTH, expand=True, pady=5)

        vsb = Scrollbar(tree_frame, orient="vertical")
        vsb.pack(side=RIGHT, fill=Y)

        columns = ('rownum', 'orig_param', 'orig_values', 'new_param', 'delimiter', 'split_result')
        self.tree = ttk.Treeview(tree_frame, columns=columns, show='headings', height=15, yscrollcommand=vsb.set)
        self.tree.pack(side=LEFT, fill=BOTH, expand=True)
        vsb.config(command=self.tree.yview)

        self.tree.heading('rownum', text='№')
        self.tree.heading('orig_param', text='Исходный параметр')
        self.tree.heading('orig_values', text='Исходные значения')
        self.tree.heading('new_param', text='Новый параметр')
        self.tree.heading('delimiter', text='Разделитель')
        self.tree.heading('split_result', text='Результат разделения')

        self.tree.column('rownum', width=40, anchor=CENTER)
        self.tree.column('orig_param', width=130)
        self.tree.column('orig_values', width=250)
        self.tree.column('new_param', width=130)
        self.tree.column('delimiter', width=70)
        self.tree.column('split_result', width=350)

        self.tree.bind("<Double-1>", self.on_double_click)
        self.tree.bind("<Button-3>", self.show_context_menu)
        self.tree.bind("<Button-1>", self.sort_column, True)
        self.tree.bind("<Control-c>", self.copy_selected_value)
        self.tree.bind("<Control-a>", self.select_all_rows)

        # Таблица сопоставлений
        mapping_frame = LabelFrame(self.main_tab, text="Таблица сопоставлений и поиск")
        mapping_frame.pack(fill=X, pady=5)

        search_frame = Frame(mapping_frame)
        search_frame.pack(fill=X, padx=5, pady=2)
        Label(search_frame, text="Поиск по Исходным значениям:").pack(side=LEFT)
        self.search_var = StringVar()
        self.search_var.trace_add('write', self.filter_mapping_table)
        self.search_entry = Entry(search_frame, textvariable=self.search_var)
        self.search_entry.pack(side=LEFT, fill=X, expand=True, padx=5)

        mapping_vsb = Scrollbar(mapping_frame, orient="vertical")
        mapping_vsb.pack(side=RIGHT, fill=Y)

        self.mapping_tree = ttk.Treeview(mapping_frame, columns=('original', 'replacement'), show='headings', height=6,
                                         selectmode='extended',
                                         yscrollcommand=mapping_vsb.set)
        self.mapping_tree.heading('original', text='Исходное значение', command=self.sort_mapping_tree_by_original_col)
        self.mapping_tree.heading('replacement', text='Замена')
        self.mapping_tree.column('original', width=250)
        self.mapping_tree.column('replacement', width=250)
        self.mapping_tree.pack(side=LEFT, fill=BOTH, expand=True, padx=5)
        mapping_vsb.config(command=self.mapping_tree.yview)

        self.mapping_tree.bind("<Double-1>", self.on_mapping_double_click)
        self.mapping_tree.bind("<Control-a>", self.select_all_mappings)
        self.mapping_tree.bind("<Delete>", self.delete_selected_mappings)
        self.mapping_tree.bind("<Control-c>", self.copy_mapping_selected_value)

        mapping_buttons = Frame(mapping_frame)
        mapping_buttons.pack(side=LEFT, padx=5, fill=Y)

        btn_add = Button(mapping_buttons, text="Добавить", command=self.add_mapping)
        btn_add.pack(fill=X, pady=2)
        btn_del = Button(mapping_buttons, text="Удалить", command=self.delete_mapping)
        btn_del.pack(fill=X, pady=2)
        btn_select_all = Button(mapping_buttons, text="Выделить все", command=self.select_all_mappings)
        btn_select_all.pack(fill=X, pady=2)
        ToolTip(btn_select_all, "Выделить все сопоставления в таблице")
        
        btn_delete_all = Button(mapping_buttons, text="Удалить все", command=self.delete_all_mappings)
        btn_delete_all.pack(fill=X, pady=2)
        ToolTip(btn_delete_all, "Удалить все сопоставления из таблицы")
        
        btn_import = Button(mapping_buttons, text="Импорт", command=self.import_mappings)
        btn_import.pack(fill=X, pady=2)
        btn_export = Button(mapping_buttons, text="Экспорт", command=self.export_mappings)
        btn_export.pack(fill=X, pady=2)

        # Список исключений
        exclusions_frame = LabelFrame(self.main_tab, text="Список исключений (двойной клик - редактирование)")
        exclusions_frame.pack(fill=X, pady=5)

        self.exclusions_tree = ttk.Treeview(exclusions_frame, columns=('exclusion',), show='headings', height=3)
        self.exclusions_tree.heading('exclusion', text='Исключение')
        self.exclusions_tree.column('exclusion', width=400)
        self.exclusions_tree.pack(side=LEFT, fill=BOTH, expand=True)
        self.exclusions_tree.bind("<Double-1>", self.on_exclusion_double_click)
        self.exclusions_tree.bind("<Control-c>", self.copy_exclusion_selected_value)

        exclusions_buttons = Frame(exclusions_frame)
        exclusions_buttons.pack(side=LEFT, padx=5, fill=Y)

        btn_add_exclusion = Button(exclusions_buttons, text="Добавить", command=self.add_exclusion)
        btn_add_exclusion.pack(fill=X, pady=2)
        btn_del_exclusion = Button(exclusions_buttons, text="Удалить", command=self.delete_exclusion)
        btn_del_exclusion.pack(fill=X, pady=2)

        self.update_exclusions_tree()

    def setup_params_merge_tab(self):
        """Настройка вкладки объединения параметров"""
        # Инструкция
        info_frame = LabelFrame(self.params_merge_tab, text="Информация")
        info_frame.pack(fill=X, padx=5, pady=5)
        
        info_text = """Эта вкладка позволяет найти и объединить похожие параметры для исправления опечаток.
Обнаруженные группы похожих параметров будут автоматически сохранены в настройках.
При следующем запуске приложения настройки будут восстановлены."""
        
        Label(info_frame, text=info_text, justify=LEFT, wraplength=800).pack(padx=10, pady=5)

        # Кнопки управления
        control_frame = Frame(self.params_merge_tab)
        control_frame.pack(fill=X, padx=5, pady=5)

        self.btn_find_similar_params = Button(control_frame, text="Найти похожие параметры", 
                                             command=self.find_similar_parameters_for_tab)
        self.btn_find_similar_params.pack(side=LEFT, padx=5)
        
        self.btn_apply_param_merges = Button(control_frame, text="Применить выбранные объединения", 
                                           command=self.apply_param_merges_from_tab)
        self.btn_apply_param_merges.pack(side=LEFT, padx=5)
        
        self.btn_clear_param_settings = Button(control_frame, text="Очистить настройки", 
                                              command=self.clear_param_merge_settings)
        self.btn_clear_param_settings.pack(side=LEFT, padx=15)

        # Таблица похожих параметров
        table_frame = LabelFrame(self.params_merge_tab, text="Похожие параметры")
        table_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)

        tree_frame = Frame(table_frame)
        tree_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)

        columns = ('merge', 'representative', 'similar_params', 'frequency')
        self.params_tree = ttk.Treeview(tree_frame, columns=columns, show='headings')
        self.params_tree.pack(side=LEFT, fill=BOTH, expand=True)

        params_vsb = Scrollbar(tree_frame, orient="vertical", command=self.params_tree.yview)
        params_vsb.pack(side=RIGHT, fill=Y)
        self.params_tree.configure(yscrollcommand=params_vsb.set)

        # 🆕 СОРТИРОВКА ПО ЗАГОЛОВКАМ: Настраиваем заголовки с возможностью сортировки
        self.params_tree.heading('merge', text='Объединить')
        self.params_tree.heading('representative', text='Основной параметр ↕', 
                                 command=lambda: self.sort_params_tree('representative'))
        self.params_tree.heading('similar_params', text='Похожие параметры ↕', 
                                 command=lambda: self.sort_params_tree('similar_params'))
        self.params_tree.heading('frequency', text='Частота ↕', 
                                 command=lambda: self.sort_params_tree('frequency'))

        self.params_tree.column('merge', width=80, anchor=CENTER)
        self.params_tree.column('representative', width=250, anchor=W)
        self.params_tree.column('similar_params', width=350, anchor=W)
        self.params_tree.column('frequency', width=100, anchor=CENTER)

        # Переменная для отслеживания направления сортировки
        self.params_tree_sort_direction = {}  # {column: 'asc'/'desc'}
        
        self.params_tree.bind('<Button-1>', self.on_params_tree_click)
        self.params_tree.bind('<Button-3>', self.show_params_context_menu)  # 🆕 ПКМ для редактирования

        # Кнопки выбора
        btn_frame = Frame(table_frame)
        btn_frame.pack(fill=X, padx=5, pady=5)

        Button(btn_frame, text="Выбрать все", command=self.select_all_params).pack(side=LEFT, padx=5)
        Button(btn_frame, text="Снять все", command=self.deselect_all_params).pack(side=LEFT, padx=5)

    def setup_values_merge_tab(self):
        """Настройка вкладки объединения значений"""
        # Инструкция
        info_frame = LabelFrame(self.values_merge_tab, text="Информация")
        info_frame.pack(fill=X, padx=5, pady=5)
        
        info_text = """Эта вкладка позволяет найти и объединить похожие значения внутри параметров.
Например: "Support"/"Supported", "Yes"/"YES", "Нет"/"No" и т.д.
Обнаруженные группы похожих значений будут автоматически сохранены в настройках."""
        
        Label(info_frame, text=info_text, justify=LEFT, wraplength=800).pack(padx=10, pady=5)

        # Кнопки управления
        control_frame = Frame(self.values_merge_tab)
        control_frame.pack(fill=X, padx=5, pady=5)

        self.btn_find_similar_values = Button(control_frame, text="Найти похожие значения", 
                                            command=self.find_similar_values_for_tab)
        self.btn_find_similar_values.pack(side=LEFT, padx=5)
        
        self.btn_apply_value_merges = Button(control_frame, text="Применить выбранные объединения", 
                                           command=self.apply_value_merges_from_tab)
        self.btn_apply_value_merges.pack(side=LEFT, padx=5)
        
        self.btn_clear_value_settings = Button(control_frame, text="Очистить настройки", 
                                              command=self.clear_value_merge_settings)
        self.btn_clear_value_settings.pack(side=LEFT, padx=15)

        # Таблица похожих значений
        table_frame = LabelFrame(self.values_merge_tab, text="Похожие значения")
        table_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)

        tree_frame = Frame(table_frame)
        tree_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)

        columns = ('merge', 'parameter', 'representative', 'similar_values', 'frequency')
        self.values_tree = ttk.Treeview(tree_frame, columns=columns, show='headings')
        self.values_tree.pack(side=LEFT, fill=BOTH, expand=True)

        values_vsb = Scrollbar(tree_frame, orient="vertical", command=self.values_tree.yview)
        values_vsb.pack(side=RIGHT, fill=Y)
        self.values_tree.configure(yscrollcommand=values_vsb.set)

        self.values_tree.heading('merge', text='Объединить')
        self.values_tree.heading('parameter', text='Параметр')
        self.values_tree.heading('representative', text='Основное значение')
        self.values_tree.heading('similar_values', text='Похожие значения')
        self.values_tree.heading('frequency', text='Частота')

        self.values_tree.column('merge', width=80, anchor=CENTER)
        self.values_tree.column('parameter', width=200, anchor=W)
        self.values_tree.column('representative', width=150, anchor=W)
        self.values_tree.column('similar_values', width=300, anchor=W)
        self.values_tree.column('frequency', width=100, anchor=CENTER)

        self.values_tree.bind('<Button-1>', self.on_values_tree_click)

        # Кнопки выбора
        btn_frame = Frame(table_frame)
        btn_frame.pack(fill=X, padx=5, pady=5)

        Button(btn_frame, text="Выбрать все", command=self.select_all_values).pack(side=LEFT, padx=5)
        Button(btn_frame, text="Снять все", command=self.deselect_all_values).pack(side=LEFT, padx=5)

    def setup_values_translation_tab(self):
        """🆕 Настройка вкладки переводов значений и единиц измерения"""
        # Инициализируем правила перевода значений если их нет
        if not hasattr(self, 'value_translation_rules'):
            self.value_translation_rules = []
            self.load_value_translation_rules()
        
        # Заголовок с описанием
        header_frame = LabelFrame(self.values_translation_tab, text="ℹ️ Информация", font=("Arial", 10, "bold"))
        header_frame.pack(fill=X, padx=5, pady=5)
        
        info_text = """🌐 Переводы значений и единиц измерения
        
Эта вкладка позволяет настроить правила перевода значений:
• Единицы измерения: mm → мм, kg → кг, Wh → Вт⋅ч
• Технические термины: Support → Поддержка, Auto → Автоматически  
• Любые другие значения: Yes → Да, No → Нет

При нажатии "Обработать" система автоматически найдет значения с единицами измерения 
и предложит создать правила перевода."""
        
        Label(header_frame, text=info_text, justify=LEFT, wraplength=900, font=("Arial", 9)).pack(padx=10, pady=5)
        
        # Панель управления
        control_frame = LabelFrame(self.values_translation_tab, text="🔧 Управление", font=("Arial", 10, "bold"))
        control_frame.pack(fill=X, padx=5, pady=5)
        
        buttons_frame = Frame(control_frame)
        buttons_frame.pack(fill=X, padx=5, pady=5)
        
        # Кнопки управления
        Button(buttons_frame, text="➕ Добавить правило", command=self.add_value_translation_rule,
               bg="#E8F5E8", font=("Arial", 9)).pack(side=LEFT, padx=5)
        
        Button(buttons_frame, text="✏️ Редактировать", command=self.edit_value_translation_rule,
               bg="#FFF4E6", font=("Arial", 9)).pack(side=LEFT, padx=5)
        
        Button(buttons_frame, text="🗑️ Удалить", command=self.delete_value_translation_rule,
               bg="#FFE6E6", font=("Arial", 9)).pack(side=LEFT, padx=5)
        
        Button(buttons_frame, text="🔍 Найти единицы", command=self.auto_detect_units,
               bg="#E6F3FF", font=("Arial", 9, "bold")).pack(side=LEFT, padx=15)
        
        Button(buttons_frame, text="💾 Экспорт", command=self.export_value_translations,
               bg="#F0E6FF", font=("Arial", 9)).pack(side=LEFT, padx=5)
        
        Button(buttons_frame, text="📥 Импорт", command=self.import_value_translations,
               bg="#F0E6FF", font=("Arial", 9)).pack(side=LEFT, padx=5)
        
        # Таблица правил перевода
        table_frame = LabelFrame(self.values_translation_tab, text="📋 Правила перевода значений", font=("Arial", 10, "bold"))
        table_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)
        
        # Создаем фрейм для таблицы и скроллбара
        tree_frame = Frame(table_frame)
        tree_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)
        
        # Настройка колонок
        columns = ('active', 'original', 'translation', 'category', 'description', 'examples')
        self.translation_tree = ttk.Treeview(tree_frame, columns=columns, show='headings', height=15)
        
        # Настройка заголовков
        self.translation_tree.heading('active', text='✓')
        self.translation_tree.heading('original', text='Исходное значение')  
        self.translation_tree.heading('translation', text='Перевод')
        self.translation_tree.heading('category', text='Категория')
        self.translation_tree.heading('description', text='Описание')
        self.translation_tree.heading('examples', text='Примеры')
        
        # Настройка ширины колонок
        self.translation_tree.column('active', width=30, anchor=CENTER)
        self.translation_tree.column('original', width=150, anchor=W)
        self.translation_tree.column('translation', width=150, anchor=W)
        self.translation_tree.column('category', width=100, anchor=W)
        self.translation_tree.column('description', width=200, anchor=W)
        self.translation_tree.column('examples', width=200, anchor=W)
        
        # Скроллбары
        v_scrollbar = Scrollbar(tree_frame, orient=VERTICAL, command=self.translation_tree.yview)
        h_scrollbar = Scrollbar(tree_frame, orient=HORIZONTAL, command=self.translation_tree.xview)
        self.translation_tree.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
        
        # Размещение элементов
        self.translation_tree.pack(side=LEFT, fill=BOTH, expand=True)
        v_scrollbar.pack(side=RIGHT, fill=Y)
        h_scrollbar.pack(side=BOTTOM, fill=X)
        
        # Привязка событий v4.8
        self.translation_tree.bind('<Double-1>', lambda e: self.edit_value_translation_rule())
        self.translation_tree.bind('<Button-3>', self.show_translation_context_menu)
        self.translation_tree.bind('<Button-1>', self.on_translation_tree_click)
        
        # Кнопки выбора v4.8
        selection_frame = Frame(table_frame)
        selection_frame.pack(fill=X, padx=5, pady=5)
        
        # 🆕 Массовые операции с выбранными правилами v4.8
        Button(selection_frame, text="✓ Активировать выбранные", command=self.activate_selected_rules,
               bg="#E8F5E8", font=("Arial", 9)).pack(side=LEFT, padx=2)
        Button(selection_frame, text="✗ Деактивировать выбранные", command=self.deactivate_selected_rules,
               bg="#FFE6E6", font=("Arial", 9)).pack(side=LEFT, padx=2)
        
        # Разделитель
        Label(selection_frame, text=" | ", font=("Arial", 9)).pack(side=LEFT, padx=5)
        
        Button(selection_frame, text="✓ Активировать все", command=self.activate_all_translation_rules,
               font=("Arial", 8)).pack(side=LEFT, padx=2)
        Button(selection_frame, text="✗ Деактивировать все", command=self.deactivate_all_translation_rules,
               font=("Arial", 8)).pack(side=LEFT, padx=2)
        
        # Статистика v4.8
        stats_right = Frame(selection_frame)
        stats_right.pack(side=RIGHT, padx=5)
        
        self.translation_stats_label = Label(stats_right, text="Правил: 0 | Активных: 0", 
                                           font=("Arial", 9), fg="blue")
        self.translation_stats_label.pack(side=TOP)
        
        self.selected_count_label = Label(stats_right, text="Выбрано: 0", 
                                        font=("Arial", 9), fg="green")
        self.selected_count_label.pack(side=TOP)
        
        # Загружаем и отображаем правила
        self.refresh_translation_rules_display()

    def load_value_translation_rules(self):
        """Загружает правила перевода значений из файла"""
        try:
            if os.path.exists("value_translation_rules.json"):
                with open("value_translation_rules.json", "r", encoding="utf-8") as f:
                    self.value_translation_rules = json.load(f)
                print(f"✅ Загружено {len(self.value_translation_rules)} правил перевода значений")
            else:
                # Создаем правила по умолчанию
                self.value_translation_rules = self.get_default_translation_rules()
                self.save_value_translation_rules()
                print(f"✅ Созданы правила перевода по умолчанию: {len(self.value_translation_rules)} записей")
        except Exception as e:
            print(f"❌ Ошибка при загрузке правил перевода: {e}")
            self.value_translation_rules = self.get_default_translation_rules()

    def get_default_translation_rules(self):
        """Возвращает набор правил перевода по умолчанию"""
        return [
            # Единицы измерения - длина
            {"active": True, "original": "mm", "translation": "мм", "category": "Длина", 
             "description": "Миллиметры", "examples": "5mm → 5 мм"},
            {"active": True, "original": "cm", "translation": "см", "category": "Длина",
             "description": "Сантиметры", "examples": "10cm → 10 см"},
            {"active": True, "original": "m", "translation": "м", "category": "Длина",
             "description": "Метры", "examples": "1.5m → 1.5 м"},
            {"active": True, "original": "km", "translation": "км", "category": "Длина",
             "description": "Километры", "examples": "5km → 5 км"},
            {"active": True, "original": "in", "translation": "дюйм", "category": "Длина",
             "description": "Дюймы", "examples": '24in → 24"'},
            
            # Единицы измерения - вес
            {"active": True, "original": "mg", "translation": "мг", "category": "Вес",
             "description": "Миллиграммы", "examples": "500mg → 500 мг"},
            {"active": True, "original": "g", "translation": "г", "category": "Вес",
             "description": "Граммы", "examples": "250g → 250 г"},
            {"active": True, "original": "kg", "translation": "кг", "category": "Вес",
             "description": "Килограммы", "examples": "1.5kg → 1.5 кг"},
            
            # Единицы измерения - электричество
            {"active": True, "original": "V", "translation": "В", "category": "Электричество",
             "description": "Вольты", "examples": "220V → 220 В"},
            {"active": True, "original": "A", "translation": "А", "category": "Электричество",
             "description": "Амперы", "examples": "2A → 2 А"},
            {"active": True, "original": "W", "translation": "Вт", "category": "Электричество",
             "description": "Ватты", "examples": "100W → 100 Вт"},
            {"active": True, "original": "Wh", "translation": "Вт⋅ч", "category": "Электричество",
             "description": "Ватт-часы", "examples": "5000Wh → 5000 Вт⋅ч"},
            {"active": True, "original": "kWh", "translation": "кВт⋅ч", "category": "Электричество",
             "description": "Киловатт-часы", "examples": "10kWh → 10 кВт⋅ч"},
            
            # Единицы измерения - частота
            {"active": True, "original": "Hz", "translation": "Гц", "category": "Частота",
             "description": "Герцы", "examples": "50Hz → 50 Гц"},
            {"active": True, "original": "kHz", "translation": "кГц", "category": "Частота",
             "description": "Килогерцы", "examples": "20kHz → 20 кГц"},
            {"active": True, "original": "MHz", "translation": "МГц", "category": "Частота",
             "description": "Мегагерцы", "examples": "2.4MHz → 2.4 МГц"},
            {"active": True, "original": "GHz", "translation": "ГГц", "category": "Частота",
             "description": "Гигагерцы", "examples": "2.4GHz → 2.4 ГГц"},
            
            # 🆕 ИСПРАВЛЕНО: Добавлены единицы скорости передачи данных
            {"active": True, "original": "bps", "translation": "бит/с", "category": "Скорость",
             "description": "Биты в секунду", "examples": "1000bps → 1000 бит/с"},
            {"active": True, "original": "kbps", "translation": "кбит/с", "category": "Скорость",
             "description": "Килобиты в секунду", "examples": "32kbps → 32 кбит/с"},
            {"active": True, "original": "Mbps", "translation": "Мбит/с", "category": "Скорость",
             "description": "Мегабиты в секунду", "examples": "16Mbps → 16 Мбит/с"},
            {"active": True, "original": "Gbps", "translation": "Гбит/с", "category": "Скорость",
             "description": "Гигабиты в секунду", "examples": "1Gbps → 1 Гбит/с"},
            {"active": True, "original": "fps", "translation": "к/с", "category": "Скорость",
             "description": "Кадры в секунду", "examples": "30fps → 30 к/с"},
            
            # Единицы измерения - освещенность
            {"active": True, "original": "lux", "translation": "лк", "category": "Освещенность",
             "description": "Люксы", "examples": "500lux → 500 лк"},
            {"active": True, "original": "lm", "translation": "лм", "category": "Освещенность",
             "description": "Люмены", "examples": "800lm → 800 лм"},
            
            # Технические термины
            {"active": True, "original": "Support", "translation": "Поддержка", "category": "Термины",
             "description": "Поддержка функции", "examples": "Support → Поддержка"},
            {"active": True, "original": "Supported", "translation": "Поддерживается", "category": "Термины",
             "description": "Поддерживается", "examples": "Supported → Поддерживается"},
            {"active": True, "original": "Auto", "translation": "Автоматически", "category": "Термины",
             "description": "Автоматический режим", "examples": "Auto → Автоматически"},
            {"active": True, "original": "Manual", "translation": "Вручную", "category": "Термины",
             "description": "Ручной режим", "examples": "Manual → Вручную"},
            
            # Да/Нет
            {"active": True, "original": "Yes", "translation": "Да", "category": "Логические",
             "description": "Утвердительный ответ", "examples": "Yes → Да"},
            {"active": True, "original": "No", "translation": "Нет", "category": "Логические",
             "description": "Отрицательный ответ", "examples": "No → Нет"},
            {"active": True, "original": "True", "translation": "Истина", "category": "Логические",
             "description": "Логическая истина", "examples": "True → Истина"},
            {"active": True, "original": "False", "translation": "Ложь", "category": "Логические",
             "description": "Логическая ложь", "examples": "False → Ложь"},
        ]

    def save_value_translation_rules(self):
        """Сохраняет правила перевода значений в файл"""
        try:
            with open("value_translation_rules.json", "w", encoding="utf-8") as f:
                json.dump(self.value_translation_rules, f, ensure_ascii=False, indent=2)
            print(f"💾 Сохранено {len(self.value_translation_rules)} правил перевода")
            
            # 🆕 ДИНАМИЧЕСКОЕ ОБНОВЛЕНИЕ основной таблицы v4.8
            self.dynamic_update_main_table()
            
        except Exception as e:
            print(f"❌ Ошибка при сохранении правил перевода: {e}")
    
    def dynamic_update_main_table(self):
        """🆕 ДИНАМИЧЕСКОЕ обновление основной таблицы при изменении правил v4.8"""
        try:
            if not hasattr(self, 'tree') or not self.tree.get_children():
                print("ℹ️ Основная таблица пуста - обновление не требуется")
                return
            
            print("🔄 ДИНАМИЧЕСКОЕ ОБНОВЛЕНИЕ основной таблицы...")
            
            updated_count = 0
            
            # Проходим по всем строкам основной таблицы
            for item in self.tree.get_children():
                values = self.tree.item(item, "values")
                if len(values) >= 6:
                    # Получаем исходные значения (индекс 2)
                    original_values = values[2]
                    delimiter = values[4] if values[4] else ";"
                    
                    # 🆕 УЛУЧШЕННЫЙ пересчет "Результат разделения" v4.8.2
                    try:
                        if not original_values:
                            new_split_result = ""
                        else:
                            print(f"    🔄 Пересчитываем: '{original_values}' (разделитель: '{delimiter}')")
                            
                            # Разделяем значения по разделителю
                            parts = [p.strip() for p in original_values.split(delimiter) if p.strip()]
                            print(f"    📋 Разделенные части: {parts}")
                            
                            # Базовая очистка от системных символов
                            clean_parts = []
                            for part in parts:
                                if part and part not in ["/", ";", ",", "-"]:
                                    clean_parts.append(part)
                            print(f"    📋 Очищенные части: {clean_parts}")
                            
                            # Удаление дубликатов с сохранением порядка
                            unique_parts = []
                            seen = set()
                            for part in clean_parts:
                                if part.lower() not in seen:
                                    seen.add(part.lower())
                                    unique_parts.append(part)
                            print(f"    📋 Уникальные части: {unique_parts}")
                            
                            # 🆕 ОБЯЗАТЕЛЬНОЕ применение переводов v4.8.2
                            if hasattr(self, 'value_translation_rules') and self.value_translation_rules:
                                try:
                                    print(f"    🌐 Применяем переводы к: {unique_parts}")
                                    translated_parts = self.apply_value_translation_rules(unique_parts)
                                    print(f"    🌐 После переводов: {translated_parts}")
                                    unique_parts = translated_parts
                                except Exception as translate_error:
                                    print(f"    ❌ Ошибка переводов: {translate_error}")
                            
                            new_split_result = "; ".join(unique_parts)
                            print(f"    ✅ Новый результат: '{new_split_result}'")
                            
                    except Exception as e:
                        print(f"  ❌ Ошибка пересчета для '{original_values}': {e}")
                        new_split_result = values[5]  # Оставляем как было
                    
                    # Проверяем, изменился ли результат
                    if new_split_result != values[5]:
                        # Обновляем строку в таблице
                        new_values = list(values)
                        new_values[5] = new_split_result
                        self.tree.item(item, values=new_values)
                        updated_count += 1
                        print(f"  🔄 Обновлено: '{values[5]}' → '{new_split_result}'")
            
            if updated_count > 0:
                print(f"✅ ДИНАМИЧЕСКОЕ ОБНОВЛЕНИЕ: обновлено {updated_count} строк")
                if hasattr(self, 'info_label'):
                    self.info_label.config(text=f"✅ Динамически обновлено {updated_count} строк")
            else:
                print("ℹ️ ДИНАМИЧЕСКОЕ ОБНОВЛЕНИЕ: изменений не требуется")
                
        except Exception as e:
            print(f"❌ Ошибка динамического обновления: {e}")

    def is_protected_abbreviation(self, text):
        """🆕 Проверяет, является ли текст защищенной аббревиатурой v4.8"""
        if not text:
            return False
        
        # Проверяем точное совпадение
        if text in PROTECTED_ABBREVIATIONS:
            return True
        
        # Проверяем совпадение без учета регистра
        text_upper = text.upper()
        if text_upper in PROTECTED_ABBREVIATIONS:
            return True
        
        return False

    def load_ip_surveillance_units(self):
        """🆕 Загружает расширенную базу единиц измерений IP-видеонаблюдения"""
        try:
            # Попробуем загрузить из data/ папки
            units_files = [
                "data/ip_surveillance_units_compact.json",
                "data/ip_surveillance_units_editor.json",
                "ip_surveillance_units_compact.json",
                "ip_surveillance_units_editor.json"
            ]
            
            loaded = False
            for units_file in units_files:
                if os.path.exists(units_file):
                    with open(units_file, "r", encoding="utf-8") as f:
                        self.ip_surveillance_units = json.load(f)
                    print(f"✅ Загружена расширенная база единиц измерений из {units_file}")
                    
                    # Интегрируем единицы в правила перевода
                    self.integrate_ip_surveillance_units()
                    loaded = True
                    break
            
            if not loaded:
                print("ℹ️ Файлы единиц измерений IP-видеонаблюдения не найдены")
                print("   Будет использована базовая система единиц измерений")
                self.ip_surveillance_units = {}
                
        except Exception as e:
            print(f"❌ Ошибка при загрузке единиц измерений IP-видеонаблюдения: {e}")
            self.ip_surveillance_units = {}

    def integrate_ip_surveillance_units(self):
        """🆕 Интегрирует единицы измерений IP-видеонаблюдения в систему переводов"""
        try:
            if not self.ip_surveillance_units:
                return
            
            added_count = 0
            # Получаем существующие оригинальные значения
            existing_originals = {rule.get('original', '').lower() for rule in self.value_translation_rules}
            
            # Обрабатываем единицы из компактного формата
            if 'units' in self.ip_surveillance_units:
                for original, translation in self.ip_surveillance_units['units'].items():
                    if original.lower() not in existing_originals:
                        # Определяем категорию на основе единицы
                        category = self.determine_unit_category(original)
                        
                        new_rule = {
                            "active": True,
                            "original": original,
                            "translation": translation,
                            "category": category,
                            "description": f"Автоперевод IP-видеонаблюдение: {original}",
                            "examples": f"{original} → {translation}"
                        }
                        self.value_translation_rules.append(new_rule)
                        existing_originals.add(original.lower())
                        added_count += 1
            
            # Обрабатываем единицы из полного формата
            elif 'unit_variants' in self.ip_surveillance_units:
                for unit, data in self.ip_surveillance_units['unit_variants'].items():
                    if unit.lower() not in existing_originals:
                        new_rule = {
                            "active": True,
                            "original": unit,
                            "translation": data.get('russian', unit),
                            "category": data.get('category', 'IP-видеонаблюдение'),
                            "description": f"Автоперевод: {data.get('unit_type', 'general')}",
                            "examples": f"{unit} → {data.get('russian', unit)}"
                        }
                        self.value_translation_rules.append(new_rule)
                        existing_originals.add(unit.lower())
                        added_count += 1
            
            if added_count > 0:
                print(f"🌐 Автоматически добавлено {added_count} правил перевода единиц IP-видеонаблюдения")
                self.save_value_translation_rules()
            else:
                print("ℹ️ Все единицы IP-видеонаблюдения уже присутствуют в правилах перевода")
                
        except Exception as e:
            print(f"❌ Ошибка при интеграции единиц IP-видеонаблюдения: {e}")

    def determine_unit_category(self, unit):
        """🆕 Определяет категорию единицы измерения"""
        unit_lower = unit.lower()
        
        # Электричество
        if any(x in unit_lower for x in ['v', 'volt', 'a', 'amp', 'w', 'watt', 'wh', 'ohm', 'ma', 'mv', 'kw', 'kv']):
            return "Электричество"
        
        # Данные и память
        if any(x in unit_lower for x in ['b', 'byte', 'kb', 'mb', 'gb', 'tb', 'pb', 'bit']):
            return "Данные"
        
        # Скорость передачи
        if any(x in unit_lower for x in ['bps', 'kbps', 'mbps', 'gbps']):
            return "Скорость"
        
        # Время
        if any(x in unit_lower for x in ['s', 'sec', 'ms', 'μs', 'ns', 'min', 'h', 'hour', 'day']):
            return "Время"
        
        # Частота
        if any(x in unit_lower for x in ['hz', 'khz', 'mhz', 'ghz', 'fps']):
            return "Частота"
        
        # Размеры
        if any(x in unit_lower for x in ['mm', 'cm', 'm', 'km', 'in', 'ft', 'μm', 'nm']):
            return "Длина"
        
        # Вес
        if any(x in unit_lower for x in ['g', 'kg', 'mg', 'lb', 'oz']):
            return "Вес"
        
        # Звук
        if any(x in unit_lower for x in ['db', 'dba', 'dbc', 'dbi', 'dbm']):
            return "Звук"
        
        # Освещение
        if any(x in unit_lower for x in ['lux', 'lm', 'cd', 'nit']):
            return "Освещенность"
        
        # Температура
        if any(x in unit_lower for x in ['°c', '°f', 'c', 'k', 'kelvin', 'celsius']):
            return "Температура"
        
        # Изображения
        if any(x in unit_lower for x in ['mp', 'px', 'pixel', 'dpi', 'ppi', 'megapixel']):
            return "Разрешение"
        
        # Давление
        if any(x in unit_lower for x in ['pa', 'kpa', 'mpa', 'bar', 'psi', 'atm', 'mmhg']):
            return "Давление"
        
        # Углы
        if any(x in unit_lower for x in ['°', 'degree', 'fov', 'aov']):
            return "Угол"
        
        return "IP-видеонаблюдение"

    def refresh_translation_rules_display(self):
        """Обновляет отображение правил перевода в таблице"""
        # Очищаем таблицу
        for item in self.translation_tree.get_children():
            self.translation_tree.delete(item)
        
        # Заполняем таблицу
        for rule in self.value_translation_rules:
            active_text = "✓" if rule.get("active", True) else "✗"
            
            self.translation_tree.insert("", "end", values=(
                active_text,
                rule.get("original", ""),
                rule.get("translation", ""),
                rule.get("category", ""),
                rule.get("description", ""),
                rule.get("examples", "")
            ))
        
        # Обновляем статистику
        total_rules = len(self.value_translation_rules)
        active_rules = sum(1 for rule in self.value_translation_rules if rule.get("active", True))
        self.translation_stats_label.config(text=f"Правил: {total_rules} | Активных: {active_rules}")

    def add_value_translation_rule(self):
        """Добавляет новое правило перевода значений"""
        dialog = ValueTranslationRuleDialog(self.root, None)
        if dialog.result:
            self.value_translation_rules.append(dialog.result)
            self.save_value_translation_rules()
            self.refresh_translation_rules_display()

    def edit_value_translation_rule(self):
        """Редактирует выбранное правило перевода"""
        selection = self.translation_tree.selection()
        if not selection:
            messagebox.showwarning("Предупреждение", "Выберите правило для редактирования")
            return
        
        # Получаем индекс выбранного элемента
        item = selection[0]
        index = self.translation_tree.index(item)
        
        if 0 <= index < len(self.value_translation_rules):
            rule = self.value_translation_rules[index]
            dialog = ValueTranslationRuleDialog(self.root, rule)
            if dialog.result:
                self.value_translation_rules[index] = dialog.result
                self.save_value_translation_rules()
                self.refresh_translation_rules_display()

    def delete_value_translation_rule(self):
        """Удаляет выбранное правило перевода"""
        selection = self.translation_tree.selection()
        if not selection:
            messagebox.showwarning("Предупреждение", "Выберите правило для удаления")
            return
        
        item = selection[0]
        index = self.translation_tree.index(item)
        
        if 0 <= index < len(self.value_translation_rules):
            rule = self.value_translation_rules[index]
            if messagebox.askyesno("Подтверждение", 
                                 f"Удалить правило перевода?\n\n'{rule.get('original', '')}' → '{rule.get('translation', '')}'"):
                del self.value_translation_rules[index]
                self.save_value_translation_rules()
                self.refresh_translation_rules_display()

    def auto_detect_units(self):
        """🔍 Автоматически находит единицы измерения в данных"""
        if not hasattr(self, 'tree') or not self.tree.get_children():
            messagebox.showwarning("Предупреждение", "Сначала обработайте данные")
            return
        
        # Собираем все значения из столбца "Результат разделения"
        all_values = []
        for item in self.tree.get_children():
            values = self.tree.item(item, 'values')
            if len(values) >= 6:
                split_results = values[5]
                if split_results:
                    split_values = [v.strip() for v in re.split(r'[;,|]', split_results) if v.strip()]
                    all_values.extend(split_values)
        
        if not all_values:
            messagebox.showinfo("Информация", "Нет данных для анализа")
            return
        
        # Расширенные паттерны для поиска единиц измерения IP-видеонаблюдения
        unit_patterns = {
            # === ФИЗИЧЕСКИЕ РАЗМЕРЫ ===
            r'\b\d+\.?\d*\s*mm\b': ('mm', 'мм', 'Длина'),
            r'\b\d+\.?\d*\s*cm\b': ('cm', 'см', 'Длина'),
            r'\b\d+\.?\d*\s*m\b': ('m', 'м', 'Длина'),
            r'\b\d+\.?\d*\s*km\b': ('km', 'км', 'Длина'),
            r'\b\d+\.?\d*\s*in\b': ('in', 'дюйм', 'Длина'),
            r'\b\d+\.?\d*\s*ft\b': ('ft', 'фут', 'Длина'),
            r'\b\d+\.?\d*\s*μm\b': ('μm', 'мкм', 'Длина'),
            r'\b\d+\.?\d*\s*nm\b': ('nm', 'нм', 'Длина'),
            
            # === ВЕС ===
            r'\b\d+\.?\d*\s*kg\b': ('kg', 'кг', 'Вес'),
            r'\b\d+\.?\d*\s*g\b': ('g', 'г', 'Вес'),
            r'\b\d+\.?\d*\s*lb\b': ('lb', 'фунт', 'Вес'),
            r'\b\d+\.?\d*\s*oz\b': ('oz', 'унция', 'Вес'),
            
            # === ЭЛЕКТРИЧЕСТВО ===
            r'\b\d+\.?\d*\s*V\b': ('V', 'В', 'Электричество'),
            r'\b\d+\.?\d*\s*mV\b': ('mV', 'мВ', 'Электричество'),
            r'\b\d+\.?\d*\s*kV\b': ('kV', 'кВ', 'Электричество'),
            r'\b\d+\.?\d*\s*W\b': ('W', 'Вт', 'Электричество'),
            r'\b\d+\.?\d*\s*mW\b': ('mW', 'мВт', 'Электричество'),
            r'\b\d+\.?\d*\s*kW\b': ('kW', 'кВт', 'Электричество'),
            r'\b\d+\.?\d*\s*Wh\b': ('Wh', 'Вт⋅ч', 'Электричество'),
            r'\b\d+\.?\d*\s*kWh\b': ('kWh', 'кВт⋅ч', 'Электричество'),
            r'\b\d+\.?\d*\s*A\b': ('A', 'А', 'Электричество'),
            r'\b\d+\.?\d*\s*mA\b': ('mA', 'мА', 'Электричество'),
            r'\b\d+\.?\d*\s*μA\b': ('μA', 'мкА', 'Электричество'),
            r'\b\d+\.?\d*\s*mAh\b': ('mAh', 'мАч', 'Электричество'),
            r'\b\d+\.?\d*\s*Ohm\b': ('Ohm', 'Ом', 'Электричество'),
            r'\b\d+\.?\d*\s*kOhm\b': ('kOhm', 'кОм', 'Электричество'),
            
            # === ЧАСТОТА И ВРЕМЯ ===
            r'\b\d+\.?\d*\s*Hz\b': ('Hz', 'Гц', 'Частота'),
            r'\b\d+\.?\d*\s*kHz\b': ('kHz', 'кГц', 'Частота'),
            r'\b\d+\.?\d*\s*MHz\b': ('MHz', 'МГц', 'Частота'),
            r'\b\d+\.?\d*\s*GHz\b': ('GHz', 'ГГц', 'Частота'),
            r'\b\d+\.?\d*\s*fps\b': ('fps', 'кадр/с', 'Видео'),
            r'\b\d+\.?\d*\s*s\b': ('s', 'с', 'Время'),
            r'\b\d+\.?\d*\s*ms\b': ('ms', 'мс', 'Время'),
            r'\b\d+\.?\d*\s*μs\b': ('μs', 'мкс', 'Время'),
            r'\b\d+\.?\d*\s*ns\b': ('ns', 'нс', 'Время'),
            r'\b\d+\.?\d*\s*min\b': ('min', 'мин', 'Время'),
            r'\b\d+\.?\d*\s*h\b': ('h', 'ч', 'Время'),
            
            # === ПАМЯТЬ И ДАННЫЕ ===
            r'\b\d+\.?\d*\s*B\b': ('B', 'Б', 'Данные'),
            r'\b\d+\.?\d*\s*KB\b': ('KB', 'КБ', 'Данные'),
            r'\b\d+\.?\d*\s*MB\b': ('MB', 'МБ', 'Данные'),
            r'\b\d+\.?\d*\s*GB\b': ('GB', 'ГБ', 'Данные'),
            r'\b\d+\.?\d*\s*TB\b': ('TB', 'ТБ', 'Данные'),
            r'\b\d+\.?\d*\s*PB\b': ('PB', 'ПБ', 'Данные'),
            
            # === СКОРОСТЬ ПЕРЕДАЧИ ДАННЫХ ===
            r'\b\d+\.?\d*\s*bps\b': ('bps', 'бит/с', 'Скорость'),
            r'\b\d+\.?\d*\s*kbps\b': ('kbps', 'кбит/с', 'Скорость'),
            r'\b\d+\.?\d*\s*Mbps\b': ('Mbps', 'Мбит/с', 'Скорость'),
            r'\b\d+\.?\d*\s*Gbps\b': ('Gbps', 'Гбит/с', 'Скорость'),
            r'\b\d+\.?\d*\s*Tbps\b': ('Tbps', 'Тбит/с', 'Скорость'),
            
            # === ИЗОБРАЖЕНИЯ И ВИДЕО ===
            r'\b\d+\.?\d*\s*mp\b': ('mp', 'мп', 'Разрешение'),
            r'\b\d+\.?\d*\s*MP\b': ('MP', 'МП', 'Разрешение'),
            r'\b\d+\.?\d*\s*px\b': ('px', 'пикс', 'Разрешение'),
            r'\b\d+\.?\d*\s*dpi\b': ('dpi', 'точек/дюйм', 'Разрешение'),
            r'\b\d+\.?\d*\s*ppi\b': ('ppi', 'пикс/дюйм', 'Разрешение'),
            r'\b\d+\.?\d*\s*lpi\b': ('lpi', 'линий/дюйм', 'Разрешение'),
            
            # === ЗВУК И ОСВЕЩЕННОСТЬ ===
            r'\b\d+\.?\d*\s*dB\b': ('dB', 'дБ', 'Звук'),
            r'\b\d+\.?\d*\s*dBA\b': ('dBA', 'дБА', 'Звук'),
            r'\b\d+\.?\d*\s*dBC\b': ('dBC', 'дБС', 'Звук'),
            r'\b\d+\.?\d*\s*dBi\b': ('dBi', 'дБи', 'Звук'),
            r'\b\d+\.?\d*\s*dBm\b': ('dBm', 'дБм', 'Звук'),
            r'\b\d+\.?\d*\s*dBW\b': ('dBW', 'дБВт', 'Звук'),
            r'\b\d+\.?\d*\s*lux\b': ('lux', 'лк', 'Освещенность'),
            r'\b\d+\.?\d*\s*lm\b': ('lm', 'лм', 'Освещенность'),
            r'\b\d+\.?\d*\s*cd\b': ('cd', 'кд', 'Освещенность'),
            r'\b\d+\.?\d*\s*nit\b': ('nit', 'нит', 'Освещенность'),
            
            # === ТЕМПЕРАТУРА (только Цельсий!) ===
            r'\b\d+\.?\d*\s*°C\b': ('°C', '°C', 'Температура'),
            r'\b\d+\.?\d*\s*C\b': ('C', '°C', 'Температура'),
            r'\b\d+\.?\d*\s*K\b': ('K', 'К', 'Температура'),
            
            # === ДАВЛЕНИЕ И ВЛАЖНОСТЬ ===
            r'\b\d+\.?\d*\s*Pa\b': ('Pa', 'Па', 'Давление'),
            r'\b\d+\.?\d*\s*kPa\b': ('kPa', 'кПа', 'Давление'),
            r'\b\d+\.?\d*\s*MPa\b': ('MPa', 'МПа', 'Давление'),
            r'\b\d+\.?\d*\s*bar\b': ('bar', 'бар', 'Давление'),
            r'\b\d+\.?\d*\s*mbar\b': ('mbar', 'мбар', 'Давление'),
            r'\b\d+\.?\d*\s*psi\b': ('psi', 'фунт/кв.дюйм', 'Давление'),
            r'\b\d+\.?\d*\s*atm\b': ('atm', 'атм', 'Давление'),
            r'\b\d+\.?\d*\s*mmHg\b': ('mmHg', 'мм рт.ст.', 'Давление'),
            r'\b\d+\.?\d*\s*%RH\b': ('%RH', '%ОВ', 'Влажность'),
            r'\b\d+\.?\d*\s*RH\b': ('RH', '%ОВ', 'Влажность'),
            
            # === УГЛЫ ===
            r'\b\d+\.?\d*\s*°\b': ('°', '°', 'Угол'),
            r'\b\d+\.?\d*\s*degrees?\b': ('degree', '°', 'Угол'),
            
            # === СПЕЦИАЛЬНЫЕ ЕДИНИЦЫ ВИДЕОНАБЛЮДЕНИЯ ===
            r'\b\d+\.?\d*\s*FOV\b': ('FOV', 'поле зрения', 'Оптика'),
            r'\b\d+\.?\d*\s*AOV\b': ('AOV', 'угол обзора', 'Оптика'),
            r'\b\d+\.?\d*\s*HFOV\b': ('HFOV', 'гориз. обзор', 'Оптика'),
            r'\b\d+\.?\d*\s*VFOV\b': ('VFOV', 'верт. обзор', 'Оптика'),
            
            # === РАЗРЕШЕНИЯ ВИДЕО ===
            r'\b1920x1080\b': ('1920x1080', 'FullHD', 'Разрешение'),
            r'\b3840x2160\b': ('3840x2160', '4K', 'Разрешение'),
            r'\b1280x720\b': ('1280x720', 'HD', 'Разрешение'),
            r'\b\d+x\d+\b': ('resolution', 'разрешение', 'Разрешение'),
        }
        
        found_units = {}
        examples = {}
        
        # Ищем единицы в значениях
        for value in all_values:
            for pattern, (unit, translation, category) in unit_patterns.items():
                if re.search(pattern, value, re.IGNORECASE):
                    if unit not in found_units:
                        found_units[unit] = (translation, category)
                        examples[unit] = []
                    if value not in examples[unit] and len(examples[unit]) < 3:
                        examples[unit].append(value)
        
        if not found_units:
            messagebox.showinfo("Результат", "Единицы измерения не найдены")
            return
        
        # Проверяем, какие единицы уже есть в правилах
        existing_units = {rule['original'] for rule in self.value_translation_rules}
        new_units = {}
        
        for unit, (translation, category) in found_units.items():
            if unit not in existing_units:
                new_units[unit] = (translation, category, examples[unit])
        
        if not new_units:
            messagebox.showinfo("Результат", 
                              f"Найдено единиц: {len(found_units)}\nВсе найденные единицы уже есть в правилах")
            return
        
        # Показываем диалог с предложением добавить новые правила
        message = f"🔍 Найдено новых единиц измерения: {len(new_units)}\n\n"
        for unit, (translation, category, unit_examples) in new_units.items():
            message += f"• {unit} → {translation} ({category})\n"
            if unit_examples:
                message += f"  Примеры: {', '.join(unit_examples[:2])}\n"
        
        message += f"\nДобавить эти правила перевода?"
        
        if messagebox.askyesno("Найденные единицы измерения", message):
            # Добавляем новые правила
            for unit, (translation, category, unit_examples) in new_units.items():
                new_rule = {
                    "active": True,
                    "original": unit,
                    "translation": translation,
                    "category": category,
                    "description": f"Единица измерения: {translation}",
                    "examples": f"{unit_examples[0] if unit_examples else f'100{unit}'} → {unit_examples[0].replace(unit, translation) if unit_examples else f'100 {translation}'}"
                }
                self.value_translation_rules.append(new_rule)
            
            self.save_value_translation_rules()
            self.refresh_translation_rules_display()
            
            messagebox.showinfo("Успешно", f"Добавлено {len(new_units)} новых правил перевода")

    def activate_all_translation_rules(self):
        """Активирует все правила перевода"""
        for rule in self.value_translation_rules:
            rule["active"] = True
        self.save_value_translation_rules()
        self.refresh_translation_rules_display()

    def deactivate_all_translation_rules(self):
        """Деактивирует все правила перевода"""
        for rule in self.value_translation_rules:
            rule["active"] = False
        self.save_value_translation_rules()
        self.refresh_translation_rules_display()

    def on_translation_tree_click(self, event):
        """🆕 Обработка клика по таблице переводов v4.8"""
        # Обновляем счетчик выбранных элементов
        if hasattr(self, 'selected_count_label'):
            selected_count = len(self.translation_tree.selection())
            self.selected_count_label.config(text=f"Выбрано: {selected_count}")

    def activate_selected_rules(self):
        """🆕 Активирует только выбранные правила v4.8"""
        selection = self.translation_tree.selection()
        if not selection:
            messagebox.showwarning("Предупреждение", "Выберите правила для активации")
            return
        
        activated_count = 0
        for item in selection:
            index = self.translation_tree.index(item)
            if index < len(self.value_translation_rules):
                if not self.value_translation_rules[index].get("active", True):
                    self.value_translation_rules[index]["active"] = True
                    activated_count += 1
        
        if activated_count > 0:
            self.save_value_translation_rules()
            self.refresh_translation_rules_display()
            print(f"✅ Активировано {activated_count} правил")
        else:
            print("ℹ️ Все выбранные правила уже активны")

    def deactivate_selected_rules(self):
        """🆕 Деактивирует только выбранные правила v4.8"""
        selection = self.translation_tree.selection()
        if not selection:
            messagebox.showwarning("Предупреждение", "Выберите правила для деактивации")
            return
        
        deactivated_count = 0
        for item in selection:
            index = self.translation_tree.index(item)
            if index < len(self.value_translation_rules):
                if self.value_translation_rules[index].get("active", True):
                    self.value_translation_rules[index]["active"] = False
                    deactivated_count += 1
        
        if deactivated_count > 0:
            self.save_value_translation_rules()
            self.refresh_translation_rules_display()
            print(f"✅ Деактивировано {deactivated_count} правил")
        else:
            print("ℹ️ Все выбранные правила уже неактивны")

    def export_value_translations(self):
        """Экспортирует правила перевода в файл"""
        filename = filedialog.asksaveasfilename(
            title="Экспорт правил перевода",
            defaultextension=".json",
            filetypes=[("JSON файлы", "*.json"), ("Все файлы", "*.*")]
        )
        
        if filename:
            try:
                with open(filename, "w", encoding="utf-8") as f:
                    json.dump(self.value_translation_rules, f, ensure_ascii=False, indent=2)
                messagebox.showinfo("Успешно", f"Правила экспортированы в:\n{filename}")
            except Exception as e:
                messagebox.showerror("Ошибка", f"Ошибка экспорта:\n{e}")

    def import_value_translations(self):
        """Импортирует правила перевода из файла"""
        filename = filedialog.askopenfilename(
            title="Импорт правил перевода",
            filetypes=[("JSON файлы", "*.json"), ("Все файлы", "*.*")]
        )
        
        if filename:
            try:
                with open(filename, "r", encoding="utf-8") as f:
                    imported_rules = json.load(f)
                
                # Проверяем формат данных
                if not isinstance(imported_rules, list):
                    messagebox.showerror("Ошибка", "Неверный формат файла")
                    return
                
                # Добавляем импортированные правила
                existing_originals = {rule['original'] for rule in self.value_translation_rules}
                added_count = 0
                
                for rule in imported_rules:
                    if isinstance(rule, dict) and 'original' in rule and 'translation' in rule:
                        if rule['original'] not in existing_originals:
                            # Заполняем отсутствующие поля значениями по умолчанию
                            complete_rule = {
                                "active": rule.get("active", True),
                                "original": rule["original"],
                                "translation": rule["translation"],
                                "category": rule.get("category", "Импорт"),
                                "description": rule.get("description", "Импортированное правило"),
                                "examples": rule.get("examples", f"{rule['original']} → {rule['translation']}")
                            }
                            self.value_translation_rules.append(complete_rule)
                            existing_originals.add(rule['original'])
                            added_count += 1
                
                self.save_value_translation_rules()
                self.refresh_translation_rules_display()
                
                messagebox.showinfo("Успешно", f"Импортировано {added_count} новых правил")
                
            except Exception as e:
                messagebox.showerror("Ошибка", f"Ошибка импорта:\n{e}")

    def show_translation_context_menu(self, event):
        """Показывает контекстное меню для правил перевода"""
        # Определяем элемент под курсором
        item = self.translation_tree.identify('item', event.x, event.y)
        if item:
            self.translation_tree.selection_set(item)
            
            # Создаем контекстное меню
            context_menu = Menu(self.root, tearoff=0)
            context_menu.add_command(label="✏️ Редактировать", command=self.edit_value_translation_rule)
            context_menu.add_command(label="🗑️ Удалить", command=self.delete_value_translation_rule)
            context_menu.add_separator()
            
            # Получаем текущее состояние правила
            index = self.translation_tree.index(item)
            if 0 <= index < len(self.value_translation_rules):
                rule = self.value_translation_rules[index]
                is_active = rule.get("active", True)
                
                if is_active:
                    context_menu.add_command(label="✗ Деактивировать", 
                                           command=lambda: self.toggle_translation_rule(index))
                else:
                    context_menu.add_command(label="✓ Активировать", 
                                           command=lambda: self.toggle_translation_rule(index))
            
            context_menu.tk_popup(event.x_root, event.y_root)

    def toggle_translation_rule(self, index):
        """Переключает активность правила перевода"""
        if 0 <= index < len(self.value_translation_rules):
            rule = self.value_translation_rules[index]
            rule["active"] = not rule.get("active", True)
            self.save_value_translation_rules()
            self.refresh_translation_rules_display()

    def load_merge_settings(self):
        """Загрузка настроек объединения"""
        # Загрузка настроек объединения параметров
        if os.path.exists(self.PARAM_MERGE_SETTINGS_FILE):
            try:
                with open(self.PARAM_MERGE_SETTINGS_FILE, "r", encoding="utf-8") as f:
                    self.param_merge_settings = json.load(f)
            except Exception as e:
                print(f"Ошибка загрузки настроек объединения параметров: {e}")
                self.param_merge_settings = {}
        else:
            self.param_merge_settings = {}

        # Загрузка настроек объединения значений
        if os.path.exists(self.VALUE_MERGE_SETTINGS_FILE):
            try:
                with open(self.VALUE_MERGE_SETTINGS_FILE, "r", encoding="utf-8") as f:
                    self.value_merge_settings = json.load(f)
            except Exception as e:
                print(f"Ошибка загрузки настроек объединения значений: {e}")
                self.value_merge_settings = {}
        else:
            self.value_merge_settings = {}

    def save_merge_settings(self):
        """Сохранение настроек объединения"""
        try:
            with open(self.PARAM_MERGE_SETTINGS_FILE, "w", encoding="utf-8") as f:
                json.dump(self.param_merge_settings, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"Ошибка сохранения настроек объединения параметров: {e}")

        try:
            with open(self.VALUE_MERGE_SETTINGS_FILE, "w", encoding="utf-8") as f:
                json.dump(self.value_merge_settings, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"Ошибка сохранения настроек объединения значений: {e}")

    def show_help_dialog(self):
        """Показывает диалог справки с подробной инструкцией"""
        
        # Создаем диалоговое окно
        help_dialog = Toplevel(self.root)
        help_dialog.title("📚 Справка - Инструкция пользователя")
        help_dialog.geometry("800x600")
        help_dialog.transient(self.root)
        help_dialog.grab_set()
        
        # Центрируем диалог
        help_dialog.geometry("+%d+%d" % (self.root.winfo_rootx() + 100, self.root.winfo_rooty() + 50))
        
        # Создаем основной фрейм
        main_frame = Frame(help_dialog)
        main_frame.pack(fill=BOTH, expand=True, padx=10, pady=10)
        
        # Заголовок
        header_frame = Frame(main_frame)
        header_frame.pack(fill=X, pady=(0, 10))
        
        title_label = Label(header_frame, text="📚 Инструкция пользователя", 
                           font=("Arial", 16, "bold"), fg="#2E7D32")
        title_label.pack()
        
        version_label = Label(header_frame, text="Редактор атрибутов товаров v4.2", 
                             font=("Arial", 10), fg="#666666")
        version_label.pack()
        
        # Навигация (кнопки быстрого перехода)
        nav_frame = LabelFrame(main_frame, text="🧭 Быстрая навигация", font=("Arial", 9, "bold"))
        nav_frame.pack(fill=X, pady=(0, 10))
        
        nav_buttons = Frame(nav_frame)
        nav_buttons.pack(fill=X, padx=5, pady=5)
        
        # Создаем кнопки навигации
        nav_sections = [
            ("🚀 Быстрый старт", "start"),
            ("🔧 Инструкция", "instruction"), 
            ("⚙️ Настройки", "settings"),
            ("💡 Примеры", "examples"),
            ("🔧 Проблемы", "problems"),
            ("⌨️ Клавиши", "hotkeys")
        ]
        
        for i, (text, section) in enumerate(nav_sections):
            btn = Button(nav_buttons, text=text, width=15, 
                        command=lambda s=section, ht=None: self.scroll_to_section_in_dialog(ht, s))
            btn.grid(row=i//3, column=i%3, padx=2, pady=2, sticky=W+E)
        
        # Настраиваем колонки для равномерного распределения
        for i in range(3):
            nav_buttons.grid_columnconfigure(i, weight=1)
        
        # Создаем текстовую область с прокруткой
        text_frame = Frame(main_frame)
        text_frame.pack(fill=BOTH, expand=True)
        
        # Текстовое поле
        help_text = Text(text_frame, wrap=WORD, font=("Arial", 10), 
                        padx=10, pady=10, bg="#FAFAFA")
        
        # Скроллбар
        scrollbar = Scrollbar(text_frame, orient=VERTICAL, command=help_text.yview)
        help_text.configure(yscrollcommand=scrollbar.set)
        
        # Размещение
        help_text.pack(side=LEFT, fill=BOTH, expand=True)
        scrollbar.pack(side=RIGHT, fill=Y)
        
        # Настройка стилей текста
        help_text.tag_configure("title", font=("Arial", 14, "bold"), foreground="#1976D2", spacing1=10, spacing3=5)
        help_text.tag_configure("subtitle", font=("Arial", 12, "bold"), foreground="#388E3C", spacing1=8, spacing3=3)
        help_text.tag_configure("header", font=("Arial", 11, "bold"), foreground="#F57C00", spacing1=6, spacing3=2)
        help_text.tag_configure("code", font=("Courier New", 9), background="#F5F5F5", foreground="#D32F2F")
        help_text.tag_configure("emphasis", font=("Arial", 10, "bold"), foreground="#7B1FA2")
        help_text.tag_configure("success", foreground="#2E7D32")
        help_text.tag_configure("warning", foreground="#F57C00")
        help_text.tag_configure("error", foreground="#D32F2F")
        
        # Словарь позиций секций для навигации
        section_positions = {}
        
        # Загружаем содержимое инструкции
        self.load_help_content_in_dialog(help_text, section_positions)
        
        # Обновляем команды кнопок навигации с правильными ссылками
        for i, (text, section) in enumerate(nav_sections):
            btn = nav_buttons.grid_slaves(row=i//3, column=i%3)[0]
            btn.config(command=lambda s=section: self.scroll_to_section_in_dialog(help_text, section_positions, s))
        
        # Делаем текст только для чтения
        help_text.config(state=DISABLED)
        
        # Кнопка закрытия
        close_frame = Frame(main_frame)
        close_frame.pack(fill=X, pady=(10, 0))
        
        close_btn = Button(close_frame, text="Закрыть", command=help_dialog.destroy, width=15)
        close_btn.pack(side=RIGHT)
        
        # Обработка клавиш
        help_dialog.bind('<Escape>', lambda e: help_dialog.destroy())
        help_dialog.focus_set()

    def scroll_to_section_in_dialog(self, help_text, section_positions, section):
        """Прокрутка к указанной секции в диалоге"""
        if section in section_positions:
            help_text.see(section_positions[section])
            # Подсвечиваем найденную секцию
            help_text.tag_add(SEL, section_positions[section], 
                             f"{section_positions[section]} lineend")

    def load_help_content_in_dialog(self, help_text, section_positions):
        """Загружает содержимое инструкции в диалог"""
        help_text.config(state=NORMAL)
        
        # Очищаем содержимое
        help_text.delete(1.0, END)
        
        content = '''🎯 НАЗНАЧЕНИЕ ПРОГРАММЫ

Редактор атрибутов товаров предназначен для автоматической обработки Excel файлов с HTML-характеристиками товаров и преобразования их в структурированный формат.

Что делает программа:
📥 Загружает Excel файлы с HTML-характеристиками
🔧 Парсит HTML и извлекает параметры товаров  
✂️ Разделяет значения параметров (например: "Красный, Синий" → "Красный; Синий")
🔄 Нормализует названия параметров (маппинг)
💾 Экспортирует результат в новый Excel файл

---

🚀 БЫСТРЫЙ СТАРТ

1. ПОДГОТОВКА ФАЙЛА
Ваш Excel файл должен содержать:
• Столбец с артикулами товаров (обычно первый)
• Столбец с HTML-характеристиками (указываете номер)  
• Дополнительные столбцы для сохранения (опционально)

2. ПРИМЕР ИСХОДНЫХ ДАННЫХ
Артикул | Название  | Характеристики (HTML)
TOV001  | Товар 1   | <tr><td>Цвет</td><td>Красный</td></tr>
TOV002  | Товар 2   | <tr><td>Размер</td><td>XL</td></tr>

3. РЕЗУЛЬТАТ ОБРАБОТКИ  
Артикул | Название  | Цвет     | Размер
TOV001  | Товар 1   | Красный  |
TOV002  | Товар 2   |          | XL

---

🔧 ПОШАГОВАЯ ИНСТРУКЦИЯ

ШАГ 1: Настройка файлов и путей

📁 ФАЙЛЫ И НАСТРОЙКИ:
1. Выберите исходный файл: нажмите 📂 рядом с полем "Файл"
2. Укажите папку для сохранения: нажмите 📂 рядом с полем "Папка"
3. Настройте столбцы:
   • Столбец: Номер столбца с HTML (отсчет с 1)
   • Доп.столбцы: Нажмите ⚙️ для выбора дополнительных столбцов

ШАГ 2: Загрузка и обработка

🔧 ДЕЙСТВИЯ:
1. 📥 Загрузить - загружает файл в память
2. ⚙️ Обработать - извлекает параметры из HTML  
3. 💾 Сохранить - экспортирует результат в Excel

ПРОЦЕСС ОБРАБОТКИ:
1. Нажмите 📥 Загрузить для загрузки файла
2. Нажмите ⚙️ Обработать для извлечения параметров
3. Проверьте результаты на вкладке "📋 Основная таблица"
4. При необходимости настройте маппинги параметров
5. Нажмите 💾 Сохранить для экспорта

---

⚙️ НАСТРОЙКИ И КОНФИГУРАЦИЯ

БЫСТРЫЕ НАСТРОЙКИ (панель инструментов):

🔄 ПРАВИЛА ЗАМЕН
• Настройка regex-правил для обработки текста
• Удаление лишних символов
• Нормализация форматирования
• Пример: Удаление версий \\sv?\\d+(\\.\\d+)*$ → (пустая строка)

Aa НОРМАЛИЗАЦИЯ РЕГИСТРА  
• Приведение к единому регистру
• "support", "Support", "SUPPORT" → "Support"
• Предотвращает дублирование из-за регистра

≈ СИНОНИМЫ ЗНАЧЕНИЙ
• Автоматическая замена синонимов
• "Yes" → "Supported", "No" → "Not Supported"  
• Унификация значений параметров

🛡️ ЗАЩИЩЕННЫЕ ЗНАЧЕНИЯ
• Значения, которые НЕ разделяются
• Например: "B/W", "I/O", "AC/DC"
• Остаются как есть, несмотря на разделители

---

💡 ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ

ПРИМЕР 1: Обработка каталога товаров

ИСХОДНЫЕ ДАННЫЕ:
<table>
<tr><td>Бренд</td><td>Samsung</td></tr>
<tr><td>Модель</td><td>Galaxy S21</td></tr>  
<tr><td>Цвет</td><td>Черный, Белый</td></tr>
<tr><td>Память</td><td>128GB, 256GB</td></tr>
</table>

РЕЗУЛЬТАТ:
Артикул | Бренд   | Модель     | Цвет           | Память
PHONE01 | Samsung | Galaxy S21 | Черный; Белый  | 128GB; 256GB

ПРИМЕР 2: Маппинг параметров

ПРОБЛЕМА: Разные названия одного параметра
"Цвет корпуса" → "Красный"
"Color" → "Blue"
"Расцветка" → "Зеленый"

РЕШЕНИЕ: Создать маппинги
1. Цвет корпуса → Цвет
2. Color → Цвет  
3. Расцветка → Цвет

РЕЗУЛЬТАТ: Все в одном столбце "Цвет"

---

🔧 РЕШЕНИЕ ПРОБЛЕМ

ЧАСТЫЕ ОШИБКИ И РЕШЕНИЯ:

"Не удалось загрузить файл"
✅ Проверьте, что файл существует
✅ Убедитесь, что файл не открыт в Excel
✅ Проверьте формат файла (.xlsx, .xls)

"Столбец выходит за пределы таблицы"
✅ Проверьте номер столбца с характеристиками  
✅ Отсчет ведется с 1, а не с 0
✅ Убедитесь, что столбец существует

"Не найдены характеристики"
✅ Проверьте формат HTML в столбце
✅ Должны быть теги <tr> и <td>
✅ Убедитесь, что столбец не пустой

"Ошибка сохранения"
✅ Проверьте права на запись в папку
✅ Убедитесь, что папка существует
✅ Закройте Excel, если открыт результирующий файл

---

⌨️ ГОРЯЧИЕ КЛАВИШИ

ФАЙЛОВЫЕ ОПЕРАЦИИ:
Ctrl+O - Загрузить файл
Ctrl+P - Обработать таблицу
Ctrl+S - Сохранить результат  
Ctrl+Q - Выход из программы

РАБОТА С ДАННЫМИ:
Ctrl+C - Копировать выбранное
Ctrl+V - Вставить
Ctrl+Z - Отмена последнего действия
Ctrl+A - Выделить все
Delete - Удалить выбранное

НАВИГАЦИЯ:
F1 - Справка
Esc - Закрыть диалог
Enter - Подтвердить действие
Tab - Переход между элементами

---

🎯 ЛУЧШИЕ ПРАКТИКИ

ПОДГОТОВКА ДАННЫХ:
1. Очистите исходные данные от явных ошибок
2. Стандартизируйте HTML по возможности
3. Создайте резервную копию исходного файла
4. Проверьте кодировку файла (UTF-8 предпочтительно)

НАСТРОЙКА ОБРАБОТКИ:
1. Начните с малого - обработайте 10-20 товаров для тестирования  
2. Настройте маппинги для основных параметров
3. Добавьте правила замен для очистки данных
4. Создайте словарь синонимов для унификации значений

ПРОВЕРКА РЕЗУЛЬТАТОВ:
1. Проверьте количество товаров в результате
2. Сравните ключевые параметры с исходными данными
3. Убедитесь в корректности разделения значений
4. Проверьте отсутствие потери важных данных

---

🏆 ЗАКЛЮЧЕНИЕ

Редактор атрибутов товаров v4.2 - мощный инструмент для автоматизации обработки товарных характеристик.

ПРЕИМУЩЕСТВА:
⚡ Автоматизация рутинных операций
🎯 Высокая точность обработки данных
🔧 Гибкие настройки под любые задачи
💾 Сохранение времени на обработке каталогов
📈 Масштабируемость для любых объемов данных

РЕЗУЛЬТАТ ИСПОЛЬЗОВАНИЯ:
📊 Структурированные данные вместо HTML
🔄 Унифицированные параметры товаров
💾 Готовые файлы для загрузки в системы
⏰ Экономия времени на ручной обработке

Удачной работы с программой! 🚀

---

Версия инструкции: 1.0
Дата: 2025-06-02
Для программы: Редактор атрибутов товаров v4.2'''

        # Добавляем содержимое с форматированием
        lines = content.split('\n')
        
        for line in lines:
            line = line.rstrip()
            
            # Сохраняем позицию ДО добавления строки для навигации
            current_pos = help_text.index(INSERT)
            
            # Определяем тип строки и применяем соответствующий стиль
            if line.startswith('🎯 ') or line.startswith('🚀 ') or line.startswith('🔧 ') or line.startswith('⚙️ ') or line.startswith('💡 ') or line.startswith('⌨️ '):
                # Сохраняем позицию секции для навигации
                if "быстрый" in line.lower():
                    section_positions["start"] = current_pos
                elif "инструкция" in line.lower():
                    section_positions["instruction"] = current_pos
                elif "настройки" in line.lower():
                    section_positions["settings"] = current_pos
                elif "примеры" in line.lower():
                    section_positions["examples"] = current_pos
                elif "проблем" in line.lower():
                    section_positions["problems"] = current_pos
                elif "клавиши" in line.lower():
                    section_positions["hotkeys"] = current_pos
                
                help_text.insert(END, line + '\n', "title")
                    
            elif line.startswith('SHAГ ') or line.startswith('ПРИМЕР ') or line.startswith('ПРОБЛЕМА:') or line.startswith('РЕШЕНИЕ:'):
                help_text.insert(END, line + '\n', "subtitle")
            elif line.isupper() and len(line) > 3 and not line.startswith('TOV') and not line.startswith('PHONE'):
                help_text.insert(END, line + '\n', "header")
            elif line.startswith('✅') or line.startswith('•'):
                help_text.insert(END, line + '\n', "success")
            elif line.startswith('❌') or line.startswith('⚠️'):
                help_text.insert(END, line + '\n', "warning")
            elif line.startswith('Ctrl+') or line.startswith('F1') or line.startswith('Esc'):
                help_text.insert(END, line + '\n', "code")
            elif '→' in line or '<tr>' in line or '<td>' in line:
                help_text.insert(END, line + '\n', "code")
            elif line.strip() == '---':
                help_text.insert(END, '\n' + '─' * 80 + '\n\n', "emphasis")
            else:
                help_text.insert(END, line + '\n')
        
        help_text.config(state=DISABLED)

    def find_similar_parameters_for_tab(self):
        """🚨 ЭКСТРА-ИСПРАВЛЕННЫЙ поиск похожих параметров (v4.4)"""
        if not self.original_values:
            messagebox.showwarning("Ошибка", "Сначала обработайте таблицу")
            return

        print("\n🔍 ЗАПУСК ПОИСКА ПОХОЖИХ ПАРАМЕТРОВ (v4.8)")
        
        # 🚨 ПРИНУДИТЕЛЬНАЯ ОЧИСТКА ВСЕХ СТАРЫХ ДАННЫХ
        self.param_merge_settings.clear()
        print("🧹 Очищены старые настройки param_merge_settings")
        
        # 🚨 УДАЛЯЕМ ФАЙЛ НАСТРОЕК ЕСЛИ СУЩЕСТВУЕТ
        if os.path.exists(self.PARAM_MERGE_SETTINGS_FILE):
            try:
                os.remove(self.PARAM_MERGE_SETTINGS_FILE)
                print(f"🗑️ Удален файл старых настроек: {self.PARAM_MERGE_SETTINGS_FILE}")
            except Exception as e:
                print(f"⚠️ Не удалось удалить файл настроек: {e}")
        
        # 🚨 ПРИНУДИТЕЛЬНО ОЧИЩАЕМ ТАБЛИЦУ
        for item in self.params_tree.get_children():
            self.params_tree.delete(item)
        print("🧹 Очищена таблица параметров")
        
        # Собираем все параметры
        all_params = set()
        for sku_params in self.original_values.values():
            all_params.update(sku_params.keys())
        
        all_params = list(all_params)
        print(f"📋 Всего уникальных параметров: {len(all_params)}")
        
        # Обновляем частоту использования параметров
        self.update_param_frequency(all_params)
        
        print("📊 Топ-10 самых частых параметров:")
        sorted_params = sorted(self.param_frequency.items(), key=lambda x: x[1], reverse=True)[:10]
        for param, freq in sorted_params:
            print(f"  • {param}: {freq} раз")
        
        # 🔥 ТЕСТИРУЕМ ПРОБЛЕМНЫЕ ПАРЫ ДО ПОИСКА
        print("\n🧪 ТЕСТИРОВАНИЕ ПРОБЛЕМНЫХ ПАР:")
        test_pairs = [
            ("Caption", "Stream Authentication"),
            ("Weight", "Night"),
            ("IR Distance", "Night Vision"),
            ("Operating Humidity", "Mounting Bracket")
        ]
        
        for param1, param2 in test_pairs:
            if param1 in all_params and param2 in all_params:
                is_similar = self.are_parameters_truly_similar(param1, param2)
                print(f"   🔍 {param1} ↔ {param2}: {'❌ ОТКЛОНЕНО' if not is_similar else '✅ ПРИНЯТО'}")
        
        # 🔥 НАХОДИМ ПОХОЖИЕ ПАРАМЕТРЫ С НОВЫМ АЛГОРИТМОМ
        print("\n🔍 ЗАПУСК АЛГОРИТМА ПОИСКА...")
        similar_groups = self.find_similar_parameters_advanced(all_params)
        
        if not similar_groups:
            print("❌ Похожие параметры НЕ НАЙДЕНЫ")
            messagebox.showinfo("Информация", "✅ Похожие параметры не найдены с текущими критериями.")
            
            # Добавляем информационную строку в таблицу
            self.params_tree.insert('', END, values=(
                "", "Похожие параметры не найдены", "Попробуйте снизить критерии", ""
            ))
            return

        print(f"\n✅ НАЙДЕНО ГРУПП: {len(similar_groups)}")
        
        # 🔥 СОХРАНЯЕМ НОВЫЕ РЕЗУЛЬТАТЫ
        for representative, similar_list in similar_groups.items():
            # 🔥 ЗАЩИТА: Убираем представителя если случайно попал
            clean_similar_list = [p for p in similar_list if p != representative]
            
            if clean_similar_list:  # Если есть похожие параметры (без представителя)
                self.param_merge_settings[representative] = {
                    'similar_params': clean_similar_list,
                    'selected': False  # По умолчанию не выбрано
                }
                
                freq = self.param_frequency.get(representative, 0)
                print(f"✅ СОХРАНЕНО: {representative} (частота: {freq}) → {len(clean_similar_list)} похожих")

        # 🔥 СОХРАНЯЕМ ДАННЫЕ
        print(f"\n💾 СОХРАНЕНИЕ ДАННЫХ...")
        self.save_merge_settings()
        
        print(f"🎯 ОБНОВЛЕНИЕ ТАБЛИЦЫ...")
        self.update_params_tree()
        
        # Показываем результат пользователю
        message = f"✅ Найдено {len(similar_groups)} групп похожих параметров.\n"
        message += "📊 Основные параметры выбраны по количеству значений (параметр с наибольшим числом значений становится главным).\n"
        message += "✔️ Проверьте результаты на вкладке 'Объединение параметров'."
        messagebox.showinfo("Поиск завершен", message)

    def update_params_tree(self):
        """УЛУЧШЕННОЕ обновление таблицы похожих параметров с автоматической сортировкой"""
        print("🎯 ОБНОВЛЕНИЕ ТАБЛИЦЫ ПОХОЖИХ ПАРАМЕТРОВ")
        
        if not self.param_merge_settings:
            # Очищаем таблицу
            for item in self.params_tree.get_children():
                self.params_tree.delete(item)
            
            # Добавляем информационную строку
            self.params_tree.insert('', END, values=(
                "", "Похожие параметры не найдены", "Попробуйте снизить критерии", ""
            ))
            return
        
        # Сбрасываем пользовательскую сортировку и применяем сортировку по умолчанию (алфавит)
        self.params_tree_sort_direction = {}
        
        # Обновляем заголовки (убираем стрелки сортировки)
        self.update_params_tree_headers('', '')
        
        # Применяем сортировку по алфавиту (по умолчанию)
        print("📋 Применение сортировки по алфавиту (по умолчанию)...")
        # Устанавливаем направление для показа стрелки
        self.params_tree_sort_direction['representative'] = 'asc'
        self.sort_params_tree('representative')

    def on_params_tree_click(self, event):
        """Обработка клика по таблице параметров"""
        region = self.params_tree.identify("region", event.x, event.y)
        if region != "cell":
            return
        col = self.params_tree.identify_column(event.x)
        row = self.params_tree.identify_row(event.y)
        
        if not row or col != '#1':  # Колонка "Объединить"
            return
        
        values = list(self.params_tree.item(row, 'values'))
        representative = values[1]
        
        # Переключаем состояние
        if representative in self.param_merge_settings:
            current_state = self.param_merge_settings[representative].get('selected', False)
            self.param_merge_settings[representative]['selected'] = not current_state
            self.save_merge_settings()
            self.update_params_tree()

    def select_all_params(self):
        """Выбрать все параметры для объединения"""
        for representative in self.param_merge_settings:
            self.param_merge_settings[representative]['selected'] = True
        self.save_merge_settings()
        self.update_params_tree()

    def deselect_all_params(self):
        """Снять выбор со всех параметров"""
        for representative in self.param_merge_settings:
            self.param_merge_settings[representative]['selected'] = False
        self.save_merge_settings()
        self.update_params_tree()
    
    def sort_params_tree(self, column):
        """🆕 СОРТИРОВКА ПО СТОЛБЦАМ: Сортировка таблицы параметров по выбранному столбцу"""
        print(f"🔄 Сортировка по столбцу: {column}")
        
        # Определяем направление сортировки
        current_direction = self.params_tree_sort_direction.get(column, 'asc')
        new_direction = 'desc' if current_direction == 'asc' else 'asc'
        self.params_tree_sort_direction[column] = new_direction
        
        # Получаем все элементы для сортировки
        items_to_sort = []
        for representative, data in self.param_merge_settings.items():
            similar_list = data.get('similar_params', [])
            selected = data.get('selected', False)
            

            
            # 🔥 ИСПРАВЛЕНИЕ: similar_list уже НЕ содержит представителя
            if similar_list:  # Если есть похожие параметры
                # similar_list уже содержит только похожие параметры (без представителя)
                other_params_sorted = sorted(similar_list, key=lambda x: x.lower())
                similar_str = "; ".join(other_params_sorted)
                frequency = self.param_frequency.get(representative, 0)
                
                # Формируем кортеж для сортировки (значение_для_сортировки, все_данные)
                if column == 'representative':
                    sort_value = representative.lower()
                elif column == 'similar_params':
                    sort_value = similar_str.lower()
                elif column == 'frequency':
                    sort_value = frequency
                else:
                    sort_value = representative.lower()
                
                items_to_sort.append((sort_value, {
                    'representative': representative,
                    'similar_str': similar_str,
                    'selected': selected,
                    'frequency': frequency,
                    'other_params': similar_list  # 🔥 ИСПРАВЛЕНИЕ: используем similar_list
                }))
        

        
        # Сортируем
        reverse_order = (new_direction == 'desc')
        items_to_sort.sort(key=lambda x: x[0], reverse=reverse_order)
        
        # Очищаем и заполняем таблицу в новом порядке
        for item in self.params_tree.get_children():
            self.params_tree.delete(item)
        
        direction_symbol = "↓" if new_direction == 'desc' else "↑"
        print(f"📊 Сортировка {len(items_to_sort)} групп по {column} {direction_symbol}")
        
        # Обновляем заголовки для показа текущей сортировки
        self.update_params_tree_headers(column, direction_symbol)
        
        for sort_value, item_data in items_to_sort:
            merge_symbol = "☑" if item_data['selected'] else "☐"
            freq_info = f"{item_data['frequency']}"
            
            if len(item_data['other_params']) > 0:
                other_freqs = [self.param_frequency.get(p, 0) for p in item_data['other_params']]
                max_other_freq = max(other_freqs) if other_freqs else 0
                freq_info += f" (макс.др: {max_other_freq})"
            
            self.params_tree.insert('', END, values=(
                merge_symbol, item_data['representative'], item_data['similar_str'], freq_info
            ))
        
        print(f"✅ Сортировка завершена: {new_direction} по {column}")
    
    def update_params_tree_headers(self, sorted_column, direction_symbol):
        """Обновляет заголовки столбцов с индикацией текущей сортировки"""
        # Сбрасываем все заголовки
        self.params_tree.heading('merge', text='Объединить')
        self.params_tree.heading('representative', 
                                 text=f'Основной параметр {"" if sorted_column != "representative" else direction_symbol}', 
                                 command=lambda: self.sort_params_tree('representative'))
        self.params_tree.heading('similar_params', 
                                 text=f'Похожие параметры {"" if sorted_column != "similar_params" else direction_symbol}', 
                                 command=lambda: self.sort_params_tree('similar_params'))
        self.params_tree.heading('frequency', 
                                 text=f'Частота {"" if sorted_column != "frequency" else direction_symbol}', 
                                 command=lambda: self.sort_params_tree('frequency'))

    def apply_param_merges_from_tab(self):
        """Применение выбранных объединений параметров"""
        merge_groups = {}
        for representative, data in self.param_merge_settings.items():
            if data.get('selected', False):
                merge_groups[representative] = data['similar_params']
        
        if not merge_groups:
            messagebox.showinfo("Информация", "Не выбрано ни одного объединения")
            return

        self.apply_parameter_merges(merge_groups)
        messagebox.showinfo("Успешно", f"Применено {len(merge_groups)} объединений параметров")

    def clear_param_merge_settings(self):
        """Очистка настроек объединения параметров"""
        if messagebox.askyesno("Подтверждение", "Очистить все настройки объединения параметров?"):
            self.param_merge_settings.clear()
            self.save_merge_settings()
            self.update_params_tree()

    def find_similar_values_for_tab(self):
        """Поиск похожих значений для вкладки"""
        if not self.original_values:
            messagebox.showwarning("Ошибка", "Сначала обработайте таблицу")
            return

        similar_values_data = self.find_similar_values_in_all_parameters()
        
        if not similar_values_data:
            messagebox.showinfo("Информация", "Похожие значения не найдены")
            return

        # Сохраняем найденные группы в настройки
        for param_name, groups in similar_values_data.items():
            if param_name not in self.value_merge_settings:
                self.value_merge_settings[param_name] = {}
            
            for representative, similar_list in groups.items():
                if len(similar_list) > 1:
                    self.value_merge_settings[param_name][representative] = {
                        'similar_values': similar_list,
                        'selected': False
                    }

        self.save_merge_settings()
        self.update_values_tree()

    def update_values_tree(self):
        """Обновление таблицы похожих значений"""
        # Очищаем таблицу
        for item in self.values_tree.get_children():
            self.values_tree.delete(item)

        # Заполняем данными
        for param_name, groups in self.value_merge_settings.items():
            for representative, data in groups.items():
                similar_list = data.get('similar_values', [])
                selected = data.get('selected', False)
                
                if len(similar_list) > 1:
                    similar_str = "; ".join(similar_list)
                    merge_symbol = "☑" if selected else "☐"
                    frequency = len(similar_list)
                    
                    self.values_tree.insert('', END, values=(
                        merge_symbol, param_name, representative, similar_str, frequency
                    ))

    def on_values_tree_click(self, event):
        """Обработка клика по таблице значений"""
        region = self.values_tree.identify("region", event.x, event.y)
        if region != "cell":
            return
        col = self.values_tree.identify_column(event.x)
        row = self.values_tree.identify_row(event.y)
        
        if not row or col != '#1':  # Колонка "Объединить"
            return
        
        values = list(self.values_tree.item(row, 'values'))
        param_name = values[1]
        representative = values[2]
        
        # Переключаем состояние
        if param_name in self.value_merge_settings and representative in self.value_merge_settings[param_name]:
            current_state = self.value_merge_settings[param_name][representative].get('selected', False)
            self.value_merge_settings[param_name][representative]['selected'] = not current_state
            self.save_merge_settings()
            self.update_values_tree()

    def select_all_values(self):
        """Выбрать все значения для объединения"""
        for param_name in self.value_merge_settings:
            for representative in self.value_merge_settings[param_name]:
                self.value_merge_settings[param_name][representative]['selected'] = True
        self.save_merge_settings()
        self.update_values_tree()

    def deselect_all_values(self):
        """Снять выбор со всех значений"""
        for param_name in self.value_merge_settings:
            for representative in self.value_merge_settings[param_name]:
                self.value_merge_settings[param_name][representative]['selected'] = False
        self.save_merge_settings()
        self.update_values_tree()

    def apply_value_merges_from_tab(self):
        """Применение выбранных объединений значений"""
        merge_data = {}
        total_merges = 0
        
        for param_name, groups in self.value_merge_settings.items():
            param_merges = {}
            for representative, data in groups.items():
                if data.get('selected', False):
                    param_merges[representative] = data['similar_values']
                    total_merges += 1
            
            if param_merges:
                merge_data[param_name] = param_merges
        
        if not merge_data:
            messagebox.showinfo("Информация", "Не выбрано ни одного объединения")
            return

        self.apply_value_merges(merge_data)
        messagebox.showinfo("Успешно", f"Применено {total_merges} объединений значений")

    def clear_value_merge_settings(self):
        """Очистка настроек объединения значений"""
        if messagebox.askyesno("Подтверждение", "Очистить все настройки объединения значений?"):
            self.value_merge_settings.clear()
            self.save_merge_settings()
            self.update_values_tree()

    def load_settings(self):
        if os.path.exists(self.SETTINGS_FILE):
            try:
                with open(self.SETTINGS_FILE, "r", encoding="utf-8") as f:
                    self.settings = json.load(f)
            except Exception as e:
                messagebox.showwarning("Ошибка загрузки настроек", f"Не удалось загрузить настройки: {str(e)}")
                self.settings = {}
        else:
            self.settings = {}
        
        # Убеждаемся, что все необходимые ключи присутствуют
        if "extra_cols" not in self.settings:
            self.settings["extra_cols"] = []
        if "features_col" not in self.settings:
            self.settings["features_col"] = ""
        if "source_file_path" not in self.settings:
            self.settings["source_file_path"] = ""
        if "output_folder_path" not in self.settings:
            self.settings["output_folder_path"] = ""

    def save_settings(self):
        self.settings["features_col"] = self.features_col.get()
        # extra_cols уже должны быть установлены в settings, не парсим из лейбла
        if "extra_cols" not in self.settings:
            # Fallback: если по какой-то причине extra_cols нет в settings, берем из лейбла
            label_text = self.extra_cols_label.cget("text")
            self.settings["extra_cols"] = [col.strip() for col in label_text.split(",") if col.strip()] if label_text else []
        self.settings["manual_split_values"] = self.manual_split_values
        self.settings["source_file_path"] = self.source_file_var.get()
        self.settings["output_folder_path"] = self.output_folder_var.get()

        try:
            with open(self.SETTINGS_FILE, "w", encoding="utf-8") as f:
                json.dump(self.settings, f, ensure_ascii=False, indent=2)
        except Exception as e:
            messagebox.showerror("Ошибка сохранения настроек", f"Не удалось сохранить настройки: {str(e)}")

    def browse_source_file(self):
        """
        Открывает диалог выбора исходного файла
        """
        current_path = self.source_file_var.get()
        initial_dir = os.path.dirname(current_path) if current_path and os.path.exists(current_path) else os.getcwd()
        
        filename = filedialog.askopenfilename(
            title="Выберите Excel файл",
            initialdir=initial_dir,
            filetypes=[("Excel files", "*.xlsx *.xls"), ("All files", "*.*")]
        )
        
        if filename:
            self.source_file_var.set(filename)
            self.save_settings()

    def browse_output_folder(self):
        """
        Открывает диалог выбора папки для сохранения
        """
        current_path = self.output_folder_var.get()
        initial_dir = current_path if current_path and os.path.exists(current_path) else os.getcwd()
        
        folder = filedialog.askdirectory(
            title="Выберите папку для сохранения",
            initialdir=initial_dir
        )
        
        if folder:
            self.output_folder_var.set(folder)
            self.save_settings()

    def load_file_from_settings(self):
        """
        Быстрая загрузка файла из настроек
        """
        source_path = self.settings.get("source_file_path", "")
        
        if not source_path:
            messagebox.showwarning("Ошибка", "В настройках не указан путь к исходному файлу")
            return
        
        if not os.path.exists(source_path):
            messagebox.showerror("Ошибка", f"Файл не найден: {source_path}")
            return
        
        try:
            self.load_excel_file(source_path)
            messagebox.showinfo("Успешно", f"Файл загружен: {os.path.basename(source_path)}")
        except Exception as e:
            messagebox.showerror("Ошибка", f"Не удалось загрузить файл: {str(e)}")

    def show_extra_cols_dialog(self):
        if not hasattr(self, 'df_columns') or not self.df_columns:
            messagebox.showwarning("Ошибка", "Сначала загрузите файл")
            return
        
        # Получаем текущие выбранные столбцы
        current_selection = self.settings.get("extra_cols", [])
        
        dialog = ColumnSelectorDialog(self.root, self.df_columns, current_selection)
        self.root.wait_window(dialog.dialog)
        
        if dialog.result is not None:
            # Сохраняем выбранные столбцы в настройки
            self.settings["extra_cols"] = dialog.result
            
            # Обновляем отображение в интерфейсе
            display_text = ", ".join(dialog.result) if dialog.result else ""
            self.extra_cols_label.config(text=display_text)
            
            # Визуальная индикация
            col_count = len(dialog.result)
            if col_count > 0:
                self.extra_cols_label.config(bg="lightgreen")
                ToolTip(self.extra_cols_label, f"Выбрано {col_count} столбцов")
            else:
                self.extra_cols_label.config(bg="SystemButtonFace")
            
            # Сохраняем настройки в файл
            self.save_settings()
            
            # Обновляем статус
            self.info_label.config(text=f"Выбрано дополнительных столбцов: {col_count}")

    def load_file(self):
        # Сначала проверяем, есть ли путь в поле исходного файла
        source_path = self.source_file_var.get().strip()
        
        if source_path and os.path.exists(source_path):
            # Загружаем файл из поля
            try:
                self.load_excel_file(source_path)
                messagebox.showinfo("Успешно", f"Файл загружен: {os.path.basename(source_path)}")
                return
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось загрузить файл из настроек: {str(e)}")
        
        # Если файла нет или ошибка загрузки - открываем диалог
        filename = filedialog.askopenfilename(filetypes=[("Excel files", "*.xlsx *.xls")])
        if filename:
            try:
                self.load_excel_file(filename)
                # Обновляем поле исходного файла
                self.source_file_var.set(filename)
                self.save_settings()
                messagebox.showinfo("Успешно", f"Файл загружен: {os.path.basename(filename)}")
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось загрузить файл: {str(e)}")

    def load_excel_file(self, filename):
        df = pd.read_excel(filename)
        self.original_df = df.copy()
        self.df_columns = df.columns.tolist()
        
        # Обновляем подсказку для поля столбца характеристик
        if hasattr(self, 'features_col'):
            tooltip_text = f"Номер столбца с HTML-характеристиками (отсчёт с 1)\nДоступные столбцы: {', '.join([f'{i+1}. {col}' for i, col in enumerate(self.df_columns)])}"
            ToolTip(self.features_col, tooltip_text)

    def process_table(self):
        if self.original_df is None:
            messagebox.showwarning("Ошибка", "Сначала загрузите файл")
            return

        try:
            col_idx = int(self.features_col.get()) - 1
            html_col = self.original_df.columns[col_idx]
        except Exception:
            messagebox.showwarning("Ошибка", "Некорректный номер столбца характеристик")
            return

        self.param_mapping = {}
        self.original_values = {}

        if "manual_split_values" in self.settings and isinstance(self.settings["manual_split_values"], dict):
            self.manual_split_values = self.settings["manual_split_values"].copy()
        else:
            self.manual_split_values = {}

        self.manual_split_results = {}

        for idx, row in self.original_df.iterrows():
            sku = row.get("Артикул", None)
            if sku is None:
                continue
            html = str(row[html_col])
            params = self.parse_html_table(html)
            self.original_values[sku] = {}
            for param, value in params.items():
                clean_value = self.remove_duplicates_from_cell(value)
                self.original_values[sku][param] = clean_value

        all_params = set()
        for sku in self.original_df["Артикул"].unique():
            for param in self.original_values.get(sku, {}):
                all_params.add(param)

        self.update_mappings_from_params(all_params)
        # 🔧 ИСПРАВЛЕНО: Используем новую функцию вместо старой
        print("🔄 Используем НОВУЮ функцию для цветового выделения...")
        similar_groups_dict = self.find_similar_parameters_advanced(list(all_params))
        
        # Конвертируем формат для цветового выделения  
        self.similar_groups = {}
        group_id = 1
        for representative, group_params in similar_groups_dict.items():
            for param in group_params:
                self.similar_groups[param] = group_id
            group_id += 1
        self.tree.delete(*self.tree.get_children())

        rownum = 1
        self.case_value_variants = defaultdict(set)

        for param in sorted(all_params):
            group_id = self.similar_groups.get(param, 0)
            orig_values_set = set()
            for sku in self.original_df["Артикул"].unique():
                vals = self.original_values.get(sku, {})
                if param in vals:
                    orig_values_set.add(vals[param])

            norm_variants = set()
            for val in orig_values_set:
                norm_val = normalize_variant(val)
                norm_variants.add(norm_val)
                self.case_value_variants[norm_val.lower()].add(val)

            orig_values_joined = "; ".join(norm_variants)

            # 🔧 ИСПРАВЛЕНО: НЕ применяем сопоставления к исходным значениям
            orig_values_processed = self.apply_case_normalization_in_str(orig_values_joined, delimiter=';')
            orig_values_processed = self.remove_duplicates_from_list([p.strip() for p in orig_values_processed.split(";") if p.strip()])
            orig_values = "; ".join(orig_values_processed)
            orig_values = self.remove_duplicates_from_cell(orig_values)

            # 🔧 ИСПРАВЛЕНО: Правильная логика сопоставлений + переводов
            
            # 1. ИСХОДНЫЕ ДАННЫЕ (без перевода) для столбцов "Исходный параметр" и "Исходные значения"
            original_param = param  # Сохраняем исходное название на английском
            original_values = orig_values  # Сохраняем исходные значения на английском
            
            # 2. ПРИМЕНЯЕМ СОПОСТАВЛЕНИЯ И ПЕРЕВОДЫ для результирующих столбцов
            # Шаг 1: Применяем сопоставления из таблицы (если есть)
            mapped_param = self.mappings.get(param, param)
            if mapped_param != param:
                print(f"📋 Сопоставление: '{param}' → '{mapped_param}'")
            
            # Шаг 2: Применяем автоперевод к результату сопоставления
            new_param = self.apply_auto_translation(mapped_param, is_parameter=True)
            
            delimiter = ";"
            if param in self.manual_split_values and isinstance(self.manual_split_values[param], str) and self.manual_split_values[param] != False:
                delimiter = self.manual_split_values[param]
            
            # 🆕 КРИТИЧЕСКАЯ ЗАЩИТА ОТ ПУСТОГО РАЗДЕЛИТЕЛЯ v4.8.5
            if not delimiter:
                delimiter = ";"
                print(f"⚠️ АВАРИЙНАЯ ЗАМЕНА разделителя для '{param}': пустой → ';'")

            # 3. ОБРАБОТКА ЗНАЧЕНИЙ: БЕЗ сопоставлений из таблицы (они только для параметров!)
            split_result_list = self.split_values(orig_values, delimiter)
            print(f"🔧 DEBUG {param}: 1.После разделения: {split_result_list}")
            
            # 🔧 ИСПРАВЛЕНО: НЕ применяем сопоставления к значениям, только нормализация регистра
            split_result_list = [self.apply_case_normalization(v) for v in split_result_list]
            print(f"🔧 DEBUG {param}: 2.После нормализации: {split_result_list}")
            
            split_result_list = self.remove_duplicates_from_list(split_result_list)
            print(f"🔧 DEBUG {param}: 3.После первой дедупликации: {split_result_list}")

            # 🆕 ВОССТАНОВЛЕНИЕ ЗАЩИЩЕННЫХ ЗНАЧЕНИЙ в интерфейсе
            split_result_list = [self.restore_protected_values_in_string(v) for v in split_result_list]
            print(f"🔧 DEBUG {param}: 4.После восстановления: {split_result_list}")
            
            # 🆕 ПРИМЕНЯЕМ ПРАВИЛА ЗАМЕНЫ
            split_result_list = [self.apply_replacement_rules(v) for v in split_result_list]
            print(f"🔧 DEBUG {param}: 5.После правил замены: {split_result_list}")
            
            # 🆕 ПРИМЕНЯЕМ СИНОНИМЫ ЗНАЧЕНИЙ 
            split_result_list = self.synonyms_manager.merge_synonyms_in_list(split_result_list)
            print(f"🔧 DEBUG {param}: 6.После синонимов: {split_result_list}")
            
            # 🔧 ИСПРАВЛЕНО: Удаляем дубликаты ПОСЛЕ применения синонимов
            split_result_list = self.remove_duplicates_from_list(split_result_list)
            print(f"🔧 DEBUG {param}: 7.После дедупликации синонимов: {split_result_list}")
            
            # 🆕 ПРИМЕНЯЕМ ПРАВИЛА ПЕРЕВОДА ЗНАЧЕНИЙ
            split_result_list = self.apply_value_translation_rules(split_result_list)
            print(f"🔧 DEBUG {param}: 8.После правил перевода значений: {split_result_list}")
            
            # 🔧 ИСПРАВЛЕНО: НЕ применяем автоперевод к значениям (только к параметрам!)
            # Автоперевод значений вызывал частичные переводы типа "Audio in" → "Аудио in"
            # Значения должны обрабатываться только правилами замены и синонимами
            
            # 🔧 ФИНАЛЬНАЯ ДЕДУПЛИКАЦИЯ (после всех преобразований)
            split_result_list = self.remove_duplicates_from_list(split_result_list)
            print(f"🔧 DEBUG {param}: 9.ФИНАЛЬНАЯ дедупликация: {split_result_list}")
            
            split_result_str = "; ".join(split_result_list)
            print(f"🔧 DEBUG {param}: 10.ИТОГОВЫЙ результат: '{split_result_str}'")

            # 4. ЗАПОЛНЯЕМ ТАБЛИЦУ: исходные данные + переведенные результаты
            self.tree.insert("", "end", values=(
                rownum, original_param, original_values, new_param, delimiter, split_result_str
            ), tags=(f"group_{group_id}",))
            rownum += 1

        self.df = pd.DataFrame([{
            "№": int(values[0]),
            "Исходный параметр": values[1],
            "Исходные значения": values[2],
            "Новый параметр": values[3],
            "Разделитель": values[4],
            "Результат разделения": values[5]
        } for item in self.tree.get_children() for values in [self.tree.item(item, "values")]])

        # 🆕 ЦВЕТОВОЕ ВЫДЕЛЕНИЕ ПОХОЖИХ ПАРАМЕТРОВ v4.0
        self.refresh_treeview_with_colors()
        
        # Автоматическая очистка дубликатов значений
        duplicates_cleaned = self.auto_clean_duplicate_values()
        
        # Автоматическое предложение объединения похожих параметров
        self.auto_suggest_parameter_merge()
        
        # 🆕 Автоматическая нормализация формата (перенесено из отдельной кнопки)
        print("🔄 Автоматическая нормализация формата...")
        self.normalize_all_values_format(show_message=False)
        
        # 🆕 АВТОМАТИЧЕСКИЙ АНАЛИЗ ЕДИНИЦ ИЗМЕРЕНИЯ
        print("📊 Автоматический анализ единиц измерения...")
        units_added = self.auto_analyze_units_and_create_rules()
        
        # Показываем информацию о найденных группах
        similar_count = len(self.similar_param_groups)
        message = f"Обработка завершена. Найдено {len(all_params)} уникальных параметров."
        if similar_count > 0:
            message += f"\n🎨 Найдено {similar_count} групп похожих параметров (выделены цветом)."
        message += f"\n✅ Форматирование значений автоматически нормализовано."
        if units_added > 0:
            message += f"\n📊 Автоматически добавлено {units_added} правил для единиц измерения."
        
        messagebox.showinfo("Успешно", message)

    def auto_analyze_units_and_create_rules(self):
        """Автоматически анализирует единицы измерения и создает правила замены"""
        try:
            if not hasattr(self, 'tree') or not self.tree.get_children():
                return 0
            
            # Собираем все значения из столбца "Результат разделения"
            all_values = []
            for item in self.tree.get_children():
                values = self.tree.item(item, 'values')
                if len(values) >= 6:  # Убеждаемся, что есть столбец "Результат разделения"
                    split_results = values[5]  # Столбец "Результат разделения"
                    if split_results:
                        # Разделяем значения по разделителям
                        split_values = [v.strip() for v in re.split(r'[;,|]', split_results) if v.strip()]
                        all_values.extend(split_values)
            
            if not all_values:
                print("📊 Нет данных для анализа единиц измерения")
                return 0
            
            # Создаем правила для единиц измерения
            unit_rules = self.create_unit_conversion_rules(all_values)
            
            if not unit_rules:
                print("📊 Единицы измерения для перевода не найдены")
                return 0
            
            # Автоматически добавляем новые правила
            added_count = 0
            for rule in unit_rules:
                # Проверяем, не существует ли уже такое правило
                existing = any(r['name'] == rule['name'] for r in self.replacement_rules)
                if not existing:
                    self.replacement_rules.append({
                        'name': rule['name'],
                        'pattern': rule['pattern'],
                        'replacement': rule['replacement'],
                        'active': True
                    })
                    added_count += 1
                    print(f"➕ Добавлено правило: {rule['name']}")
            
            if added_count > 0:
                # Сохраняем правила
                self.save_replacement_rules(self.replacement_rules)
                print(f"✅ Автоматически добавлено {added_count} правил для единиц измерения")
            
            return added_count
            
        except Exception as e:
            print(f"❌ Ошибка при автоматическом анализе единиц измерения: {e}")
            return 0
    
    def create_unit_conversion_rules(self, values):
        """Создает правила преобразования единиц измерения (расширенная версия для IP-видеонаблюдения)"""
        unit_patterns = {
            # === ФИЗИЧЕСКИЕ РАЗМЕРЫ ===
            'inch': ('дюйм', r'\\b(\\d+(?:\\.\\d+)?)"\\b'),
            'inches': ('дюймов', r'\\b(\\d+(?:\\.\\d+)?)\\s*inches?\\b'),
            'ft': ('фут', r'\\b(\\d+(?:\\.\\d+)?)\\s*ft\\b'),
            'feet': ('футов', r'\\b(\\d+(?:\\.\\d+)?)\\s*feet\\b'),
            'mm': ('мм', r'\\b(\\d+(?:\\.\\d+)?)\\s*mm\\b'),
            'cm': ('см', r'\\b(\\d+(?:\\.\\d+)?)\\s*cm\\b'),
            'meter': ('метр', r'\\b(\\d+(?:\\.\\d+)?)\\s*meters?\\b'),
            'm': ('м', r'\\b(\\d+(?:\\.\\d+)?)\\s*m\\b'),
            'μm': ('мкм', r'\\b(\\d+(?:\\.\\d+)?)\\s*μm\\b'),
            'nm': ('нм', r'\\b(\\d+(?:\\.\\d+)?)\\s*nm\\b'),
            
            # === ВЕС ===
            'kg': ('кг', r'\\b(\\d+(?:\\.\\d+)?)\\s*kg\\b'),
            'gram': ('грамм', r'\\b(\\d+(?:\\.\\d+)?)\\s*grams?\\b'),
            'g': ('г', r'\\b(\\d+(?:\\.\\d+)?)\\s*g\\b'),
            'lb': ('фунт', r'\\b(\\d+(?:\\.\\d+)?)\\s*lbs?\\b'),
            'oz': ('унция', r'\\b(\\d+(?:\\.\\d+)?)\\s*oz\\b'),
            
            # === ЭЛЕКТРИЧЕСКИЕ ПАРАМЕТРЫ ===
            'volt': ('вольт', r'\\b(\\d+(?:\\.\\d+)?)\\s*V\\b'),
            'v': ('В', r'\\b(\\d+(?:\\.\\d+)?)\\s*V\\b'),
            'mv': ('мВ', r'\\b(\\d+(?:\\.\\d+)?)\\s*mV\\b'),
            'kv': ('кВ', r'\\b(\\d+(?:\\.\\d+)?)\\s*kV\\b'),
            'amp': ('ампер', r'\\b(\\d+(?:\\.\\d+)?)\\s*A\\b'),
            'a': ('А', r'\\b(\\d+(?:\\.\\d+)?)\\s*A\\b'),
            'ma': ('мА', r'\\b(\\d+(?:\\.\\d+)?)\\s*mA\\b'),
            'μa': ('мкА', r'\\b(\\d+(?:\\.\\d+)?)\\s*μA\\b'),
            'watt': ('ватт', r'\\b(\\d+(?:\\.\\d+)?)\\s*W\\b'),
            'w': ('Вт', r'\\b(\\d+(?:\\.\\d+)?)\\s*W\\b'),
            'mw': ('мВт', r'\\b(\\d+(?:\\.\\d+)?)\\s*mW\\b'),
            'kw': ('кВт', r'\\b(\\d+(?:\\.\\d+)?)\\s*kW\\b'),
            'wh': ('Вт⋅ч', r'\\b(\\d+(?:\\.\\d+)?)\\s*Wh\\b'),
            'kwh': ('кВт⋅ч', r'\\b(\\d+(?:\\.\\d+)?)\\s*kWh\\b'),
            'mah': ('мАч', r'\\b(\\d+(?:\\.\\d+)?)\\s*mAh\\b'),
            'ohm': ('Ом', r'\\b(\\d+(?:\\.\\d+)?)\\s*Ohm\\b'),
            'kohm': ('кОм', r'\\b(\\d+(?:\\.\\d+)?)\\s*kOhm\\b'),
            
            # === ЧАСТОТА И ВРЕМЯ ===
            'hz': ('Гц', r'\\b(\\d+(?:\\.\\d+)?)\\s*Hz\\b'),
            'khz': ('кГц', r'\\b(\\d+(?:\\.\\d+)?)\\s*kHz\\b'),
            'mhz': ('МГц', r'\\b(\\d+(?:\\.\\d+)?)\\s*MHz\\b'),
            'ghz': ('ГГц', r'\\b(\\d+(?:\\.\\d+)?)\\s*GHz\\b'),
            'fps': ('кадр/с', r'\\b(\\d+(?:\\.\\d+)?)\\s*fps\\b'),
            'sec': ('сек', r'\\b(\\d+(?:\\.\\d+)?)\\s*sec\\b'),
            's': ('с', r'\\b(\\d+(?:\\.\\d+)?)\\s*s\\b'),
            'ms': ('мс', r'\\b(\\d+(?:\\.\\d+)?)\\s*ms\\b'),
            'μs': ('мкс', r'\\b(\\d+(?:\\.\\d+)?)\\s*μs\\b'),
            'ns': ('нс', r'\\b(\\d+(?:\\.\\d+)?)\\s*ns\\b'),
            'min': ('мин', r'\\b(\\d+(?:\\.\\d+)?)\\s*min\\b'),
            'hour': ('час', r'\\b(\\d+(?:\\.\\d+)?)\\s*hours?\\b'),
            'h': ('ч', r'\\b(\\d+(?:\\.\\d+)?)\\s*h\\b'),
            'day': ('день', r'\\b(\\d+(?:\\.\\d+)?)\\s*days?\\b'),
            
            # === ПАМЯТЬ И ДАННЫЕ ===
            'b': ('Б', r'\\b(\\d+(?:\\.\\d+)?)\\s*B\\b'),
            'byte': ('байт', r'\\b(\\d+(?:\\.\\d+)?)\\s*bytes?\\b'),
            'kb': ('КБ', r'\\b(\\d+(?:\\.\\d+)?)\\s*KB\\b'),
            'mb': ('МБ', r'\\b(\\d+(?:\\.\\d+)?)\\s*MB\\b'),
            'gb': ('ГБ', r'\\b(\\d+(?:\\.\\d+)?)\\s*GB\\b'),
            'tb': ('ТБ', r'\\b(\\d+(?:\\.\\d+)?)\\s*TB\\b'),
            'pb': ('ПБ', r'\\b(\\d+(?:\\.\\d+)?)\\s*PB\\b'),
            'eb': ('ЭБ', r'\\b(\\d+(?:\\.\\d+)?)\\s*EB\\b'),
            
            # === СКОРОСТЬ ПЕРЕДАЧИ ДАННЫХ ===
            'bps': ('бит/с', r'\\b(\\d+(?:\\.\\d+)?)\\s*bps\\b'),
            'kbps': ('кбит/с', r'\\b(\\d+(?:\\.\\d+)?)\\s*kbps\\b'),
            'mbps': ('Мбит/с', r'\\b(\\d+(?:\\.\\d+)?)\\s*Mbps\\b'),
            'gbps': ('Гбит/с', r'\\b(\\d+(?:\\.\\d+)?)\\s*Gbps\\b'),
            'tbps': ('Тбит/с', r'\\b(\\d+(?:\\.\\d+)?)\\s*Tbps\\b'),
            
            # === ИЗОБРАЖЕНИЯ И ВИДЕО ===
            'mp': ('мп', r'\\b(\\d+(?:\\.\\d+)?)\\s*mp\\b'),
            'megapixel': ('мегапикселей', r'\\b(\\d+(?:\\.\\d+)?)\\s*megapixels?\\b'),
            'px': ('пикс', r'\\b(\\d+(?:\\.\\d+)?)\\s*px\\b'),
            'pixel': ('пиксель', r'\\b(\\d+(?:\\.\\d+)?)\\s*pixels?\\b'),
            'dpi': ('точек/дюйм', r'\\b(\\d+(?:\\.\\d+)?)\\s*dpi\\b'),
            'ppi': ('пикс/дюйм', r'\\b(\\d+(?:\\.\\d+)?)\\s*ppi\\b'),
            'lpi': ('линий/дюйм', r'\\b(\\d+(?:\\.\\d+)?)\\s*lpi\\b'),
            
            # === ЗВУК И ОСВЕЩЕННОСТЬ ===
            'db': ('дБ', r'\\b(\\d+(?:\\.\\d+)?)\\s*dB\\b'),
            'dba': ('дБА', r'\\b(\\d+(?:\\.\\d+)?)\\s*dBA\\b'),
            'dbc': ('дБС', r'\\b(\\d+(?:\\.\\d+)?)\\s*dBC\\b'),
            'dbi': ('дБи', r'\\b(\\d+(?:\\.\\d+)?)\\s*dBi\\b'),
            'dbm': ('дБм', r'\\b(\\d+(?:\\.\\d+)?)\\s*dBm\\b'),
            'dbw': ('дБВт', r'\\b(\\d+(?:\\.\\d+)?)\\s*dBW\\b'),
            'dbfs': ('дБFS', r'\\b(\\d+(?:\\.\\d+)?)\\s*dBFS\\b'),
            'lux': ('лк', r'\\b(\\d+(?:\\.\\d+)?)\\s*lux\\b'),
            'lm': ('лм', r'\\b(\\d+(?:\\.\\d+)?)\\s*lm\\b'),
            'cd': ('кд', r'\\b(\\d+(?:\\.\\d+)?)\\s*cd\\b'),
            'cd/m²': ('кд/м²', r'\\b(\\d+(?:\\.\\d+)?)\\s*cd/m²\\b'),
            'nit': ('нит', r'\\b(\\d+(?:\\.\\d+)?)\\s*nit\\b'),
            
            # === ТЕМПЕРАТУРА (только Цельсий!) ===
            'celsius': ('°C', r'\\b(\\d+(?:\\.\\d+)?)\\s*°?C\\b'),
            'kelvin': ('К', r'\\b(\\d+(?:\\.\\d+)?)\\s*K\\b'),
            
            # === ДАВЛЕНИЕ И ВЛАЖНОСТЬ ===
            'pa': ('Па', r'\\b(\\d+(?:\\.\\d+)?)\\s*Pa\\b'),
            'kpa': ('кПа', r'\\b(\\d+(?:\\.\\d+)?)\\s*kPa\\b'),
            'mpa': ('МПа', r'\\b(\\d+(?:\\.\\d+)?)\\s*MPa\\b'),
            'bar': ('бар', r'\\b(\\d+(?:\\.\\d+)?)\\s*bar\\b'),
            'mbar': ('мбар', r'\\b(\\d+(?:\\.\\d+)?)\\s*mbar\\b'),
            'psi': ('фунт/кв.дюйм', r'\\b(\\d+(?:\\.\\d+)?)\\s*psi\\b'),
            'atm': ('атм', r'\\b(\\d+(?:\\.\\d+)?)\\s*atm\\b'),
            'mmhg': ('мм рт.ст.', r'\\b(\\d+(?:\\.\\d+)?)\\s*mmHg\\b'),
            'rh': ('%ОВ', r'\\b(\\d+(?:\\.\\d+)?)\\s*%?RH\\b'),
            
            # === УГЛЫ И ГЕОМЕТРИЯ ===
            'degree': ('°', r'\\b(\\d+(?:\\.\\d+)?)\\s*degrees?\\b'),
            'fov': ('поле зрения', r'\\b(\\d+(?:\\.\\d+)?)\\s*FOV\\b'),
            'aov': ('угол обзора', r'\\b(\\d+(?:\\.\\d+)?)\\s*AOV\\b'),
            'hfov': ('гориз. обзор', r'\\b(\\d+(?:\\.\\d+)?)\\s*HFOV\\b'),
            'vfov': ('верт. обзор', r'\\b(\\d+(?:\\.\\d+)?)\\s*VFOV\\b'),
            
            # === ПРОСТЫЕ ЗАМЕНЫ БЕЗ ЦИФР ===
            'yes': ('Да', r'\\bYes\\b'),
            'no': ('Нет', r'\\bNo\\b'),
            'support': ('Поддерживается', r'\\bSupport(?:ed)?\\b'),
            'available': ('Доступно', r'\\bAvailable\\b'),
            'enabled': ('Включено', r'\\bEnabled\\b'),
            'disabled': ('Отключено', r'\\bDisabled\\b'),
            'optional': ('Опционально', r'\\bOptional\\b'),
            'required': ('Требуется', r'\\bRequired\\b'),
            'manual': ('Ручной', r'\\bManual\\b'),
            'automatic': ('Автоматический', r'\\bAutomatic\\b'),
            'auto': ('Авто', r'\\bAuto\\b'),
            'high': ('Высокий', r'\\bHigh\\b'),
            'medium': ('Средний', r'\\bMedium\\b'),
            'low': ('Низкий', r'\\bLow\\b'),
            'none': ('Нет', r'\\bNone\\b'),
            'standard': ('Стандартный', r'\\bStandard\\b'),
            'advanced': ('Продвинутый', r'\\bAdvanced\\b'),
            'professional': ('Профессиональный', r'\\bProfessional\\b'),
            'indoor': ('Внутренний', r'\\bIndoor\\b'),
            'outdoor': ('Наружный', r'\\bOutdoor\\b'),
            'wireless': ('Беспроводной', r'\\bWireless\\b'),
            'wired': ('Проводной', r'\\bWired\\b'),
            'ethernet': ('Ethernet', r'\\bEthernet\\b'),
            'wifi': ('Wi-Fi', r'\\bWi-?Fi\\b'),
            'bluetooth': ('Bluetooth', r'\\bBluetooth\\b'),
            'poe': ('PoE', r'\\bPoE\\b'),
            'poe+': ('PoE+', r'\\bPoE\\+\\b'),
            'poe++': ('PoE++', r'\\bPoE\\+\\+\\b'),
            'onvif': ('ONVIF', r'\\bONVIF\\b'),
            'rtsp': ('RTSP', r'\\bRTSP\\b'),
            'rtmp': ('RTMP', r'\\bRTMP\\b'),
            'http': ('HTTP', r'\\bHTTP\\b'),
            'https': ('HTTPS', r'\\bHTTPS\\b'),
            'tcp': ('TCP', r'\\bTCP\\b'),
            'udp': ('UDP', r'\\bUDP\\b'),
            'ip': ('IP', r'\\bIP\\b'),
            'ir': ('ИК', r'\\bIR\\b'),
            'led': ('Светодиод', r'\\bLED\\b'),
            'ccd': ('ПЗС', r'\\bCCD\\b'),
            'cmos': ('КМОП', r'\\bCMOS\\b'),
            'ptz': ('ПТЗ', r'\\bPTZ\\b'),
            'dvr': ('Видеорегистратор', r'\\bDVR\\b'),
            'nvr': ('Сетевой регистратор', r'\\bNVR\\b'),
            'hdd': ('Жесткий диск', r'\\bHDD\\b'),
            'ssd': ('SSD', r'\\bSSD\\b'),
            'sd': ('SD карта', r'\\bSD\\b'),
            'micro sd': ('MicroSD', r'\\bMicro\\s*SD\\b'),
            'motion detection': ('Детекция движения', r'\\bMotion\\s*Detection\\b'),
            'night vision': ('Ночное видение', r'\\bNight\\s*Vision\\b'),
            'day/night': ('День/Ночь', r'\\bDay/?Night\\b'),
            'full hd': ('FullHD', r'\\bFull\\s*HD\\b'),
            '4k': ('4K', r'\\b4K\\b'),
            'ultra hd': ('UltraHD', r'\\bUltra\\s*HD\\b'),
            'wide dynamic range': ('Широкий динамический диапазон', r'\\bWide\\s*Dynamic\\s*Range\\b'),
            'wdr': ('ШДД', r'\\bWDR\\b'),
            'true wdr': ('Истинный ШДД', r'\\bTrue\\s*WDR\\b'),
        }
        
        # Ищем совпадения в значениях
        found_rules = []
        values_text = ' '.join(values).lower()
        
        for unit_key, (russian, pattern) in unit_patterns.items():
            # Проверяем, есть ли единица в данных
            simple_pattern = unit_key.replace('_', ' ')
            if any(simple_pattern in value.lower() for value in values):
                # Создаем правило с оригинальным паттерном
                rule_name = f"Автоперевод: {unit_key.title()}"
                if unit_key in ['yes', 'no', 'support', 'available', 'enabled', 'disabled', 
                              'optional', 'required', 'manual', 'automatic', 'high', 'medium', 
                              'low', 'none', 'standard', 'advanced', 'professional', 'indoor', 
                              'outdoor', 'wireless', 'wired']:
                    # Простая замена без групп
                    replacement = russian
                else:
                    # Замена с сохранением числа
                    replacement = f"\\1 {russian}"
                
                found_rules.append({
                    'name': rule_name,
                    'pattern': pattern,
                    'replacement': replacement,
                    'active': True
                })
        
        return found_rules

    def apply_replacement_rules(self, text):
        """Применяет правила замены к тексту"""
        if not hasattr(self, 'replacement_rules') or not self.replacement_rules:
            return text
        
        original_text = text
        result = text
        applied_rules = []
        
        for rule in self.replacement_rules:
            if rule.get('active', True):
                try:
                    pattern = rule.get('pattern', '')
                    replacement = rule.get('replacement', '')
                    old_result = result
                    result = re.sub(pattern, replacement, result)
                    if old_result != result:
                        applied_rules.append(rule.get('name', 'Unnamed'))
                except re.error:
                    # Игнорируем неверные регулярные выражения
                    continue
        
        if applied_rules and original_text != result:
            print(f"🔄 Правила замены: '{original_text}' → '{result}' (применены: {', '.join(applied_rules)})")
        
        return result

    def normalize_units_spacing(self, text):
        """🆕 Нормализация пробелов перед единицами измерения v4.9.2"""
        if not text or not isinstance(text, str):
            return text
            
        import re
        
        # Список единиц измерения для нормализации
        units = [
            # Длина
            'mm', 'cm', 'm', 'km', 'in', 'ft',
            # Вес  
            'mg', 'g', 'kg', 'lb', 'oz',
            # Электричество
            'V', 'A', 'W', 'Wh', 'kWh', 'mAh',
            # Частота
            'Hz', 'kHz', 'MHz', 'GHz',
            # Скорость передачи данных
            'bps', 'kbps', 'Mbps', 'Gbps', 'fps',
            # Температура
            '°C', '°F', 'K',
            # Освещенность
            'lux', 'lm', 'cd',
            # Память
            'B', 'KB', 'MB', 'GB', 'TB',
            # Давление
            'Pa', 'kPa', 'MPa', 'bar', 'psi',
            # Время
            'ms', 's', 'min', 'h',
        ]
        
        normalized = text
        changes_made = 0
        
        # Для каждой единицы измерения ищем паттерн "число+единица" и заменяем на "число + единица"
        for unit in units:
            # Паттерн: число (целое или дробное) + единица без пробела
            pattern = r'(\d+(?:\.\d+)?)\s*' + re.escape(unit) + r'\b'
            replacement = r'\1 ' + unit
            
            new_text = re.sub(pattern, replacement, normalized, flags=re.IGNORECASE)
            if new_text != normalized:
                changes_made += 1
                print(f"    🔧 НОРМАЛИЗАЦИЯ: '{normalized}' → '{new_text}'")
                normalized = new_text
        
        if changes_made > 0:
            print(f"🔧 Нормализовано единиц: {changes_made}")
            
        return normalized

    def apply_value_translation_rules(self, value_list):
        """🆕 УЛУЧШЕННАЯ функция перевода значений v4.9.2 - с нормализацией единиц"""
        try:
            if not value_list:
                return value_list
            
            print(f"🌐 Применение правил перевода значений к: {value_list}")
            
            translated_values = []
            translation_count = 0
            protected_count = 0
            normalization_count = 0
            
            for value in value_list:
                translated_value = value
                translation_applied = False
                
                # Проверка на защищенные аббревиатуры
                if self.is_protected_abbreviation(value):
                    print(f"  🛡️ ЗАЩИЩЕНО: '{value}' - известная аббревиатура")
                    translated_values.append(value)
                    protected_count += 1
                    continue
                
                # 🆕 СНАЧАЛА нормализуем пробелы перед единицами измерения
                normalized_value = self.normalize_units_spacing(translated_value)
                # 🆕 ДОПОЛНИТЕЛЬНО нормализуем формат значения
                normalized_value = normalize_value_format(normalized_value)
                if normalized_value != translated_value:
                    translated_value = normalized_value
                    normalization_count += 1
                    print(f"    🔧 НОРМАЛИЗОВАНО: '{value}' → '{normalized_value}'")
                
                # Применяем базовые правила из translation_dict
                if hasattr(self, 'translation_system') and self.translation_system:
                    try:
                        basic_translation = self.translation_system.translation_dict.get(translated_value)
                        if basic_translation and basic_translation != translated_value:
                            translated_value = basic_translation
                            translation_count += 1
                            translation_applied = True
                            print(f"    ✅ БАЗОВОЕ ПРАВИЛО: '{normalized_value}' → '{basic_translation}'")
                    except Exception as e:
                        print(f"    ⚠️ Ошибка применения базовых правил: {e}")
                
                # Если базовое правило не применилось, проверяем пользовательские правила
                if not translation_applied and hasattr(self, 'value_translation_rules') and self.value_translation_rules:
                    for rule in self.value_translation_rules:
                        if rule.get("active", True):
                            original_pattern = rule.get("original", "")
                            translation = rule.get("translation", "")
                            
                            if original_pattern and translation:
                                # Точное совпадение (регистро-независимое)
                                if translated_value.lower() == original_pattern.lower():
                                    translated_value = translation
                                    translation_count += 1
                                    translation_applied = True
                                    print(f"    ✅ ПОЛЬЗОВАТЕЛЬСКОЕ ПРАВИЛО (точное): '{original_pattern}' → '{translation}'")
                                    break
                                # Проверяем частичное совпадение (для единиц измерения)
                                elif original_pattern.lower() in translated_value.lower():
                                    import re
                                    pattern = r'\b' + re.escape(original_pattern) + r'\b'
                                    if re.search(pattern, translated_value, re.IGNORECASE):
                                        new_value = re.sub(pattern, translation, translated_value, flags=re.IGNORECASE)
                                        if new_value != translated_value:
                                            translated_value = new_value
                                            translation_count += 1
                                            translation_applied = True
                                            print(f"    ✅ ПОЛЬЗОВАТЕЛЬСКОЕ ПРАВИЛО (частичное): '{value}' → '{translated_value}'")
                                            break
                
                # Если ничего не применилось, применяем дополнительную проверку для единиц измерения
                if not translation_applied:
                    # Проверяем на kbps/Mbps паттерны с помощью регулярных выражений
                    import re
                    speed_patterns = {
                        r'\b(\d+(?:\.\d+)?)\s+kbps\b': r'\1 кбит/с',
                        r'\b(\d+(?:\.\d+)?)\s+Mbps\b': r'\1 Мбит/с',
                        r'\b(\d+(?:\.\d+)?)\s+Gbps\b': r'\1 Гбит/с',
                        r'\b(\d+(?:\.\d+)?)\s+bps\b': r'\1 бит/с',
                        r'\b(\d+(?:\.\d+)?)\s+fps\b': r'\1 к/с',
                    }
                    
                    for pattern, replacement in speed_patterns.items():
                        new_value = re.sub(pattern, replacement, translated_value, flags=re.IGNORECASE)
                        if new_value != translated_value:
                            translated_value = new_value
                            translation_count += 1
                            translation_applied = True
                            print(f"    ✅ ПАТТЕРН СКОРОСТИ: '{value}' → '{translated_value}'")
                            break
                
                translated_values.append(translated_value)
            
            if normalization_count > 0:
                print(f"🔧 Нормализовано значений: {normalization_count}")
            if translation_count > 0:
                print(f"🌐 Переведено значений: {translation_count}")
            if protected_count > 0:
                print(f"🛡️ Защищено аббревиатур: {protected_count}")
            
            return translated_values
        
        except Exception as e:
            print(f"❌ ОШИБКА в apply_value_translation_rules: {e}")
            return value_list

    def log_print(self, message, log_file=None):
        """Функция для вывода сообщений и в консоль, и в лог-файл"""
        print(message)
        if log_file:
            try:
                with open(log_file, 'a', encoding='utf-8') as f:
                    f.write(message + '\n')
            except Exception as e:
                print(f"⚠️ Ошибка записи в лог: {e}")

    def save_results(self):
        if self.original_df is None:
            messagebox.showwarning("Внимание", "Сначала загрузите файл!")
            return

        # 🆕 СОЗДАНИЕ ФАЙЛА ЛОГОВ
        from datetime import datetime
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        log_filename = f"debug_export_{timestamp}.log"
        
        # Создаем лог-файл и записываем заголовок
        try:
            with open(log_filename, 'w', encoding='utf-8') as f:
                f.write(f"ОТЛАДОЧНЫЙ ЛОГ ЭКСПОРТА\n")
                f.write(f"Время: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                f.write(f"=" * 80 + "\n\n")
        except Exception as e:
            print(f"⚠️ Не удалось создать лог-файл: {e}")
            log_filename = None

        # 🆕 ОТЛАДОЧНАЯ ИНФОРМАЦИЯ ДЛЯ ДИАГНОСТИКИ ПРОБЛЕМЫ
        self.log_print("\n" + "="*80, log_filename)
        self.log_print("🔍 ОТЛАДКА SAVE_RESULTS НАЧАТА", log_filename)
        self.log_print("="*80, log_filename)
        self.log_print(f"📊 Исходный файл: {self.original_df.shape[0]} строк, {self.original_df.shape[1]} столбцов", log_filename)
        self.log_print(f"📋 Столбцы файла: {list(self.original_df.columns)}", log_filename)

        # Получаем названия дополнительных столбцов из настроек
        extra_col_names = self.settings.get("extra_cols", [])
        
        # Проверяем, что выбранные столбцы существуют в DataFrame
        available_extra_cols = [col for col in extra_col_names if col in self.original_df.columns]
        
        if extra_col_names and not available_extra_cols:
            self.log_print(f"⚠️ Предупреждение: выбранные столбцы {extra_col_names} не найдены в файле", log_filename)
        
        cols_to_save = ["Артикул"] + available_extra_cols
        self.log_print(f"📋 Столбцы для сохранения: {cols_to_save}", log_filename)

        result = []

        html_col_idx = int(self.features_col.get()) - 1
        self.log_print(f"📋 Индекс столбца характеристик: {html_col_idx} (столбец: {self.original_df.columns[html_col_idx]})", log_filename)
        
        # Проблемные товары для отслеживания
        problematic_articles = ['IPC523-F255-NR', 'IPC2241-FN/FNW-SIR', 'IPC427', 'IPC422', 'IPC980']
        processed_problematic = []

        for idx, (_, row) in enumerate(self.original_df.iterrows()):
            sku = row.get("Артикул", None)
            if sku is None:
                continue
                
            is_problematic = sku in problematic_articles
            if is_problematic:
                self.log_print(f"\n🔍 ОБРАБОТКА ПРОБЛЕМНОГО ТОВАРА {idx}: {sku}", log_filename)
                
            item_data = {col: row[col] for col in cols_to_save if col in row}

            html = str(row[self.original_df.columns[html_col_idx]])
            
            if is_problematic:
                self.log_print(f"   📋 HTML длина: {len(html)}", log_filename)
                self.log_print(f"   📋 HTML начало: {html[:100]}...", log_filename)
                
            params = self.parse_html_table(html)
            
            if is_problematic:
                self.log_print(f"   ✅ Параметров найдено: {len(params)}", log_filename)
                if len(params) == 0:
                    self.log_print(f"   ❌ НЕ НАЙДЕНО ПАРАМЕТРОВ для {sku}!", log_filename)
                else:
                    self.log_print(f"   📋 Первые параметры: {list(params.keys())[:3]}...", log_filename)

            param_count = 0
            for param, value in params.items():
                try:
                    clean_value = self.remove_duplicates_from_cell(value)
                    mapped_param = self.mappings.get(param, param)

                    delimiter = ";"
                    node_id = None
                    for iid in self.tree.get_children():
                        vals = self.tree.item(iid, "values")
                        if vals[1] == param:
                            node_id = iid
                            break
                    if node_id:
                        try:
                            delimiter_val = self.tree.item(node_id, "values")[4].strip()
                            if delimiter_val:
                                delimiter = delimiter_val
                        except:
                            pass
                    
                    # 🆕 ФИНАЛЬНАЯ ЗАЩИТА ОТ ПУСТОГО РАЗДЕЛИТЕЛЯ v4.8.5
                    if not delimiter:
                        delimiter = ";"
                        self.log_print(f"      ⚠️ ПУСТОЙ разделитель заменен на ';'", log_filename)

                    split_vals = self.split_values(clean_value, delimiter)
                    
                    if is_problematic and param_count == 0:  # Показываем только для первого параметра
                        self.log_print(f"   🔄 Первый параметр '{param}':", log_filename)
                        self.log_print(f"      Исходное: {value[:50]}...", log_filename)
                        self.log_print(f"      Очищенное: {clean_value[:50]}...", log_filename)
                        self.log_print(f"      Разделитель: '{delimiter}'", log_filename)
                        self.log_print(f"      Разделенные: {split_vals}", log_filename)
                        
                    param_count += 1
                except Exception as e:
                    if is_problematic:
                        self.log_print(f"   ❌ Ошибка обработки параметра {param}: {e}", log_filename)
                    continue

                # 🆕 УЛУЧШЕННАЯ ОБРАБОТКА: Применяем маппинг к каждому значению отдельно
                # 🔧 ИСПРАВЛЕНО: НЕ применяем сопоставления к значениям
                split_vals_mapped = [val.strip() for val in split_vals if val.strip()]
                
                self.log_print(f"🔄 МНОЖЕСТВЕННЫЕ РАЗДЕЛИТЕЛИ '{delimiter}': {len(split_vals)} значений → {split_vals_mapped}", log_filename)

                split_vals_mapped = [self.apply_case_normalization(v) for v in split_vals_mapped]
                
                # 🆕 ВОССТАНОВЛЕНИЕ ЗАЩИЩЕННЫХ ЗНАЧЕНИЙ в split_vals_mapped
                split_vals_mapped = [self.restore_protected_values_in_string(v) for v in split_vals_mapped]
                
                seen_normalized = set()
                final_split_vals = []
                for v in split_vals_mapped:
                    key_norm = normalize_variant(v).lower()
                    if key_norm not in seen_normalized:
                        seen_normalized.add(key_norm)
                        final_split_vals.append(v)

                if param in self.manual_split_results:
                    result_str = self.manual_split_results[param]
                    # 🆕 ПРЕОБРАЗОВАНИЕ для WooCommerce: ";" → ", " (запятая с пробелом)
                    if "; " in result_str:
                        result_str = result_str.replace("; ", ", ")
                        self.log_print(f"🔄 ПРЕОБРАЗОВАНО для WooCommerce: '{param}' → '; ' заменено на ', '", log_filename)
                else:
                    result_str = ", ".join(final_split_vals)

                    # 🆕 ВОССТАНОВЛЕНИЕ ЗАЩИЩЕННЫХ ЗНАЧЕНИЙ при экспорте
                    result_str = self.restore_protected_values_in_string(result_str)

                    item_data[mapped_param] = result_str
                    
                    if is_problematic and param_count == 1:  # Показываем результат для первого параметра
                        self.log_print(f"      Финальный результат: '{result_str[:50]}...'", log_filename)

            if is_problematic:
                self.log_print(f"   📊 Обработано параметров: {param_count}", log_filename)
                self.log_print(f"   📊 Итого полей в товаре: {len(item_data)}", log_filename)
                if len(item_data) <= 1:  # Только Артикул
                    self.log_print(f"   ❌ ТОВАР БУДЕТ ПУСТЫМ!", log_filename)
                    processed_problematic.append(f"{sku} - ПУСТОЙ")
                else:
                    self.log_print(f"   ✅ Товар обработан успешно", log_filename)
                    processed_problematic.append(f"{sku} - ОК ({len(item_data)-1} полей)")

            result.append(item_data)

        df_result = pd.DataFrame(result)
        
        self.log_print(f"\n📊 ИТОГОВАЯ СТАТИСТИКА:", log_filename)
        self.log_print(f"   Обработано товаров: {len(result)}", log_filename)
        self.log_print(f"   Столбцов в результате: {len(df_result.columns) if not df_result.empty else 0}", log_filename)
        self.log_print(f"   Проблемные товары:", log_filename)
        for status in processed_problematic:
            self.log_print(f"      {status}", log_filename)
        self.log_print("="*80, log_filename)
        self.log_print("🔍 ОТЛАДКА SAVE_RESULTS ЗАВЕРШЕНА", log_filename)
        self.log_print("="*80, log_filename)
        
        # Сообщаем пользователю о сохранении лога
        if log_filename:
            self.log_print(f"\n💾 ОТЛАДОЧНЫЙ ЛОГ СОХРАНЕН В ФАЙЛ: {log_filename}", log_filename)
            messagebox.showinfo("Отладка", f"Отладочный лог сохранен в файл:\n{log_filename}\n\nИспользуйте этот файл для анализа проблем с экспортом.")
        
        # Определяем директорию для сохранения
        output_folder = self.output_folder_var.get().strip()
        if output_folder and os.path.exists(output_folder):
            initial_dir = output_folder
        else:
            initial_dir = os.getcwd()
        
        # Предлагаем имя файла на основе исходного
        source_file = self.source_file_var.get().strip()
        if source_file:
            base_name = os.path.splitext(os.path.basename(source_file))[0]
            suggested_name = f"{base_name}_обработанный.xlsx"
        else:
            suggested_name = "результат_обработки.xlsx"
        
        suggested_path = os.path.join(initial_dir, suggested_name)
        
        save_path = filedialog.asksaveasfilename(
            title="Сохранить результат обработки",
            initialdir=initial_dir,
            initialfile=suggested_name,
            defaultextension=".xlsx", 
            filetypes=[("Excel файлы", "*.xlsx"), ("Все файлы", "*.*")]
        )

        if save_path:
            try:
                df_result.to_excel(save_path, index=False)
                messagebox.showinfo("Успех", f"Результат сохранён в {save_path}")
                
                # Обновляем папку сохранения в настройках
                saved_folder = os.path.dirname(save_path)
                self.output_folder_var.set(saved_folder)
                self.save_settings()
                
            except Exception as e:
                messagebox.showerror("Ошибка сохранения", str(e))

    def on_close(self):
        """Обработчик закрытия приложения"""
        self.save_settings()
        self.save_merge_settings()
        self.root.destroy()

    def open_replacement_rules_editor(self):
        dialog = ReplacementRulesEditorDialog(self.root, self.replacement_rules, self.save_replacement_rules)

    def open_case_normalization_dialog(self):
        dialog = CaseNormalizationDialog(self.root, self.case_normalization, self.save_case_normalization)
    
    # 🆕 НОВЫЕ МЕТОДЫ v4.0 - Синонимы и цветовое выделение
    def open_value_synonyms_dialog(self):
        """Открывает диалог настройки синонимов значений"""
        dialog = ValueSynonymsDialog(self.root, self.synonyms_manager)
        if dialog.result:
            # После изменения синонимов обновляем отображение
            self.refresh_treeview_with_colors()
            messagebox.showinfo("Успешно", "Настройки синонимов сохранены")
    
    def open_protected_values_dialog(self):
        """🆕 Открывает диалог управления защищенными значениями"""
        dialog = Toplevel(self.root)
        dialog.title("🛡️ Защищенные значения от разделения")
        dialog.geometry("600x500")
        dialog.transient(self.root)
        dialog.grab_set()
        
        # Центрируем диалог
        dialog.update_idletasks()
        x = (dialog.winfo_screenwidth() // 2) - (dialog.winfo_width() // 2)
        y = (dialog.winfo_screenheight() // 2) - (dialog.winfo_height() // 2)
        dialog.geometry(f"+{x}+{y}")
        
        # Заголовок с описанием
        header_frame = Frame(dialog)
        header_frame.pack(fill=X, padx=10, pady=10)
        
        Label(header_frame, text="🛡️ Защищенные значения", 
              font=("Arial", 14, "bold")).pack()
        
        info_text = ("Значения из этого списка НЕ БУДУТ разделяться, даже если содержат символ разделителя.\n"
                    "Пример: при разделителе '/' значение 'B/W' останется как 'B/W', а не станет 'B; W'")
        Label(header_frame, text=info_text, 
              font=("Arial", 9), fg="gray", wraplength=550, justify=LEFT).pack(pady=5)
        
        # Пример использования
        example_frame = LabelFrame(dialog, text="💡 Пример использования", padx=5, pady=5)
        example_frame.pack(fill=X, padx=10, pady=5)
        
        example_text = ('Исходное: "Auto (ICR) / Color / B/W"\n'
                       'Разделитель: "/" (без защиты) → "Auto (ICR); Color; B; W" ❌\n'
                       'Разделитель: "/" (с защитой "B/W") → "Auto (ICR); Color; B/W" ✅')
        Label(example_frame, text=example_text, font=("Courier", 9), 
              justify=LEFT, bg="#FFFACD").pack(fill=X)
        
        # Список защищенных значений
        list_frame = LabelFrame(dialog, text="📋 Текущие защищенные значения", padx=5, pady=5)
        list_frame.pack(fill=BOTH, expand=True, padx=10, pady=5)
        
        # Создаем Treeview для списка
        tree_frame = Frame(list_frame)
        tree_frame.pack(fill=BOTH, expand=True)
        
        protected_tree = ttk.Treeview(tree_frame, columns=('value',), show='headings', height=10)
        protected_tree.heading('value', text='Защищенное значение')
        protected_tree.column('value', width=500)
        protected_tree.pack(side=LEFT, fill=BOTH, expand=True)
        
        scrollbar = Scrollbar(tree_frame, orient=VERTICAL, command=protected_tree.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        protected_tree.configure(yscrollcommand=scrollbar.set)
        
        # Заполняем список текущими значениями
        def refresh_protected_list():
            for item in protected_tree.get_children():
                protected_tree.delete(item)
            for value in self.protected_values:
                protected_tree.insert('', END, values=(value,))
        
        refresh_protected_list()
        
        # Кнопки управления
        buttons_frame = Frame(list_frame)
        buttons_frame.pack(fill=X, pady=5)
        
        def add_protected_value():
            """Добавить новое защищенное значение"""
            value = simpledialog.askstring(
                "Добавить защищенное значение", 
                "Введите значение, которое НЕ должно разделяться:\n(например: B/W, I/O, AC/DC, USB-C)"
            )
            if value and value.strip():
                clean_value = value.strip()
                if clean_value not in self.protected_values:
                    self.protected_values.append(clean_value)
                    self.save_protected_values()
                    refresh_protected_list()
                    messagebox.showinfo("Добавлено", f"Значение '{clean_value}' добавлено в защищенные")
                else:
                    messagebox.showwarning("Предупреждение", f"Значение '{clean_value}' уже в списке")
        
        def remove_protected_value():
            """Удалить выбранное защищенное значение"""
            selection = protected_tree.selection()
            if not selection:
                messagebox.showwarning("Предупреждение", "Выберите значение для удаления")
                return
            
            for item in selection:
                value = protected_tree.item(item, 'values')[0]
                if value in self.protected_values:
                    self.protected_values.remove(value)
            
            self.save_protected_values()
            refresh_protected_list()
            messagebox.showinfo("Удалено", "Выбранные значения удалены")
        
        def add_common_values():
            """Добавить часто используемые защищенные значения"""
            common_values = ["B/W", "I/O", "AC/DC", "USB-C", "Wi-Fi", "Bluetooth", "RJ-45", "RS-232", "RS-485"]
            added = []
            
            for value in common_values:
                if value not in self.protected_values:
                    self.protected_values.append(value)
                    added.append(value)
            
            if added:
                self.save_protected_values()
                refresh_protected_list()
                messagebox.showinfo("Добавлено", f"Добавлено {len(added)} распространенных значений:\n" + ", ".join(added))
            else:
                messagebox.showinfo("Информация", "Все распространенные значения уже в списке")
        
        Button(buttons_frame, text="➕ Добавить", command=add_protected_value).pack(side=LEFT, padx=2)
        Button(buttons_frame, text="🗑️ Удалить", command=remove_protected_value).pack(side=LEFT, padx=2)
        Button(buttons_frame, text="📥 Добавить распространенные", command=add_common_values).pack(side=LEFT, padx=2)
        
        # Кнопка закрытия
        close_frame = Frame(dialog)
        close_frame.pack(fill=X, padx=10, pady=10)
        
        Button(close_frame, text="✅ Готово", command=dialog.destroy, bg="#E6FFE6").pack()
    
    def detect_similar_parameters(self):
        """Определяет похожие параметры для цветового выделения"""
        if not hasattr(self, 'tree') or not self.tree.get_children():
            return
        
        # Собираем все параметры
        all_params = []
        for item in self.tree.get_children():
            values = self.tree.item(item, "values")
            param = values[1]  # Исходный параметр
            if param not in all_params:
                all_params.append(param)
        
        # Находим похожие группы
        self.similar_param_groups = {}
        processed = set()
        color_index = 0
        
        for param1 in all_params:
            if param1 in processed:
                continue
                
            similar_group = [param1]
            processed.add(param1)
            
            # Ищем похожие параметры
            for param2 in all_params:
                if param2 == param1 or param2 in processed:
                    continue
                    
                # Проверяем похожесть
                similarity = fuzz.ratio(param1.lower(), param2.lower())
                if similarity >= 70:  # Порог похожести
                    similar_group.append(param2)
                    processed.add(param2)
            
            # Если группа содержит более одного параметра, назначаем цвет
            if len(similar_group) > 1:
                representative = min(similar_group, key=len)  # Самый короткий как представитель
                self.similar_param_groups[representative] = similar_group
                
                # Назначаем цвет всем параметрам группы
                color = self.similar_param_colors[color_index % len(self.similar_param_colors)]
                for param in similar_group:
                    self.param_color_map[param] = color
                color_index += 1
        
        print(f"🎨 Найдено групп похожих параметров: {len(self.similar_param_groups)}")
        for rep, group in self.similar_param_groups.items():
            print(f"  🎨 {rep}: {group}")
    
    def refresh_treeview_with_colors(self):
        """Обновляет отображение дерева с цветовым выделением"""
        if not hasattr(self, 'tree'):
            return
            
        # Сначала определяем похожие параметры
        self.detect_similar_parameters()
        
        # Применяем цвета
        for item in self.tree.get_children():
            values = self.tree.item(item, "values")
            param = values[1]  # Исходный параметр
            
            # Получаем цвет для параметра
            if param in self.param_color_map:
                color = self.param_color_map[param]
                # Настраиваем теги для цветового выделения
                tag_name = f"color_{color.replace('#', '')}"
                self.tree.set(item, tags=(tag_name,))
                self.tree.tag_configure(tag_name, background=color)
            else:
                # Убираем цветовое выделение
                self.tree.set(item, tags=())

    def load_mappings(self):
        if os.path.exists(self.MAPPINGS_FILE):
            try:
                with open(self.MAPPINGS_FILE, "r", encoding="utf-8") as f:
                    self.mappings = json.load(f)
            except:
                self.mappings = {}
        else:
            self.mappings = {}

    def save_mappings(self):
        try:
            with open(self.MAPPINGS_FILE, "w", encoding="utf-8") as f:
                json.dump(self.mappings, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"Ошибка сохранения маппингов: {e}")

    def refresh_mapping_table(self):
        if not hasattr(self, 'mapping_tree'):
            return
            
        self.mapping_tree.delete(*self.mapping_tree.get_children())
        self._mapping_items_cache = []
        for orig, repl in self.mappings.items():
            self._mapping_items_cache.append((orig, repl))
        self._mapping_items_cache.sort(key=lambda x: x[0].lower())
        for orig, repl in self._mapping_items_cache:
            self.mapping_tree.insert("", "end", values=(orig, repl))

    def update_main_tree_with_mappings(self):
        """Обновляет основную таблицу с учетом сопоставлений И автоперевода"""
        updated_count = 0
        
        for item in self.tree.get_children():
            values = list(self.tree.item(item, "values"))
            param = values[1]  # Исходный параметр
            orig_values = values[2]  # Исходные значения
            delimiter = values[4]
            if not delimiter:
                delimiter = ";"

            # 🔧 ИСПРАВЛЕНО: Применяем сопоставления + автоперевод
            # Шаг 1: Применяем сопоставление из таблицы
            mapped_param = self.mappings.get(param, param)
            
            # Шаг 2: Применяем автоперевод к результату сопоставления
            new_param = self.apply_auto_translation(mapped_param, is_parameter=True)
            
            # Проверяем, изменился ли параметр
            if values[3] != new_param:
                updated_count += 1
                
            values[3] = new_param

            if param in self.manual_split_results:
                split_result_str = self.manual_split_results[param]
            else:
                if param in self.manual_split_values and isinstance(self.manual_split_values[param], str) and self.manual_split_values[param] != False:
                    split_vals = [v.strip() for v in self.manual_split_values[param].split(";") if v.strip()]
                else:
                    # 🆕 ДОПОЛНИТЕЛЬНАЯ ЗАЩИТА ОТ ПУСТОГО РАЗДЕЛИТЕЛЯ v4.8.5
                    if not delimiter:
                        delimiter = ";"
                        print(f"  ⚠️ ДУБЛИРОВАННАЯ ЗАЩИТА: пустой разделитель → ';' для '{param}'")
                    
                    split_vals = self.split_values(orig_values, delimiter)

                # 🔧 ИСПРАВЛЕНО: БЕЗ сопоставлений к значениям (только для параметров!)
                split_vals_processed = [val.strip() for val in split_vals if val.strip()]
                print(f"🔧 DEBUG UPDATE {param}: 1.После разделения: {split_vals_processed}")
                
                split_vals_processed = [self.apply_case_normalization(v) for v in split_vals_processed]
                print(f"🔧 DEBUG UPDATE {param}: 2.После нормализации: {split_vals_processed}")
                
                split_vals_processed = self.remove_duplicates_from_list(split_vals_processed)
                print(f"🔧 DEBUG UPDATE {param}: 3.После первой дедупликации: {split_vals_processed}")
                
                # 🆕 ВОССТАНОВЛЕНИЕ ЗАЩИЩЕННЫХ ЗНАЧЕНИЙ в интерфейсе
                split_vals_processed = [self.restore_protected_values_in_string(v) for v in split_vals_processed]
                print(f"🔧 DEBUG UPDATE {param}: 4.После восстановления: {split_vals_processed}")
                
                # 🆕 ПРИМЕНЯЕМ ПРАВИЛА ЗАМЕНЫ
                split_vals_processed = [self.apply_replacement_rules(v) for v in split_vals_processed]
                print(f"🔧 DEBUG UPDATE {param}: 5.После правил замены: {split_vals_processed}")
                
                # 🆕 ПРИМЕНЯЕМ СИНОНИМЫ ЗНАЧЕНИЙ
                split_vals_processed = self.synonyms_manager.merge_synonyms_in_list(split_vals_processed)
                print(f"🔧 DEBUG UPDATE {param}: 6.После синонимов: {split_vals_processed}")
                
                # 🔧 ИСПРАВЛЕНО: Удаляем дубликаты ПОСЛЕ применения синонимов
                split_vals_processed = self.remove_duplicates_from_list(split_vals_processed)
                print(f"🔧 DEBUG UPDATE {param}: 7.После дедупликации синонимов: {split_vals_processed}")
                
                # 🔧 ИСПРАВЛЕНО: НЕ применяем автоперевод к значениям (только к параметрам!)
                # Автоперевод значений вызывал частичные переводы типа "Audio in" → "Аудио in"
                # Значения должны обрабатываться только правилами замены и синонимами
                
                # 🔧 ФИНАЛЬНАЯ ДЕДУПЛИКАЦИЯ (без автоперевода)
                split_vals_processed = self.remove_duplicates_from_list(split_vals_processed)
                print(f"🔧 DEBUG UPDATE {param}: 8.ФИНАЛЬНАЯ дедупликация: {split_vals_processed}")
                
                split_result_str = "; ".join(split_vals_processed)
                print(f"🔧 DEBUG UPDATE {param}: 9.ИТОГОВЫЙ результат: '{split_result_str}'")

            values[5] = split_result_str
            self.tree.item(item, values=values)
        
        # Выводим информацию об обновлениях только если были изменения
        if updated_count > 0:
            print(f"🔄 Обновлено переводов в основной таблице: {updated_count}")

    def load_excl(self):
        if os.path.exists(self.EXCLUSIONS_FILE):
            try:
                with open(self.EXCLUSIONS_FILE, "r", encoding="utf-8") as f:
                    self.exclusions = json.load(f)
            except:
                self.exclusions = []
        else:
            self.exclusions = []

    def load_protected_values(self):
        """🆕 Загрузка защищенных значений от разделения"""
        if os.path.exists(self.PROTECTED_VALUES_FILE):
            try:
                with open(self.PROTECTED_VALUES_FILE, "r", encoding="utf-8") as f:
                    self.protected_values = json.load(f)
                print(f"🛡️ ЗАГРУЖЕНО {len(self.protected_values)} защищенных значений: {self.protected_values}")
            except Exception as e:
                print(f"⚠️ Ошибка загрузки защищенных значений: {e}")
                self.protected_values = []
        else:
            self.protected_values = []

    def save_protected_values(self):
        """🆕 Сохранение защищенных значений"""
        try:
            with open(self.PROTECTED_VALUES_FILE, "w", encoding="utf-8") as f:
                json.dump(self.protected_values, f, ensure_ascii=False, indent=2)
            print(f"💾 СОХРАНЕНО {len(self.protected_values)} защищенных значений")
        except Exception as e:
            print(f"⚠️ Ошибка сохранения защищенных значений: {e}")

    def restore_protected_values_in_string(self, text):
        """🆕 Восстановление защищенных значений из плейсхолдеров в строке"""
        if not text or not self.protected_values:
            return text
            
        # Ищем и заменяем плейсхолдеры на оригинальные защищенные значения
        restored_text = text
        for i, protected_value in enumerate(self.protected_values):
            placeholder = f"__PROTECTED_{i}__"
            if placeholder in restored_text:
                restored_text = restored_text.replace(placeholder, protected_value)
                print(f"🔓 ВОССТАНОВЛЕНО в экспорте: '{placeholder}' → '{protected_value}'")
        
        return restored_text

    def load_case_normalization(self):
        if os.path.exists(self.CASE_NORM_FILE):
            try:
                with open(self.CASE_NORM_FILE, "r", encoding="utf-8") as f:
                    self.case_normalization = json.load(f)
            except:
                self.case_normalization = {}
        else:
            self.case_normalization = {}

    def load_replacement_rules(self):
        if os.path.exists(self.REPLACEMENT_RULES_FILE):
            try:
                with open(self.REPLACEMENT_RULES_FILE, "r", encoding="utf-8") as f:
                    self.replacement_rules = json.load(f)
            except:
                self.replacement_rules = []
        else:
            self.replacement_rules = []

    def save_replacement_rules(self, rules):
        self.replacement_rules = rules
        try:
            with open(self.REPLACEMENT_RULES_FILE, "w", encoding="utf-8") as f:
                json.dump(self.replacement_rules, f, ensure_ascii=False, indent=2)
            
            # 🆕 ДИНАМИЧЕСКОЕ ОБНОВЛЕНИЕ после сохранения правил замены v4.9
            print("🔄 Выполняем динамическое обновление после изменения правил замены...")
            self.dynamic_update_main_table()
        except Exception as e:
            messagebox.showerror("Ошибка", f"Не удалось сохранить правила замены: {str(e)}")

    def save_case_normalization(self, case_norm):
        self.case_normalization = case_norm
        try:
            with open(self.CASE_NORM_FILE, "w", encoding="utf-8") as f:
                json.dump(self.case_normalization, f, ensure_ascii=False, indent=2)
        except Exception as e:
            messagebox.showerror("Ошибка", f"Не удалось сохранить настройки нормализации: {str(e)}")

    def update_exclusions_tree(self):
        """Обновление дерева исключений"""
        for item in self.exclusions_tree.get_children():
            self.exclusions_tree.delete(item)
        
        for exclusion in self.exclusions:
            self.exclusions_tree.insert('', END, values=(exclusion,))

    def add_exclusion(self):
        """Добавление исключения"""
        exclusion = simpledialog.askstring("Добавить исключение", "Введите исключение:")
        if exclusion and exclusion.strip():
            self.exclusions.append(exclusion.strip())
            self.update_exclusions_tree()

    def delete_exclusion(self):
        """Удаление исключения"""
        selection = self.exclusions_tree.selection()
        if selection:
            for item in selection:
                exclusion = self.exclusions_tree.item(item, 'values')[0]
                if exclusion in self.exclusions:
                    self.exclusions.remove(exclusion)
            self.update_exclusions_tree()

    def filter_mapping_table(self, *args):
        if not hasattr(self, '_mapping_items_cache'):
            return
            
        search_text = self.search_var.get().lower()
        self.mapping_tree.delete(*self.mapping_tree.get_children())
        filtered = []
        for orig, repl in self._mapping_items_cache:
            if search_text in orig.lower():
                filtered.append((orig, repl))
        for orig, repl in filtered:
            self.mapping_tree.insert("", "end", values=(orig, repl))

    def sort_mapping_tree_by_original_col(self):
        if not hasattr(self, '_mapping_items_cache'):
            return
            
        self.mapping_sort_reverse = not self.mapping_sort_reverse
        self._mapping_items_cache.sort(key=lambda x: x[0].lower(), reverse=self.mapping_sort_reverse)
        self.mapping_tree.delete(*self.mapping_tree.get_children())
        for orig, repl in self._mapping_items_cache:
            self.mapping_tree.insert("", "end", values=(orig, repl))

    def add_mapping(self):
        original = simpledialog.askstring("Добавить сопоставление", "Исходное значение:")
        if original:
            replacement = simpledialog.askstring("Добавить сопоставление", "Замена:")
            if replacement is not None:
                self.mappings[original] = replacement
                self.save_mappings()
                self.refresh_mapping_table()
                # 🔧 ИСПРАВЛЕНО: Автоматически обновляем основную таблицу
                self.update_main_tree_with_mappings()

    def delete_mapping(self):
        selected = self.mapping_tree.selection()
        if selected:
            for item in selected:
                values = self.mapping_tree.item(item, 'values')
                original = values[0]
                if original in self.mappings:
                    del self.mappings[original]
            self.save_mappings()
            self.refresh_mapping_table()
            # 🔧 ИСПРАВЛЕНО: Автоматически обновляем основную таблицу
            self.update_main_tree_with_mappings()

    def delete_all_mappings(self):
        if messagebox.askyesno("Подтверждение", "Удалить все сопоставления?"):
            self.mappings.clear()
            self.save_mappings()
            self.refresh_mapping_table()
            # 🔧 ИСПРАВЛЕНО: Автоматически обновляем основную таблицу
            self.update_main_tree_with_mappings()

    def import_mappings(self):
        filename = filedialog.askopenfilename(filetypes=[("JSON files", "*.json")])
        if filename:
            try:
                with open(filename, "r", encoding="utf-8") as f:
                    imported_mappings = json.load(f)
                self.mappings.update(imported_mappings)
                self.save_mappings()
                self.refresh_mapping_table()
                # 🔧 ИСПРАВЛЕНО: Автоматически обновляем основную таблицу
                self.update_main_tree_with_mappings()
                messagebox.showinfo("Успешно", f"Импортировано {len(imported_mappings)} сопоставлений")
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось импортировать: {str(e)}")

    def export_mappings(self):
        filename = filedialog.asksaveasfilename(
            defaultextension=".json",
            filetypes=[("JSON files", "*.json")]
        )
        if filename:
            try:
                with open(filename, "w", encoding="utf-8") as f:
                    json.dump(self.mappings, f, ensure_ascii=False, indent=2)
                messagebox.showinfo("Успешно", f"Экспортировано {len(self.mappings)} сопоставлений")
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось экспортировать: {str(e)}")

    def update_df_from_treeview(self):
        """Обновление DataFrame из текущего содержимого Treeview"""
        if not hasattr(self, 'tree') or not self.tree.get_children():
            return
        
        # Создаем новый DataFrame на основе текущего содержимого treeview
        self.df = pd.DataFrame([{
            "№": int(values[0]),
            "Исходный параметр": values[1],
            "Исходные значения": values[2],
            "Новый параметр": values[3],
            "Разделитель": values[4],
            "Результат разделения": values[5]
        } for item in self.tree.get_children() for values in [self.tree.item(item, "values")]])

    def normalize_all_values_format(self, show_message=True):
        """Нормализация формата всех значений
        
        Args:
            show_message (bool): Показывать ли сообщение о завершении (False при автоматическом вызове)
        """
        if not hasattr(self, 'df') or self.df is None or self.df.empty:
            if show_message:
                messagebox.showwarning("Ошибка", "Сначала обработайте таблицу")
            return
        
        count = 0
        for item in self.tree.get_children():
            values = list(self.tree.item(item, "values"))
            split_result = values[5]
            if split_result:
                normalized_values = []
                for val in split_result.split(";"):
                    val = val.strip()
                    if val:
                        normalized_val = normalize_value_format(val)
                        normalized_values.append(normalized_val)
                # Удаляем дубликаты, сохраняя порядок
                seen = set()
                unique_normalized = []
                for v in normalized_values:
                    v_lower = v.lower()
                    if v_lower not in seen:
                        seen.add(v_lower)
                        unique_normalized.append(v)
                new_split_result = "; ".join(unique_normalized)
                if new_split_result != split_result:
                    values[5] = new_split_result
                    self.tree.item(item, values=values)
                    count += 1
        
        # Обновляем df и отображение
        self.update_df_from_treeview()
        
        if show_message:
            messagebox.showinfo("Завершено", f"Нормализован формат в {count} строках")

    def force_refresh_delimiters(self):
        """🔧 МАССОВОЕ ОБНОВЛЕНИЕ всех разделителей (обычно не нужно - обновление происходит автоматически)"""
        if not hasattr(self, 'df') or self.df is None or self.df.empty:
            messagebox.showwarning("Ошибка", "Сначала обработайте таблицу")
            return
        
        # Предупреждаем пользователя
        result = messagebox.askyesno(
            "Массовое обновление", 
            "🔄 Разделители теперь обновляются автоматически при изменении!\n\n" +
            "Эта функция нужна только для массового обновления всех строк.\n\n" +
            "Продолжить массовое обновление?"
        )
        
        if not result:
            return
        
        print("\n🔄 МАССОВОЕ ОБНОВЛЕНИЕ ВСЕХ РАЗДЕЛИТЕЛЕЙ НАЧАТО")
        print("=" * 80)
        
        updated_count = 0
        
        for item in self.tree.get_children():
            values = list(self.tree.item(item, "values"))
            param = values[1]  # Исходный параметр
            orig_values = values[2]  # Исходные значения
            delimiter = values[4]  # Разделитель
            current_result = values[5]  # Текущий результат разделения
            
            # Применяем улучшенную логику разделения
            print(f"\n🔍 ОБНОВЛЕНИЕ: '{param}'")
            print(f"  📋 Разделитель: '{delimiter}'")
            print(f"  📋 Исходные значения: '{orig_values}'")
            print(f"  📋 Текущий результат: '{current_result}'")
            
            # 🆕 ЗАЩИТА ОТ ПУСТОГО РАЗДЕЛИТЕЛЯ v4.8.5
            if not delimiter:
                delimiter = ";"
                print(f"  ⚠️ ЗАМЕНА пустого разделителя → ';'")
            
            # 🆕 ИСПОЛЬЗУЕМ УЛУЧШЕННУЮ ФУНКЦИЮ split_values
            new_split_vals = self.split_values(orig_values, delimiter)
            
            # 🔧 ИСПРАВЛЕНО: НЕ применяем сопоставления к значениям
            split_vals_mapped = [val.strip() for val in new_split_vals if val.strip()]
            
            # Применяем нормализацию регистра
            split_vals_mapped = [self.apply_case_normalization(v) for v in split_vals_mapped]
            
            # Удаляем дубликаты
            split_vals_mapped = self.remove_duplicates_from_list(split_vals_mapped)
            
            # 🆕 ВОССТАНОВЛЕНИЕ ЗАЩИЩЕННЫХ ЗНАЧЕНИЙ в интерфейсе
            split_vals_mapped = [self.restore_protected_values_in_string(v) for v in split_vals_mapped]
            
            # Формируем новый результат
            new_result = "; ".join(split_vals_mapped)
            
            print(f"  ✅ Новый результат: '{new_result}'")
            
            # Обновляем только если результат изменился
            if new_result != current_result:
                values[5] = new_result
                self.tree.item(item, values=values)
                updated_count += 1
                print(f"  🔄 ОБНОВЛЕНО!")
            else:
                print(f"  ✓ Без изменений")
        
        print(f"\n✅ ОБНОВЛЕНИЕ ЗАВЕРШЕНО: {updated_count} строк изменено")
        
        if updated_count > 0:
            messagebox.showinfo("Массовое обновление завершено", 
                               f"Обновлено {updated_count} строк.\n\n" +
                               "💡 В дальнейшем разделители будут обновляться автоматически при изменении!")
        else:
            messagebox.showinfo("Информация", 
                               "Все разделители уже корректны.\n\n" +
                               "💡 Теперь разделители обновляются автоматически при изменении!")

    # Остальные методы - заглушки для совместимости  
    def on_double_click(self, event):
        region = self.tree.identify("region", event.x, event.y)
        if region != "cell":
            return

        column = self.tree.identify_column(event.x)
        item = self.tree.identify_row(event.y)

        if not item or not column:
            return

        col_index = int(column.replace('#', '')) - 1
        values = list(self.tree.item(item, "values"))
        orig_param = values[1]
        current_value = values[col_index]

        if col_index not in [3, 4, 5]:
            return

        if self.edit_entry:
            self.edit_entry.destroy()
            self.edit_entry = None
            self.edit_undo_stack.clear()

        entry = Entry(self.tree)
        entry.insert(0, current_value)
        entry.select_range(0, END)
        entry.focus_set()

        x, y, width, height = self.tree.bbox(item, column)
        entry.place(x=x, y=y, width=width, height=height)

        self.edit_undo_stack = [current_value]
        self.edit_entry = entry

        def save_and_destroy(event=None):
            new_value = entry.get().strip()
            values[col_index] = new_value

            if col_index == 4:  # Разделитель
                self.manual_split_values[orig_param] = new_value
                self.settings["manual_split_values"] = self.manual_split_values.copy()
                self.save_settings()
                
                # 🆕 ПРОСТОЕ И НАДЕЖНОЕ ОБНОВЛЕНИЕ при изменении разделителя v4.8.1
                print(f"\n🔄 ОБНОВЛЕНИЕ разделителя для '{orig_param}'")
                print(f"  📋 Новый разделитель: '{new_value}'")
                
                # Получаем исходные значения для пересчета
                orig_values = values[2]  # Столбец "Исходные значения"
                print(f"  📋 Исходные значения: '{orig_values}'")
                
                # Простое разделение значений
                if not orig_values:
                    new_result = ""
                else:
                    # Разделяем по новому разделителю или стандартному
                    delimiter = new_value if new_value else ";"
                    split_parts = [part.strip() for part in orig_values.split(delimiter) if part.strip()]
                    print(f"  📋 Разделенные части: {split_parts}")
                    
                    # Применяем только базовую обработку
                    processed_parts = []
                    for part in split_parts:
                        # Очищаем пустые и неправильные значения
                        clean_part = part.strip()
                        if clean_part and clean_part not in ["/", ";", ",", "-"]:
                            processed_parts.append(clean_part)
                    
                    print(f"  📋 Очищенные части: {processed_parts}")
                    
                    # Удаляем дубликаты, сохраняя порядок
                    unique_parts = []
                    seen = set()
                    for part in processed_parts:
                        if part.lower() not in seen:
                            seen.add(part.lower())
                            unique_parts.append(part)
                    
                    print(f"  📋 Уникальные части: {unique_parts}")
                    
                    # Применяем переводы ТОЛЬКО если они есть
                    if hasattr(self, 'value_translation_rules') and self.value_translation_rules:
                        try:
                            translated_parts = self.apply_value_translation_rules(unique_parts)
                            unique_parts = translated_parts
                            print(f"  📋 После переводов: {unique_parts}")
                        except Exception as e:
                            print(f"  ⚠️ Ошибка применения переводов: {e}")
                    
                    # Формируем итоговый результат
                    new_result = "; ".join(unique_parts)
                
                print(f"  ✅ ИТОГОВЫЙ результат: '{new_result}'")
                
                # Обновляем столбец "Результат разделения" (индекс 5)
                values[5] = new_result
            elif col_index == 3:  # Новый параметр
                old_map_val = self.mappings.get(orig_param, orig_param)
                if old_map_val != new_value and new_value.strip():
                    self.mappings[orig_param] = new_value.strip()
                    self.save_mappings()
                    self.refresh_mapping_table()
                    self.update_main_tree_with_mappings()

            self.tree.item(item, values=values)

            entry.destroy()
            self.edit_entry = None
            self.edit_undo_stack.clear()

        entry.bind("<Return>", save_and_destroy)
        entry.bind("<FocusOut>", save_and_destroy)

    def show_context_menu(self, event):
        """Показать контекстное меню"""
        try:
            self.context_menu.post(event.x_root, event.y_root)
        except:
            pass

    def sort_column(self, event):
        """Сортировка колонки"""
        # Простая реализация - можно расширить при необходимости
        pass

    def copy_selected_value(self, event=None):
        """Копирование выбранного значения"""
        try:
            selection = self.tree.selection()
            if selection:
                item = selection[0]
                values = self.tree.item(item, "values")
                if values:
                    pyperclip.copy(str(values[1]))  # Копируем исходный параметр
        except:
            pass

    def select_all_rows(self, event):
        """Выделение всех строк"""
        try:
            for item in self.tree.get_children():
                self.tree.selection_add(item)
        except:
            pass

    def on_mapping_double_click(self, event):
        """Обработка двойного клика на маппинге - редактирование столбца 'Замена'"""
        print(f"🔍 DEBUG: Двойной клик на mapping_tree! x={event.x}, y={event.y}")
        
        try:
            # Получаем выделенный элемент
            selection = self.mapping_tree.selection()
            print(f"🔍 DEBUG: selection = {selection}")
            
            if not selection:
                print("🔍 DEBUG: Нет выделенного элемента")
                return
                
            item = selection[0]
            values = self.mapping_tree.item(item, "values")
            print(f"🔍 DEBUG: values = {values}")
            
            if len(values) < 2:
                print(f"🔍 DEBUG: Недостаточно значений: {len(values)}")
                return
                
            original_value = values[0]  # Исходное значение
            current_replacement = values[1]  # Текущая замена
            
            print(f"🔍 DEBUG: Открываем диалог редактирования для '{original_value}' → '{current_replacement}'")
            
            # 🔧 УПРОЩЕНО: Открываем диалог редактирования при любом двойном клике
            new_replacement = simpledialog.askstring(
                "Редактировать замену", 
                f"Исходное значение: {original_value}\n\nВведите новую замену:",
                initialvalue=current_replacement
            )
            
            if new_replacement is not None and new_replacement != current_replacement:
                # Обновляем маппинг
                self.mappings[original_value] = new_replacement
                self.save_mappings()
                self.refresh_mapping_table()
                
                # 🔧 ИСПРАВЛЕНО: Динамически обновляем основную таблицу
                self.update_main_tree_with_mappings()
                
                print(f"📝 Изменено сопоставление: '{original_value}' → '{new_replacement}'")
                messagebox.showinfo("Успешно", f"Сопоставление обновлено:\n'{original_value}' → '{new_replacement}'")
            elif new_replacement is None:
                print("🔍 DEBUG: Пользователь отменил редактирование")
            else:
                print("🔍 DEBUG: Значение не изменилось")
                
        except Exception as e:
            print(f"🔍 DEBUG: Ошибка при редактировании маппинга: {e}")
            import traceback
            traceback.print_exc()
            messagebox.showerror("Ошибка", f"Не удалось отредактировать сопоставление:\n{e}")

    def select_all_mappings(self, event=None):
        """Выделение всех маппингов"""
        try:
            for item in self.mapping_tree.get_children():
                self.mapping_tree.selection_add(item)
        except:
            pass

    def delete_selected_mappings(self, event):
        """Удаление выбранных маппингов"""
        try:
            selection = self.mapping_tree.selection()
            for item in selection:
                values = self.mapping_tree.item(item, "values")
                if len(values) >= 2:
                    orig_param = values[0]
                    if orig_param in self.mappings:
                        del self.mappings[orig_param]
                self.mapping_tree.delete(item)
        except:
            pass

    def copy_mapping_selected_value(self, event):
        """Копирование выбранного маппинга"""
        try:
            selection = self.mapping_tree.selection()
            if selection:
                item = selection[0]
                values = self.mapping_tree.item(item, "values")
                if values:
                    pyperclip.copy(str(values[0]))
        except:
            pass

    def on_exclusion_double_click(self, event):
        """Обработка двойного клика на исключении"""
        # Можно добавить редактирование исключения
        pass

    def copy_exclusion_selected_value(self, event):
        """Копирование выбранного исключения"""
        try:
            selection = self.exclusions_tree.selection()
            if selection:
                item = selection[0]
                values = self.exclusions_tree.item(item, "values")
                if values:
                    pyperclip.copy(str(values[0]))
        except:
            pass

    def global_ctrl_c(self, event):
        """Глобальное копирование (Ctrl+C)"""
        try:
            widget = self.root.focus_get()
            if hasattr(widget, 'selection_get'):
                pyperclip.copy(widget.selection_get())
        except:
            pass

    def global_ctrl_v(self, event):
        """Глобальная вставка (Ctrl+V)"""
        try:
            widget = self.root.focus_get()
            if hasattr(widget, 'insert'):
                widget.insert('insert', pyperclip.paste())
        except:
            pass

    def global_ctrl_z(self, event):
        """Глобальная отмена (Ctrl+Z)"""
        # Можно добавить функциональность отмены
        pass

    def global_ctrl_a(self, event):
        """Глобальное выделение всего (Ctrl+A)"""
        try:
            widget = self.root.focus_get()
            if hasattr(widget, 'select_range'):
                widget.select_range(0, 'end')
            elif widget == self.tree:
                self.select_all_rows(event)
        except:
            pass

    def global_delete(self, event):
        """Глобальное удаление (Delete)"""
        try:
            widget = self.root.focus_get()
            if widget == self.tree:
                # Можно добавить удаление выбранных строк
                pass
            elif widget == self.mapping_tree:
                self.delete_selected_mappings(event)
        except:
            pass

    def merge_selected_similar_params(self):
        """Объединение выбранных похожих параметров"""
        selection = self.tree.selection()
        if len(selection) < 2:
            messagebox.showwarning("Внимание", "Выберите как минимум 2 параметра для объединения")
            return
        
        # Получаем имена выбранных параметров
        selected_params = []
        for item in selection:
            values = self.tree.item(item, "values")
            param = values[1]  # Исходный параметр
            selected_params.append(param)
        
        # Показываем диалог выбора основного параметра
        dialog = ParameterMergeDialog(self.root, selected_params)
        if dialog.result:
            representative = dialog.result
            merge_groups = {representative: selected_params}
            self.apply_parameter_merges(merge_groups)
            
            # 🔧 ИСПРАВЛЕНО: Обновляем переводы после объединения
            print(f"🔄 Обновляем переводы после объединения под представителем: '{representative}'")
            
            # Добавляем сопоставление для представителя если его нет
            translation_copied = False
            if representative not in self.mappings:
                # Ищем перевод представителя среди объединяемых параметров
                for param in selected_params:
                    if param in self.mappings:
                        # Копируем перевод на представителя
                        self.mappings[representative] = self.mappings[param]
                        print(f"📋 Скопирован перевод: '{representative}' ← '{param}' → '{self.mappings[param]}'")
                        translation_copied = True
                        break
            
            # Сохраняем обновленные маппинги
            if translation_copied:
                self.save_mappings()
                self.refresh_mapping_table()
            
            # Обновляем переводы в основной таблице
            self.update_main_tree_with_mappings()
            
            messagebox.showinfo("Успешно", 
                f"Параметры объединены под именем '{representative}'\n"
                f"Переводы обновлены автоматически")
    
    def merge_selected_params_and_values(self):
        """ЭКСТРА-ОТЛАДОЧНАЯ версия объединения выбранных параметров и их значений"""
        print("\n" + "🔥"*100)
        print("🔥 ЭКСТРА-ОТЛАДКА: merge_selected_params_and_values НАЧАЛО")
        print("🔥"*100)
        
        selection = self.tree.selection()
        print(f"📋 Количество выбранных элементов: {len(selection)}")
        
        if len(selection) < 2:
            messagebox.showwarning("Внимание", "Выберите как минимум 2 параметра для объединения")
            return
        
        # Получаем информацию о выбранных параметрах
        selected_items_info = []
        selected_params = []
        
        print("\n🔥 АНАЛИЗ ВЫБРАННЫХ ПАРАМЕТРОВ:")
        for i, item in enumerate(selection):
            values = self.tree.item(item, "values")
            param = values[1]  # Исходный параметр
            selected_params.append(param)
            selected_items_info.append({
                'item': item,
                'param': param,
                'original_values': values[2],
                'result_values': values[5]
            })
            print(f"  📋 Элемент {i+1}: '{param}' → исходные: '{values[2]}' → результат: '{values[5]}'")
        
        print(f"\n🔥 СПИСОК ВЫБРАННЫХ ПАРАМЕТРОВ: {selected_params}")
        
        # Показываем диалог выбора основного параметра
        dialog = ParameterMergeDialog(self.root, selected_params)
        if dialog.result:
            representative = dialog.result
            print(f"\n🔥 ВЫБРАННЫЙ ОСНОВНОЙ ПАРАМЕТР: '{representative}'")
            
            # Создаем группу для объединения
            merge_groups = {representative: selected_params}
            print(f"🔥 СФОРМИРОВАННЫЕ ГРУППЫ: {merge_groups}")
            
            # Применяем объединение параметров
            self.apply_parameter_merges(merge_groups)
            
            # 🔧 ИСПРАВЛЕНО: После объединения НУЖНО обновить переводы!
            # Переводы должны применяться к представителю (representative)
            print(f"🔄 Обновляем переводы после объединения под представителем: '{representative}'")
            
            # Добавляем сопоставление для представителя если его нет
            translation_copied_extended = False
            if representative not in self.mappings:
                # Ищем перевод представителя среди объединяемых параметров
                for param in selected_params:
                    if param in self.mappings:
                        # Копируем перевод на представителя
                        self.mappings[representative] = self.mappings[param]
                        print(f"📋 Скопирован перевод: '{representative}' ← '{param}' → '{self.mappings[param]}'")
                        translation_copied_extended = True
                        break
            
            # Сохраняем обновленные маппинги
            if translation_copied_extended:
                self.save_mappings()
                self.refresh_mapping_table()
            
            # Обновляем переводы в основной таблице
            self.update_main_tree_with_mappings()
            
            messagebox.showinfo("Успешно", 
                f"Параметры и их значения объединены под именем '{representative}'\n"
                f"Объединено {len(selected_params)} параметров\n"
                f"Переводы обновлены автоматически")
            
            # Обновляем настройки объединения параметров для сохранения
            if representative not in self.param_merge_settings:
                self.param_merge_settings[representative] = {
                    'similar_params': selected_params,
                    'selected': True
                }
            
            self.save_merge_settings()
            
            print("\n🔥 ЭКСТРА-ОТЛАДКА: merge_selected_params_and_values ЗАВЕРШЕНО")
            print("🔥"*100)

    def apply_parameter_merges(self, merge_groups):
        """
        Исправленная функция объединения параметров:
        - Корректно собирает все значения из всех объединяемых параметров
        - В столбец 'Исходные значения' пишет все уникальные исходные значения (до перевода), нормализованные
        - В столбец 'Результат разделения' пишет все уникальные переведённые значения, нормализованные
        - Дубли удаляются после всех преобразований
        """
        print("\n" + "🔥"*80)
        print("🔥 ИСПРАВЛЕННАЯ ВЕРСИЯ ОБЪЕДИНЕНИЯ - ПОЛНОЕ РЕШЕНИЕ ПРОБЛЕМ")
        print("🔥"*80)
        
        if not merge_groups:
            print("❌ Нет групп для объединения")
            return
        
        print(f"📋 Группы объединения: {merge_groups}")
        
        for representative, param_list in merge_groups.items():
            print(f"\n🔥 ГРУППА: '{representative}' ← {param_list}")
            rows_to_merge = []
            target_row = None
            for item in self.tree.get_children():
                values = self.tree.item(item, "values")
                param_name = values[1]
                if param_name in param_list:
                    rows_to_merge.append({
                        'item': item,
                        'param': param_name,
                        'original': values[2],
                        'result': values[5],
                        'delimiter': values[4] or ";"
                    })
                    if param_name == representative:
                        target_row = item
            if len(rows_to_merge) < 2:
                print(f"  ⚠️ Недостаточно строк для объединения: {len(rows_to_merge)}")
                continue
            # Сбор всех исходных значений (до перевода)
            all_original_values = []
            for row in rows_to_merge:
                source_text = row['result'] if row['result'] else row['original']
                if source_text:
                    parts = self._smart_split_values(source_text, row['delimiter'])
                    for part in parts:
                        clean_part = normalize_value_format(part.strip())
                        if clean_part:
                            all_original_values.append(clean_part)
            # Удаляем дубли (без учёта регистра)
            seen = set()
            unique_original_values = []
            for v in all_original_values:
                v_lower = v.lower()
                if v_lower not in seen:
                    seen.add(v_lower)
                    unique_original_values.append(v)
            # Сбор переведённых значений
            translated_values = []
            for v in unique_original_values:
                # Применяем перевод к каждому значению
                translated = self.apply_value_translation_rules([v])[0] if hasattr(self, 'apply_value_translation_rules') else v
                translated = normalize_value_format(translated.strip())
                if translated:
                    translated_values.append(translated)
            # Удаляем дубли после перевода
            seen_tr = set()
            unique_translated_values = []
            for v in translated_values:
                v_lower = v.lower()
                if v_lower not in seen_tr:
                    seen_tr.add(v_lower)
                    unique_translated_values.append(v)
            # Формируем итоговые строки
            final_delimiter = rows_to_merge[0]['delimiter'] if rows_to_merge else ";"
            original_result = final_delimiter.join(unique_original_values)
            translated_result = final_delimiter.join(unique_translated_values)
            # Выбор целевой строки
            if not target_row:
                target_row = rows_to_merge[0]['item']
            # Обновляем целевую строку
            target_values = list(self.tree.item(target_row, "values"))
            target_values[1] = representative  # Исходный параметр
            target_values[2] = original_result  # Исходные значения (уникальные, нормализованные)
            target_values[3] = self.mappings.get(representative, representative)  # Новый параметр (переведённый)
            target_values[5] = translated_result  # Результат разделения (уникальные, переведённые)
            self.tree.item(target_row, values=target_values)
            # Удаляем остальные строки
            for row in rows_to_merge:
                if row['item'] != target_row:
                    self.tree.delete(row['item'])
        print("\n" + "🔥"*80)
        print("🔥 ОБЪЕДИНЕНИЕ ЗАВЕРШЕНО УСПЕШНО (исправлено)")
        print("🔥"*80)
        self.dynamic_update_main_table()
        self.root.after(1000, self.delayed_check_after_merge)

    def _smart_split_values(self, text, delimiter):
        """🆕 Умное разделение значений с сохранением переводов в скобках v4.8.5"""
        if not text:
            return []
        
        # 🆕 ЗАЩИТА ОТ ПУСТОГО РАЗДЕЛИТЕЛЯ v4.8.5
        if not delimiter:
            delimiter = ";"
            print(f"⚠️ УМНОЕ РАЗДЕЛЕНИЕ: пустой разделитель → ';'")
        
        # Сначала пробуем разделить по указанному разделителю
        if delimiter and delimiter in text:
            parts = [p.strip() for p in text.split(delimiter) if p.strip()]
        elif "; " in text:
            parts = [p.strip() for p in text.split("; ") if p.strip()]
        elif ";" in text:
            parts = [p.strip() for p in text.split(";") if p.strip()]
        elif " / " in text:
            parts = [p.strip() for p in text.split(" / ") if p.strip()]
        elif "/" in text:
            parts = [p.strip() for p in text.split("/") if p.strip()]
        else:
            # Если разделителей нет, возвращаем как одно значение
            parts = [text.strip()] if text.strip() else []
        
        # Очищаем от пустых и системных символов
        clean_parts = []
        for part in parts:
            if part and part not in ["/", ";", ",", "-"]:
                clean_parts.append(part)
        
        return clean_parts

    def delayed_check_after_merge(self):
        """Проверка состояния данных через 1 секунду после объединения"""
        print("\n" + "🕐"*100)
        print("🕐 ОТЛОЖЕННАЯ ПРОВЕРКА ЧЕРЕЗ 1 СЕКУНДУ:")
        print("🎯 ФИНАЛЬНАЯ ПРОВЕРКА ВСЕЙ ТАБЛИЦЫ:")
        for item in self.tree.get_children():
            values = self.tree.item(item, "values")
            if "Multi-Stream" in values[1] or "Multi-stream" in values[1]:
                print(f"  📋 {values[1]}: '{values[5]}'")  # Параметр: Результат разделения
        print("🕐"*100)
    
    def merge_similar_values_in_set(self, values_set):
        """Объединяет похожие значения в наборе, оставляя наиболее частые"""
        if not values_set:
            return values_set
            
        values_list = list(values_set)
        return self.merge_similar_values_in_list(values_list)
    
    def merge_similar_values_in_list(self, values_list):
        """Улучшенное объединение похожих значений в списке"""
        if not values_list:
            return []
        # 🆕 НОРМАЛИЗАЦИЯ ФОРМАТА ЗНАЧЕНИЙ ПЕРЕД ОБЪЕДИНЕНИЕМ
        clean_values = [normalize_value_format(v.strip()) for v in values_list if v and v.strip()]
        if not clean_values:
            return []
        
        merged_values = []
        processed = set()
        
        # Подсчитываем частоту каждого конкретного значения в исходных данных
        value_frequencies = {}
        for sku_data in self.original_values.values():
            for param_values in sku_data.values():
                if param_values:
                    if isinstance(param_values, str):
                        # Более аккуратное разбиение значений
                        vals = self.split_parameter_values(param_values)
                        for val in vals:
                            if val in clean_values:
                                value_frequencies[val] = value_frequencies.get(val, 0) + 1
        
        # Группируем похожие значения
        similar_groups = {}
        for value in clean_values:
            if value in processed:
                continue
                
            group = [value]
            for other_value in clean_values:
                if other_value != value and other_value not in processed:
                    if self.are_values_similar_conservative(value, other_value):
                        group.append(other_value)
            
            # Выбираем представителя группы
            if len(group) > 1:
                # Сортируем по частоте, потом по длине (предпочитаем более полные варианты)
                representative = max(group, key=lambda x: (
                    value_frequencies.get(x, 0),  # Частота использования
                    len(x),  # Длина (более полные варианты)
                    -clean_values.index(x)  # Порядок появления (обратный)
                ))
            else:
                representative = group[0]
            
            merged_values.append(representative)
            processed.update(group)
        
        # Сохраняем исходный порядок
        result = []
        for value in clean_values:
            if value not in result and value in merged_values:
                result.append(value)
            elif value not in result:
                # Находим представителя группы для этого значения
                for merged_val in merged_values:
                    if self.are_values_similar_conservative(value, merged_val):
                        if merged_val not in result:
                            result.append(merged_val)
                        break
        
        return result
    
    def are_values_similar_advanced(self, val1, val2):
        """Расширенная проверка схожести значений для объединения"""
        val1_norm = normalize_variant(val1).lower()
        val2_norm = normalize_variant(val2).lower()
        
        # Точное совпадение после нормализации
        if val1_norm == val2_norm:
            return True
        
        # Одно значение содержится в другом (например, "Support" в "Supported")
        if val1_norm in val2_norm or val2_norm in val1_norm:
            # Дополнительная проверка: разница в длине не должна быть слишком большой
            len_diff = abs(len(val1_norm) - len(val2_norm))
            max_len = max(len(val1_norm), len(val2_norm))
            if max_len > 0 and len_diff / max_len <= 0.3:  # Максимум 30% разницы
                return True
        
        # Проверка на опечатки для коротких значений
        if max(len(val1_norm), len(val2_norm)) <= 10:
            return self.check_typo_similarity(val1_norm, val2_norm)
        
        return False
    
    def are_values_similar_conservative(self, val1, val2):
        """Консервативная проверка схожести значений - более строгая"""
        if not val1 or not val2:
            return False
            
        val1_norm = normalize_variant(val1).lower().strip()
        val2_norm = normalize_variant(val2).lower().strip()
        
        # Точное совпадение после нормализации
        if val1_norm == val2_norm:
            return True
        
        # Проверяем только очевидные варианты
        # Один является подстрокой другого, но с ограничениями
        if len(val1_norm) >= 3 and len(val2_norm) >= 3:
            # Разрешаем только если одно значение почти полностью содержит другое
            if val1_norm in val2_norm:
                # Разница не более 20% от длины большего значения
                if (len(val2_norm) - len(val1_norm)) / len(val2_norm) <= 0.2:
                    return True
            elif val2_norm in val1_norm:
                if (len(val1_norm) - len(val2_norm)) / len(val1_norm) <= 0.2:
                    return True
        
        # Проверка только явных опечаток для коротких значений
        if 3 <= max(len(val1_norm), len(val2_norm)) <= 8:
            # Только одна замена символа
            if abs(len(val1_norm) - len(val2_norm)) <= 1:
                from difflib import SequenceMatcher
                similarity = SequenceMatcher(None, val1_norm, val2_norm).ratio()
                if similarity >= 0.85:  # Очень высокая схожесть
                    return True
        
        return False

    def split_parameter_values(self, param_values):
        """Более аккуратное разбиение значений параметров"""
        if not param_values:
            return []
        
        # Пробуем разные разделители
        for delimiter in [';', ',', '|']:
            if delimiter in param_values:
                values = [v.strip() for v in param_values.split(delimiter) if v.strip()]
                if len(values) > 1:
                    return values
        
        # Если разделителей нет, возвращаем как есть
        return [param_values.strip()] if param_values.strip() else []

    def are_values_similar(self, val1, val2):
        """Проверяет, являются ли два значения похожими"""
        # Удаляем пунктуацию и приводим к нижнему регистру
        import string
        val1_clean = val1.translate(str.maketrans('', '', string.punctuation)).replace(' ', '')
        val2_clean = val2.translate(str.maketrans('', '', string.punctuation)).replace(' ', '')
        
        # Проверяем, содержит ли одно значение другое
        if val1_clean in val2_clean or val2_clean in val1_clean:
            return True
            
        # Проверяем схожесть с помощью SequenceMatcher
        similarity = SequenceMatcher(None, val1_clean, val2_clean).ratio()
        return similarity > 0.8  # 80% схожести

    def apply_value_merges(self, merge_data):
        """Применение объединений значений"""
        if not merge_data:
            return
            
        # Проходим по всем элементам дерева и применяем объединения значений
        for item in self.tree.get_children():
            values = list(self.tree.item(item, "values"))
            param = values[1]  # Исходный параметр
            orig_values = values[2]  # Исходные значения
            result_values = values[5]  # Результат разделения
            
            # Проверяем, есть ли объединения для этого параметра
            if param in merge_data:
                param_merges = merge_data[param]
                
                # Применяем объединения к исходным значениям
                if orig_values:
                    merged_orig_values = self.apply_value_merges_to_string(orig_values, param_merges)
                    values[2] = merged_orig_values
                
                # Применяем объединения к результирующим значениям
                if result_values:
                    merged_result_values = self.apply_value_merges_to_string(result_values, param_merges)
                    values[5] = merged_result_values
                
                # Обновляем элемент дерева
                self.tree.item(item, values=values)
        
        # ВРЕМЕННО УБРАНО: Обновление DataFrame может вызывать проблемы  
        # self.df = pd.DataFrame([{
        #     "№": int(values[0]),
        #     "Исходный параметр": values[1],
        #     "Исходные значения": values[2],
        #     "Новый параметр": values[3],
        #     "Разделитель": values[4],
        #     "Результат разделения": values[5]
        # } for item in self.tree.get_children() for values in [self.tree.item(item, "values")]])
        
        # УБРАНО: self.refresh_treeview_with_colors() - ВТОРАЯ ФУНКЦИЯ тоже перезаписывала результаты!
        # Обновление цветов не критично для объединения значений
        
        # 🆕 ДИНАМИЧЕСКОЕ ОБНОВЛЕНИЕ после объединения значений v4.9
        print("🔄 Выполняем динамическое обновление после объединения значений...")
        self.dynamic_update_main_table()
    
    def apply_value_merges_to_string(self, values_string, param_merges):
        """Применяет объединения значений к строке значений"""
        if not values_string or not param_merges:
            return values_string
            
        # Разбиваем строку на отдельные значения
        values_list = [v.strip() for v in values_string.split(';') if v.strip()]
        merged_values = []
        processed_values = set()
        
        for value in values_list:
            if value in processed_values:
                continue
                
            # Проверяем, нужно ли объединить это значение
            merged = False
            for representative, merge_info in param_merges.items():
                if merge_info.get('selected', False):
                    similar_values = merge_info.get('similar_values', [])
                    if value in similar_values:
                        # Добавляем представителя группы, если его еще нет
                        if representative not in merged_values:
                            merged_values.append(representative)
                        processed_values.update(similar_values)
                        merged = True
                        break
            
            # Если значение не было объединено, добавляем его как есть
            if not merged:
                merged_values.append(value)
                processed_values.add(value)
        
        return "; ".join(merged_values)

    def clean_cell(self, value):
        """🔧 ИСПРАВЛЕНО: Улучшенная очистка ячейки"""
        if not isinstance(value, str):
            return value
        value = value.strip()
        
        # Используем улучшенную функцию очистки
        return clean_value_enhanced(value)

    def remove_duplicates_from_cell(self, cell_value):
        """🔧 ИСПРАВЛЕНО: Улучшенное удаление дубликатов из ячейки"""
        if not cell_value:
            return cell_value
        seps = [',', ';']
        sep = None
        for s in seps:
            if s in cell_value:
                sep = s
                break
        if sep is None:
            return cell_value
        
        items = [self.clean_cell(v) for v in cell_value.split(sep)]
        seen = set()
        unique = []
        
        for v in items:
            # Проверяем валидность значения
            if v and is_valid_value_enhanced(v):
                norm_v = normalize_variant(v).lower()
                if norm_v not in seen:
                    seen.add(norm_v)
                    unique.append(v)
        
        return sep.join(unique) if unique else ""

    def remove_duplicates_from_list(self, values):
        seen = set()
        result = []
        removed_duplicates = []
        
        for v in values:
            if v:
                norm_v = normalize_variant(v).lower()
                if norm_v not in seen:
                    seen.add(norm_v)
                    result.append(v)
                else:
                    removed_duplicates.append(v)
        
        if removed_duplicates:
            print(f"    🗑️ УДАЛЕНЫ ДУБЛИ: {removed_duplicates} (оставлены: {result})")
        
        return result

    def parse_html_table(self, html):
        """🆕 УЛУЧШЕННЫЙ ПАРСЕР HTML-ТАБЛИЦ для многоколоночных структур
        
        🔧 ВАЖНО: Этот метод сохраняет ИСХОДНЫЕ данные без перевода!
        Переводы применяются только в процессе отображения результатов.
        """
        soup = BeautifulSoup(html, 'html.parser')
        params = {}
        current_param = None
        
        for tr in soup.find_all('tr'):
            tds = tr.find_all('td')

            if len(tds) == 0:
                continue

            # 🆕 ОБРАБОТКА СТРОК С ОДНОЙ ЯЧЕЙКОЙ
            if len(tds) == 1:
                colspan = tds[0].get('colspan')
                try:
                    colspan_num = int(colspan) if colspan else 1
                except:
                    colspan_num = 1
                
                cell_text = self.clean_cell(tds[0].get_text())
                
                # Если это заголовок секции (colspan >= 2), пропускаем
                if colspan_num >= 2:
                    current_param = None
                    continue
                    
                # Если это дополнительное значение для текущего параметра
                if current_param and cell_text:
                    # 🔧 ИСПРАВЛЕНО: Сохраняем исходные данные без перевода
                    params[current_param] += f"; {cell_text}"
                continue

            # 🆕 ОБРАБОТКА МНОГОКОЛОНОЧНЫХ СТРОК (2+ столбцов)
            if len(tds) >= 2:
                # Первый столбец - название параметра
                param_cell = tds[0]
                param = self.clean_cell(param_cell.get_text())
                
                # Если первый столбец пустой или это заголовок с colspan, пропускаем
                if not param:
                    current_param = None
                    continue
                
                param_colspan = param_cell.get('colspan')
                try:
                    param_colspan_num = int(param_colspan) if param_colspan else 1
                except:
                    param_colspan_num = 1
                
                # Если параметр занимает несколько столбцов, это заголовок секции
                if param_colspan_num >= 2:
                    current_param = None
                    continue
                
                # 🆕 СОБИРАЕМ ЗНАЧЕНИЯ ИЗ ВСЕХ ОСТАЛЬНЫХ СТОЛБЦОВ
                values = []
                for i in range(1, len(tds)):
                    value_cell = tds[i]
                    value = self.clean_cell(value_cell.get_text())
                    
                    if value:
                        # Проверяем colspan значения
                        value_colspan = value_cell.get('colspan')
                        try:
                            value_colspan_num = int(value_colspan) if value_colspan else 1
                        except:
                            value_colspan_num = 1
                        
                        values.append(value)
                        
                        # Если значение занимает несколько столбцов, 
                        # пропускаем соответствующее количество следующих ячеек
                        if value_colspan_num > 1:
                            i += value_colspan_num - 1
                
                # 🆕 ОБЪЕДИНЯЕМ ВСЕ НАЙДЕННЫЕ ЗНАЧЕНИЯ
                if param and values:
                    # Удаляем дубликаты значений, сохраняя порядок
                    unique_values = []
                    seen = set()
                    for val in values:
                        val_normalized = val.lower().strip()
                        if val_normalized not in seen:
                            seen.add(val_normalized)
                            unique_values.append(val)
                    
                    combined_value = "; ".join(unique_values)
                    
                    # 🔧 ИСПРАВЛЕНО: Сохраняем исходные данные без перевода
                    params[param] = combined_value
                    current_param = param
                else:
                    current_param = None
                continue

            current_param = None
            
        return params

    def find_similar_param_groups(self, param_series):
        """
        🚨 ЗАБЛОКИРОВАННАЯ СТАРАЯ ФУНКЦИЯ - НЕ ИСПОЛЬЗОВАТЬ!
        
        Эта функция была источником ошибочной группировки Weight + Night.
        Заменена на find_similar_parameters_advanced().
        """
        print("🚨 ВНИМАНИЕ: Вызвана заблокированная старая функция find_similar_param_groups!")
        print("🚨 Используйте find_similar_parameters_advanced() вместо неё!")
        
        # Возвращаем пустой результат, чтобы не было группировки по старым критериям
        return {param: 0 for param in param_series}

    def update_mappings_from_params(self, params):
        """Обновление маппингов из параметров"""
        for param in params:
            if param not in self.mappings:
                self.mappings[param] = param

    # ❌ УДАЛЕН метод replace_mapped_values_in_str - ИСТОЧНИК ЧАСТИЧНОГО ПЕРЕВОДА
    # Этот метод применял сопоставления к значениям, что вызывало частичный перевод
    # Теперь сопоставления применяются ТОЛЬКО к параметрам

    def apply_case_normalization(self, value):
        """Применение нормализации регистра"""
        if not value or not isinstance(value, str):
            return value
        
        # Проверяем точное совпадение
        if value in self.case_normalization:
            return self.case_normalization[value]
        
        # Проверяем нечувствительное к регистру совпадение
        for norm_value, replacement in self.case_normalization.items():
            if value.lower() == norm_value.lower():
                return replacement
        
        return value

    def apply_case_normalization_in_str(self, value, delimiter=';'):
        """Применение нормализации регистра в строке"""
        if not value:
            return value
        
        parts = [part.strip() for part in value.split(delimiter)]
        normalized_parts = [self.apply_case_normalization(part) for part in parts]
        
        return delimiter.join(normalized_parts)

    def split_values(self, value, delimiter):
        """
        🆕 УЛУЧШЕННОЕ разделение значений с поддержкой защищенных значений
        
        НОВЫЕ ВОЗМОЖНОСТИ:
        - Поддержка множественных разделителей (например: ",/" означает разделение по "," И по "/")
        - 🛡️ ЗАЩИЩЕННЫЕ ЗНАЧЕНИЯ: не разделяются, даже если содержат символ разделителя
        - Пример: "Auto (ICR) / Color / B/W" с защищенным "B/W" → ["Auto (ICR)", "Color", "B/W"]
        """
        if not value:
            return []
        
        # 🆕 ПРОВЕРКА РАЗДЕЛИТЕЛЯ v4.8.5
        if not delimiter:
            print("⚠️ ПУСТОЙ РАЗДЕЛИТЕЛЬ - используем стандартный ';'")
            delimiter = ";"
        
        # 🛡️ ЗАЩИТА ЗНАЧЕНИЙ ОТ РАЗДЕЛЕНИЯ
        protected_replacements = {}
        modified_value = value
        
        if self.protected_values:
            print(f"🛡️ ПРОВЕРКА ЗАЩИЩЕННЫХ ЗНАЧЕНИЙ: {self.protected_values}")
            
            for i, protected in enumerate(self.protected_values):
                if protected in modified_value:
                    placeholder = f"__PROTECTED_{i}__"
                    protected_replacements[placeholder] = protected
                    modified_value = modified_value.replace(protected, placeholder)
                    print(f"  🔒 ЗАЩИЩЕНО: '{protected}' → '{placeholder}'")
            
            print(f"  📋 После защиты: '{modified_value}'")
        
        # 🔍 ОБРАБОТКА МНОЖЕСТВЕННЫХ РАЗДЕЛИТЕЛЕЙ
        if len(delimiter) > 1 and delimiter != "; ":
            # Множественные разделители (например: ",/" означает разделение по "," И "/")
            import re
            
            # Создаем регулярное выражение для разделения по любому из символов
            # Экранируем специальные символы для regex
            escaped_chars = [re.escape(char) for char in delimiter]
            pattern = '[' + ''.join(escaped_chars) + ']'
            
            print(f"🔍 МНОЖЕСТВЕННЫЕ РАЗДЕЛИТЕЛИ: '{delimiter}' → pattern: '{pattern}'")
            result_values = [v.strip() for v in re.split(pattern, modified_value) if v.strip()]
        else:
            # Обычное разделение по одному разделителю
            # 🆕 ДОПОЛНИТЕЛЬНАЯ ПРОВЕРКА РАЗДЕЛИТЕЛЯ v4.8.5
            if not delimiter:
                delimiter = ";"
                print("⚠️ АВАРИЙНАЯ ЗАМЕНА: пустой разделитель → ';'")
            
            result_values = [v.strip() for v in modified_value.split(delimiter) if v.strip()]
        
        # 🔓 ВОССТАНОВЛЕНИЕ ЗАЩИЩЕННЫХ ЗНАЧЕНИЙ
        if protected_replacements:
            restored_values = []
            for val in result_values:
                if val in protected_replacements:
                    restored_val = protected_replacements[val]
                    print(f"  🔓 ВОССТАНОВЛЕНО: '{val}' → '{restored_val}'")
                    restored_values.append(restored_val)
                else:
                    restored_values.append(val)
            result_values = restored_values
        
        # 🔧 ИСПРАВЛЕНО: Очистка невалидных значений
        cleaned_values = []
        for val in result_values:
            cleaned = clean_value_enhanced(val)
            if is_valid_value_enhanced(cleaned):
                cleaned_values.append(cleaned)
            else:
                print(f"  🗑️ Удалено невалидное значение: '{val}'")
        
        # 🆕 УДАЛЕНИЕ ДУБЛИКАТОВ ПОСЛЕ РАЗДЕЛЕНИЯ
        # Используем метод remove_duplicates_from_list, который учитывает варианты написания
        result_values = self.remove_duplicates_from_list(cleaned_values)
        
        print(f"  ✅ ФИНАЛЬНЫЙ РЕЗУЛЬТАТ (очищенный, без дубликатов): {result_values}")
        return result_values

    def refresh_treeview_with_colors(self):
        """Обновление раскраски дерева"""
        # Настраиваем теги для раскраски
        colors = ['#f0f0f0', '#e0e0e0', '#d0d0d0', '#c0c0c0', '#b0b0b0']
        
        # Определяем группы параметров по их схожести
        group_colors = {}
        color_index = 0
        
        for item in self.tree.get_children():
            values = self.tree.item(item, "values")
            tags = self.tree.item(item, "tags")
            
            if tags:
                group_tag = tags[0]
                if group_tag not in group_colors:
                    group_colors[group_tag] = colors[color_index % len(colors)]
                    color_index += 1
                
                # Настраиваем тег с цветом
                self.tree.tag_configure(group_tag, background=group_colors[group_tag])
            else:
                # Для элементов без группы используем белый фон
                self.tree.tag_configure("no_group", background="white")
                self.tree.item(item, tags=("no_group",))

    def auto_clean_duplicate_values(self):
        """Автоматическая очистка дубликатов"""
        # Простая реализация
        return 0

    def auto_suggest_parameter_merge(self):
        """🚨 ОТКЛЮЧЕНА: Автоматическое предложение объединения параметров
        
        ЭТА ФУНКЦИЯ СОЗДАВАЛА ДУБЛИРОВАНИЕ В param_merge_settings!
        Теперь используется только find_similar_parameters_for_tab()
        """
        print("🚨 ФУНКЦИЯ auto_suggest_parameter_merge ОТКЛЮЧЕНА - источник дублирования!")
        return
        
        # СТАРЫЙ КОД ЗАКОММЕНТИРОВАН
        # if not hasattr(self, 'similar_groups') or not self.similar_groups:
        #     return
        # 
        # # Собираем группы похожих параметров
        # param_groups = {}
        # for param, group_id in self.similar_groups.items():
        #     if group_id not in param_groups:
        #         param_groups[group_id] = []
        #     param_groups[group_id].append(param)
        # 
        # # Создаем предложения для объединения групп с более чем одним параметром
        # suggestions = []
        # for group_id, params in param_groups.items():
        #     if len(params) > 1:
        #         # Выбираем наиболее частый параметр как представителя
        #         param_frequencies = {}
        #         for param in params:
        #             count = 0
        #             for sku_data in self.original_values.values():
        #                 if param in sku_data:
        #                     count += 1
        #             param_frequencies[param] = count
        #         
        #         if param_frequencies:
        #             representative = max(param_frequencies.items(), key=lambda x: x[1])[0]
        #             suggestions.append((representative, params))
        # 
        # # Сохраняем предложения в настройки объединения параметров
        # for representative, similar_params in suggestions:
        #     self.param_merge_settings[representative] = {
        #         'similar_params': similar_params,
        #         'selected': False
        #     }
        # 
        # # Обновляем дерево параметров на вкладке объединения
        # if hasattr(self, 'params_tree'):
        #     self.update_params_tree()

    def update_param_frequency(self, params):
        """ИСПРАВЛЕННОЕ обновление частоты использования параметров"""
        print("📊 ПОДСЧЕТ ЧАСТОТЫ ИСПОЛЬЗОВАНИЯ ПАРАМЕТРОВ")
        self.param_frequency = {}
        
        for param in params:
            count = 0
            for sku_params in self.original_values.values():
                if param in sku_params:
                    count += 1
            self.param_frequency[param] = count
        
        # Показываем статистику для отладки
        total_params = len(params)
        print(f"📋 Обработано параметров: {total_params}")
        
        if total_params > 0:
            avg_frequency = sum(self.param_frequency.values()) / total_params
            max_frequency = max(self.param_frequency.values()) if self.param_frequency else 0
            print(f"📊 Средняя частота: {avg_frequency:.1f}")
            print(f"📊 Максимальная частота: {max_frequency}")
            
            # Показываем примеры Multi параметров для демонстрации
            multi_params = [p for p in params if 'multi' in p.lower()]
            if multi_params:
                print("🔍 Multi параметры и их частота:")
                for param in sorted(multi_params, key=lambda x: self.param_frequency.get(x, 0), reverse=True):
                    freq = self.param_frequency.get(param, 0)
                    print(f"  • {param}: {freq} раз")

    def find_similar_parameters_advanced(self, params):
        """
        ИСПРАВЛЕННЫЙ поиск похожих параметров с правильной логикой группировки
        
        ИСПРАВЛЕНИЯ:
        - Повышены пороги схожести для уменьшения ложных срабатываний
        - Добавлена проверка на содержательную похожесть (общие слова)
        - Исправлена логика выбора основного параметра по частоте
        - Убрано дублирование основных параметров
        """
        print("🔍 ПОИСК ПОХОЖИХ ПАРАМЕТРОВ - ИСПРАВЛЕННАЯ ВЕРСИЯ")
        print(f"📊 Получено параметров для обработки: {len(params)}")
        
        similar_groups = {}
        processed = set()
        
        for i, param1 in enumerate(params):
            if param1 in processed:
                continue
                
            group = [param1]
            
            for j, param2 in enumerate(params[i+1:], i+1):
                if param2 in processed:
                    continue
                
                # БОЛЕЕ СТРОГИЕ КРИТЕРИИ СХОЖЕСТИ
                is_similar = self.are_parameters_truly_similar(param1, param2)
                
                if is_similar:
                    group.append(param2)
                    print(f"  ✅ Найдена похожая пара: '{param1}' ↔ '{param2}'")
            
            if len(group) > 1:
                # 🆕 ИСПРАВЛЕНИЕ v4.9.2: Выбираем параметр с НАИБОЛЬШИМ КОЛИЧЕСТВОМ ЗНАЧЕНИЙ как представителя
                def get_value_count(param_name):
                    """Подсчитывает количество значений в параметре"""
                    total_values = 0
                    for sku_params in self.original_values.values():
                        if param_name in sku_params:
                            values = sku_params[param_name]
                            if isinstance(values, list):
                                total_values += len([v for v in values if v.strip()])
                            elif isinstance(values, str) and values.strip():
                                # Считаем количество значений разделенных точкой с запятой
                                parts = [v.strip() for v in values.split(';') if v.strip()]
                                total_values += len(parts)
                    return total_values
                
                # Выбираем параметр с максимальным количеством значений
                value_counts = {}
                for param in group:
                    value_counts[param] = get_value_count(param)
                
                representative = max(group, key=lambda x: value_counts.get(x, 0))
                
                # 🔥 УБИРАЕМ ИЗ ГРУППЫ ПРЕДСТАВИТЕЛЯ - оставляем только похожие
                other_params = [p for p in group if p != representative]
                
                print(f"🎯 ГРУППА НАЙДЕНА:")
                print(f"  👑 ОСНОВНОЙ: {representative}")
                print(f"      📊 Количество значений: {value_counts.get(representative, 0)}")
                print(f"      📈 Частота: {self.param_frequency.get(representative, 0)}")
                
                for param in other_params:
                    values_count = value_counts.get(param, 0)
                    freq = self.param_frequency.get(param, 0)
                    print(f"     ↳ {param}")
                    print(f"       📊 Количество значений: {values_count}")
                    print(f"       📈 Частота: {freq}")
                
                # 🔥 КЛЮЧЕВОЕ ИСПРАВЛЕНИЕ: Сохраняем представителя → список остальных
                # 🔥 ДОПОЛНИТЕЛЬНАЯ ЗАЩИТА: Убираем представителя если случайно попал
                clean_other_params = [p for p in other_params if p != representative]
                similar_groups[representative] = clean_other_params
                
                print(f"  🔍 ПРОВЕРКА ДУБЛИРОВАНИЯ:")
                print(f"     Представитель: {representative}")
                print(f"     Список похожих: {clean_other_params}")
                if representative in clean_other_params:
                    print(f"  🚨 ОШИБКА: Представитель найден в списке похожих!")
                else:
                    print(f"  ✅ ОК: Дублирования нет")
                
                # 🔥 ОТМЕЧАЕМ ВСЕ ПАРАМЕТРЫ ГРУППЫ КАК ОБРАБОТАННЫЕ
                processed.update(group)
            else:
                # Если группа из одного элемента - просто отмечаем как обработанный
                processed.add(param1)
        
        print(f"📊 ИТОГО НАЙДЕНО ГРУПП: {len(similar_groups)}")
        return similar_groups
    
    def are_parameters_truly_similar(self, param1, param2):
        """
        🚨 ЭКСТРЕМАЛЬНАЯ ОТЛАДКА: Проверяет, действительно ли параметры похожи
        """
        print(f"\n🔍 ПРОВЕРКА СХОЖЕСТИ: '{param1}' ↔ '{param2}'")
        
        param1_lower = param1.lower().strip()
        param2_lower = param2.lower().strip()
        
        # 1. ТОЧНОЕ СОВПАДЕНИЕ (разный регистр)
        if param1_lower == param2_lower:
            print(f"  ✅ ТОЧНОЕ СОВПАДЕНИЕ")
            return True
        
        # 2. ПРОВЕРКА НА ОБЩИЕ КЛЮЧЕВЫЕ СЛОВА
        words1 = set(re.findall(r'\b\w+\b', param1_lower))
        words2 = set(re.findall(r'\b\w+\b', param2_lower))
        
        print(f"  📋 Слова1: {words1}")
        print(f"  📋 Слова2: {words2}")
        
        # Исключаем очень общие слова
        common_words = {'and', 'or', 'the', 'a', 'an', 'is', 'are', 'in', 'on', 'at', 'to', 'for', 'of', 'with'}
        words1_filtered = words1 - common_words
        words2_filtered = words2 - common_words
        
        print(f"  📋 Слова1 (отфильтр): {words1_filtered}")
        print(f"  📋 Слова2 (отфильтр): {words2_filtered}")
        
        if not words1_filtered or not words2_filtered:
            print(f"  ❌ ОТКЛОНЕНО: Пустые слова после фильтрации")
            return False
            
        # 🔥 УСИЛЕННЫЕ КРИТЕРИИ: Должно быть значимое пересечение слов
        common_significant_words = words1_filtered & words2_filtered
        print(f"  📋 Общие слова: {common_significant_words}")
        
        if not common_significant_words:
            print(f"  ❌ ОТКЛОНЕНО: Нет общих значимых слов")
            return False
        
        # 🔥 НОВЫЙ КРИТЕРИЙ: Отклоняем если общие слова слишком общие
        # Слова типа "stream", "dual", "output", "input" могут встречаться в разных контекстах
        very_common_tech_words = {'stream', 'output', 'input', 'mode', 'type', 'format', 'level', 'rate', 'dual', 'multi'}
        only_common_tech_words = common_significant_words.issubset(very_common_tech_words)
        
        if only_common_tech_words and len(common_significant_words) == 1:
            common_word = list(common_significant_words)[0]
            print(f"  ❌ ОТКЛОНЕНО: Только одно техническое слово '{common_word}' недостаточно")
            return False
        
        # 🔥 ДОПОЛНИТЕЛЬНАЯ ПРОВЕРКА: Если есть общие слова, но контекст разный
        if len(common_significant_words) == 1:
            # Проверяем, не слишком ли разные остальные слова
            unique_words1 = words1_filtered - common_significant_words
            unique_words2 = words2_filtered - common_significant_words
            
            if unique_words1 and unique_words2:
                # Есть уникальные слова в обеих группах - может быть разный контекст
                print(f"  📋 Уникальные слова1: {unique_words1}")
                print(f"  📋 Уникальные слова2: {unique_words2}")
                
                # Проверяем семантическую совместимость
                incompatible_pairs = [
                    ({'multi'}, {'adc', 'dac', 'analog', 'digital'}),
                    ({'dual'}, {'ir', 'infrared', 'night'}),
                    ({'audio'}, {'video', 'image', 'picture'}),
                    ({'ip'}, {'stream', 'streaming', 'video', 'audio'}),  # 🆕 IP ≠ Stream!
                    ({'stream'}, {'ip', 'protocol', 'network', 'address'}),  # 🆕 Stream ≠ IP!
                ]
                
                for set1, set2 in incompatible_pairs:
                    if (unique_words1 & set1 and unique_words2 & set2) or (unique_words1 & set2 and unique_words2 & set1):
                        print(f"  ❌ ОТКЛОНЕНО: Семантически несовместимые контексты")
                        return False
        
        # 3. ВЫСОКАЯ СХОЖЕСТЬ ПО НЕСКОЛЬКИМ МЕТРИКАМ ОДНОВРЕМЕННО
        ratio = fuzz.ratio(param1_lower, param2_lower)
        token_sort_ratio = fuzz.token_sort_ratio(param1_lower, param2_lower)
        token_set_ratio = fuzz.token_set_ratio(param1_lower, param2_lower)
        
        print(f"  📊 Метрики схожести:")
        print(f"    • ratio: {ratio}")
        print(f"    • token_sort_ratio: {token_sort_ratio}")
        print(f"    • token_set_ratio: {token_set_ratio}")
        
        # БОЛЕЕ СТРОГИЕ ПОРОГИ
        high_threshold = 85
        
        # Требуем высокой схожести по НЕСКОЛЬКИМ метрикам
        high_similarity_count = 0
        if ratio >= high_threshold:
            high_similarity_count += 1
            print(f"    ✅ ratio >= {high_threshold}")
        if token_sort_ratio >= high_threshold:
            high_similarity_count += 1
            print(f"    ✅ token_sort_ratio >= {high_threshold}")
        if token_set_ratio >= high_threshold:
            high_similarity_count += 1
            print(f"    ✅ token_set_ratio >= {high_threshold}")
        
        print(f"  📊 Высоких метрик: {high_similarity_count}/3")
        
        # Требуем минимум 2 из 3 метрик с высокой схожестью
        if high_similarity_count >= 2:
            print(f"  ✅ ПРИНЯТО: Высокая схожесть по {high_similarity_count} метрикам")
            return True
        
        # 4. ПРОВЕРКА НА ПРОСТЫЕ ВАРИАЦИИ (дефисы, пробелы, регистр)
        normalized1 = re.sub(r'[-_\s]+', '', param1_lower)
        normalized2 = re.sub(r'[-_\s]+', '', param2_lower)
        
        print(f"  📋 Нормализованные: '{normalized1}' ↔ '{normalized2}'")
        
        if normalized1 == normalized2:
            print(f"  ✅ ПРИНЯТО: Одинаковые после нормализации")
            return True
        
        # 5. ПРОВЕРКА НА ОЧЕНЬ ПОХОЖИЕ ОКОНЧАНИЯ/НАЧАЛА
        if len(common_significant_words) > 0:
            print(f"  🔍 Проверка похожих окончаний...")
            for word1 in words1_filtered:
                for word2 in words2_filtered:
                    if (len(word1) >= 4 and len(word2) >= 4 and
                        (word1.startswith(word2[:4]) or word2.startswith(word1[:4])) and
                        abs(len(word1) - len(word2)) <= 3):
                        edit_ratio = fuzz.ratio(word1, word2)
                        print(f"    📋 Сравнение '{word1}' ↔ '{word2}': {edit_ratio}")
                        if edit_ratio >= 80:
                            print(f"    ✅ ПРИНЯТО: Похожие окончания ({edit_ratio}%)")
                            return True
        
        print(f"  ❌ ОТКЛОНЕНО: Не прошли все проверки")
        return False
    
    def calculate_edit_distance(self, s1, s2):
        """Вычисляет расстояние Левенштейна между двумя строками"""
        if len(s1) < len(s2):
            return self.calculate_edit_distance(s2, s1)
        
        if len(s2) == 0:
            return len(s1)
        
        previous_row = list(range(len(s2) + 1))
        for i, c1 in enumerate(s1):
            current_row = [i + 1]
            for j, c2 in enumerate(s2):
                insertions = previous_row[j + 1] + 1
                deletions = current_row[j] + 1
                substitutions = previous_row[j] + (c1 != c2)
                current_row.append(min(insertions, deletions, substitutions))
            previous_row = current_row
        
        return previous_row[-1]
    
    def check_typo_similarity(self, s1, s2):
        """Проверяет, являются ли строки опечатками друг друга"""
        len_diff = abs(len(s1) - len(s2))
        max_len = max(len(s1), len(s2))
        min_len = min(len(s1), len(s2))
        
        # Слишком большая разница в длине - не опечатка
        if len_diff > 2:
            return False
        
        # Если одно слово значительно длиннее другого (более чем на 30%), это скорее не опечатка
        if max_len > 0 and len_diff / max_len > 0.4:
            return False
            
        distance = self.calculate_edit_distance(s1, s2)
        
        # Специальная обработка для очень коротких слов (2-3 символа)
        if max_len <= 3:
            return distance == 1  # Только одна ошибка
        
        # Для коротких слов (4-6 символов)
        if max_len <= 6:
            # Если разница в длине больше 1 символа, то должно быть расстояние 1
            if len_diff > 1:
                return distance == 1
            else:
                return distance <= 2
        
        # Для средних слов (7-10 символов)
        if max_len <= 10:
            return distance <= 2
        
        # Для длинных слов
        return distance <= 3

    def find_similar_values_in_all_parameters(self):
        """Поиск похожих значений во всех параметрах"""
        similar_values_data = {}
        
        # Собираем все значения для каждого параметра
        param_values = defaultdict(set)
        for sku_params in self.original_values.values():
            for param, values_list in sku_params.items():
                if isinstance(values_list, list):
                    param_values[param].update(values_list)
                else:
                    param_values[param].add(values_list)
        
        # Ищем похожие значения в каждом параметре
        for param, values in param_values.items():
            values_list = list(values)
            if len(values_list) < 2:
                continue
                
            similar_groups = {}
            processed = set()
            
            for i, val1 in enumerate(values_list):
                if val1 in processed:
                    continue
                    
                group = [val1]
                for j, val2 in enumerate(values_list[i+1:], i+1):
                    if val2 in processed:
                        continue
                        
                    # Различные метрики схожести
                    ratio = fuzz.ratio(val1.lower(), val2.lower())
                    token_sort_ratio = fuzz.token_sort_ratio(val1.lower(), val2.lower())
                    
                    if ratio >= 80 or token_sort_ratio >= 85:
                        group.append(val2)
                        processed.add(val2)
                
                if len(group) > 1:
                    representative = min(group, key=len)  # Выбираем самое короткое как представителя
                    similar_groups[representative] = group
                    processed.update(group)
            
            if similar_groups:
                similar_values_data[param] = similar_groups
        
        return similar_values_data

    # 🆕 ФУНКЦИИ ДЛЯ РЕДАКТИРОВАНИЯ ГРУПП ПАРАМЕТРОВ

    def show_params_context_menu(self, event):
        """Показать контекстное меню для таблицы параметров"""
        try:
            # Определяем, на какой строке был клик
            item = self.params_tree.identify_row(event.y)
            if item:
                self.params_tree.selection_set(item)
                self.params_context_menu.post(event.x_root, event.y_root)
        except:
            pass

    def edit_param_group(self):
        """Редактировать группу параметров"""
        try:
            selection = self.params_tree.selection()
            if not selection:
                messagebox.showwarning("Предупреждение", "Выберите группу для редактирования.")
                return
            
            item = selection[0]
            values = self.params_tree.item(item, "values")
            representative = values[1]  # Основной параметр
            current_similar = values[2]  # Похожие параметры
            
            if representative not in self.param_merge_settings:
                messagebox.showerror("Ошибка", "Группа не найдена в настройках.")
                return
            
            # Открываем диалог редактирования
            self.open_group_edit_dialog(representative, current_similar)
            
        except Exception as e:
            messagebox.showerror("Ошибка", f"Ошибка при редактировании группы: {e}")

    def remove_from_param_group(self):
        """Удалить выбранные параметры из группы"""
        try:
            selection = self.params_tree.selection()
            if not selection:
                messagebox.showwarning("Предупреждение", "Выберите группу для редактирования.")
                return
            
            item = selection[0]
            values = self.params_tree.item(item, "values")
            representative = values[1]  # Основной параметр
            current_similar = values[2]  # Похожие параметры
            
            if representative not in self.param_merge_settings:
                messagebox.showerror("Ошибка", "Группа не найдена в настройках.")
                return
            
            # Получаем список похожих параметров
            similar_list = self.param_merge_settings[representative].get('similar_params', [])
            
            if not similar_list:
                messagebox.showinfo("Информация", "В группе нет параметров для удаления.")
                return
            
            # Открываем диалог выбора параметров для удаления
            self.open_remove_params_dialog(representative, similar_list)
            
        except Exception as e:
            messagebox.showerror("Ошибка", f"Ошибка при удалении из группы: {e}")

    def delete_param_group(self):
        """Удалить всю группу параметров"""
        try:
            selection = self.params_tree.selection()
            if not selection:
                messagebox.showwarning("Предупреждение", "Выберите группу для удаления.")
                return
            
            item = selection[0]
            values = self.params_tree.item(item, "values")
            representative = values[1]  # Основной параметр
            
            if representative not in self.param_merge_settings:
                messagebox.showerror("Ошибка", "Группа не найдена в настройках.")
                return
            
            # Подтверждение удаления
            result = messagebox.askyesno(
                "Подтверждение", 
                f"Удалить группу '{representative}' полностью?\n\nЭто действие нельзя отменить."
            )
            
            if result:
                del self.param_merge_settings[representative]
                self.save_merge_settings()
                self.update_params_tree()
                messagebox.showinfo("Успех", f"Группа '{representative}' удалена.")
            
        except Exception as e:
            messagebox.showerror("Ошибка", f"Ошибка при удалении группы: {e}")

    def open_group_edit_dialog(self, representative, current_similar):
        """Открыть диалог редактирования группы"""
        dialog = Toplevel(self.root)
        dialog.title(f"Редактирование группы: {representative}")
        dialog.geometry("600x400")
        dialog.transient(self.root)
        dialog.grab_set()
        
        # Центрируем диалог
        dialog.update_idletasks()
        x = (dialog.winfo_screenwidth() // 2) - (dialog.winfo_width() // 2)
        y = (dialog.winfo_screenheight() // 2) - (dialog.winfo_height() // 2)
        dialog.geometry(f"+{x}+{y}")
        
        # Заголовок
        Label(dialog, text=f"Основной параметр: {representative}", 
              font=("Arial", 12, "bold")).pack(pady=10)
        
        # Текущие похожие параметры
        Label(dialog, text="Похожие параметры (по одному на строку):").pack(anchor=W, padx=10)
        
        text_frame = Frame(dialog)
        text_frame.pack(fill=BOTH, expand=True, padx=10, pady=5)
        
        text_widget = Text(text_frame, height=15, width=70)
        scrollbar = Scrollbar(text_frame, orient=VERTICAL, command=text_widget.yview)
        text_widget.configure(yscrollcommand=scrollbar.set)
        
        text_widget.pack(side=LEFT, fill=BOTH, expand=True)
        scrollbar.pack(side=RIGHT, fill=Y)
        
        # Заполняем текущими параметрами
        current_params = self.param_merge_settings[representative].get('similar_params', [])
        text_widget.insert(END, '\n'.join(current_params))
        
        # Кнопки
        button_frame = Frame(dialog)
        button_frame.pack(fill=X, padx=10, pady=10)
        
        def save_changes():
            try:
                # Получаем новый список параметров
                content = text_widget.get(1.0, END).strip()
                new_params = [p.strip() for p in content.split('\n') if p.strip()]
                
                # Удаляем основной параметр, если он случайно попал в список
                new_params = [p for p in new_params if p != representative]
                
                if not new_params:
                    # Если список пуст, удаляем всю группу
                    if messagebox.askyesno("Подтверждение", 
                                         "Список похожих параметров пуст.\nУдалить всю группу?"):
                        del self.param_merge_settings[representative]
                        self.save_merge_settings()
                        self.update_params_tree()
                        dialog.destroy()
                        messagebox.showinfo("Успех", "Группа удалена.")
                    return
                
                # Сохраняем изменения
                self.param_merge_settings[representative]['similar_params'] = new_params
                self.save_merge_settings()
                self.update_params_tree()
                dialog.destroy()
                messagebox.showinfo("Успех", "Группа обновлена.")
                
            except Exception as e:
                messagebox.showerror("Ошибка", f"Ошибка при сохранении: {e}")
        
        def cancel_changes():
            dialog.destroy()
        
        Button(button_frame, text="💾 Сохранить", command=save_changes).pack(side=LEFT, padx=5)
        Button(button_frame, text="❌ Отмена", command=cancel_changes).pack(side=LEFT, padx=5)
        
        # Подсказка
        Label(dialog, text="💡 Совет: Удалите ненужные параметры или добавьте новые", 
              font=("Arial", 9), fg="gray").pack(pady=5)

    def open_remove_params_dialog(self, representative, similar_list):
        """Открыть диалог выбора параметров для удаления"""
        dialog = Toplevel(self.root)
        dialog.title(f"Удаление из группы: {representative}")
        dialog.geometry("500x300")
        dialog.transient(self.root)
        dialog.grab_set()
        
        # Центрируем диалог
        dialog.update_idletasks()
        x = (dialog.winfo_screenwidth() // 2) - (dialog.winfo_width() // 2)
        y = (dialog.winfo_screenheight() // 2) - (dialog.winfo_height() // 2)
        dialog.geometry(f"+{x}+{y}")
        
        Label(dialog, text=f"Выберите параметры для удаления из группы '{representative}':", 
              font=("Arial", 11, "bold")).pack(pady=10)
        
        # Список с чекбоксами
        frame = Frame(dialog)
        frame.pack(fill=BOTH, expand=True, padx=10, pady=5)
        
        canvas = Canvas(frame)
        scrollbar = Scrollbar(frame, orient="vertical", command=canvas.yview)
        scrollable_frame = Frame(canvas)
        
        scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )
        
        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)
        
        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
        
        # Переменные для чекбоксов
        checkbox_vars = {}
        for param in similar_list:
            var = BooleanVar()
            checkbox_vars[param] = var
            Checkbutton(scrollable_frame, text=param, variable=var).pack(anchor=W, padx=5, pady=2)
        
        # Кнопки
        button_frame = Frame(dialog)
        button_frame.pack(fill=X, padx=10, pady=10)
        
        def remove_selected():
            try:
                # Получаем выбранные для удаления параметры
                to_remove = [param for param, var in checkbox_vars.items() if var.get()]
                
                if not to_remove:
                    messagebox.showwarning("Предупреждение", "Выберите параметры для удаления.")
                    return
                
                # Подтверждение
                result = messagebox.askyesno(
                    "Подтверждение", 
                    f"Удалить {len(to_remove)} параметр(ов) из группы?\n\n" + 
                    '\n'.join(to_remove)
                )
                
                if result:
                    # Обновляем список
                    new_similar_list = [p for p in similar_list if p not in to_remove]
                    
                    if not new_similar_list:
                        # Если список стал пустым, удаляем всю группу
                        if messagebox.askyesno("Подтверждение", 
                                             "Список похожих параметров стал пустым.\nУдалить всю группу?"):
                            del self.param_merge_settings[representative]
                        dialog.destroy()
                    else:
                        # Сохраняем обновленный список
                        self.param_merge_settings[representative]['similar_params'] = new_similar_list
                        dialog.destroy()
                    
                    self.save_merge_settings()
                    self.update_params_tree()
                    messagebox.showinfo("Успех", f"Удалено {len(to_remove)} параметр(ов).")
                
            except Exception as e:
                messagebox.showerror("Ошибка", f"Ошибка при удалении: {e}")
        
        def cancel_removal():
            dialog.destroy()
        
        Button(button_frame, text="🗑️ Удалить выбранные", command=remove_selected).pack(side=LEFT, padx=5)
        Button(button_frame, text="❌ Отмена", command=cancel_removal).pack(side=LEFT, padx=5)

    def open_exclusions_dialog(self):
        """Открывает диалог управления исключениями"""
        dialog = Toplevel(self.root)
        dialog.title("Управление исключениями")
        dialog.geometry("500x400")
        dialog.transient(self.root)
        dialog.grab_set()
        
        # Центрируем диалог
        dialog.geometry("+%d+%d" % (self.root.winfo_rootx() + 100, self.root.winfo_rooty() + 100))
        
        Label(dialog, text="Управление исключениями", font=("Arial", 12, "bold")).pack(pady=10)
        Label(dialog, text="Параметры в списке исключений не будут разделяться на значения", 
              font=("Arial", 9)).pack(pady=(0, 10))
        
        # Фрейм со списком
        list_frame = Frame(dialog)
        list_frame.pack(fill=BOTH, expand=True, padx=10, pady=5)
        
        # Список исключений
        self.exclusions_listbox = Listbox(list_frame)
        self.exclusions_listbox.pack(side=LEFT, fill=BOTH, expand=True)
        
        scrollbar = Scrollbar(list_frame, orient=VERTICAL, command=self.exclusions_listbox.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.exclusions_listbox.configure(yscrollcommand=scrollbar.set)
        
        # Обновляем список
        for exclusion in self.exclusions:
            self.exclusions_listbox.insert(END, exclusion)
        
        # Кнопки управления
        btn_frame = Frame(dialog)
        btn_frame.pack(fill=X, padx=10, pady=10)
        
        Button(btn_frame, text="➕ Добавить", command=lambda: self.add_exclusion_dialog(dialog)).pack(side=LEFT, padx=5)
        Button(btn_frame, text="🗑️ Удалить", command=lambda: self.delete_exclusion_dialog(dialog)).pack(side=LEFT, padx=5)
        Button(btn_frame, text="✅ Закрыть", command=dialog.destroy).pack(side=RIGHT, padx=5)

    def add_exclusion_dialog(self, parent):
        """Добавляет новое исключение"""
        new_exclusion = simpledialog.askstring("Добавить исключение", 
                                              "Введите название параметра для исключения:", 
                                              parent=parent)
        if new_exclusion and new_exclusion.strip():
            new_exclusion = new_exclusion.strip()
            if new_exclusion not in self.exclusions:
                self.exclusions.append(new_exclusion)
                self.exclusions_listbox.insert(END, new_exclusion)
                self.save_excl()

    def delete_exclusion_dialog(self, parent):
        """Удаляет выбранное исключение"""
        selection = self.exclusions_listbox.curselection()
        if selection:
            index = selection[0]
            exclusion = self.exclusions_listbox.get(index)
            if messagebox.askyesno("Подтверждение", 
                                 f"Удалить исключение '{exclusion}'?", 
                                 parent=parent):
                self.exclusions.remove(exclusion)
                self.exclusions_listbox.delete(index)
                self.save_excl()

    def show_about_dialog(self):
        """Показывает информацию о программе"""
        translation_status = "✅ Активна" if self.translation_available else "❌ Недоступна"
        
        about_text = f"""Редактор атрибутов товаров v4.5 + Переводы

🎯 Компактная эргономичная версия
🌐 Система автоматического перевода: {translation_status}

Основные возможности:
• Парсинг HTML-характеристик из Excel
• Автоматическое разделение значений
• Маппинг параметров
• Правила замен и нормализации
• Управление синонимами значений
• Защищенные значения
• 🌐 Автоматический перевод EN→RU
• 🎯 Интеллектуальный поиск переводов
• 📚 Пользовательские словари переводов
• Экспорт результатов

Версия: 4.5 + Translation System
Дата: 2025-06-02
Автор: AI Assistant

© 2025 - Инструмент для обработки товарных атрибутов"""
        
        messagebox.showinfo("О программе", about_text)

    # 🌐 МЕТОДЫ ДЛЯ РАБОТЫ С ПЕРЕВОДАМИ

    def open_translation_dialog(self):
        """Открывает функциональное меню переводов"""
        if not self.translation_available:
            messagebox.showwarning("Переводы недоступны", 
                                 "Система переводов не загружена. Проверьте файлы переводов.")
            return
        
        # Создаем функциональное меню переводов
        menu = Menu(self.root, tearoff=0)
        
        # Основные функции переводов
        menu.add_command(label="🌐 Перевести параметры", 
                        command=self.translate_current_parameters)
        menu.add_command(label="🔤 Перевести значения", 
                        command=self.translate_current_values)
        menu.add_separator()
        
        # Управление переводами
        menu.add_command(label="📥 Импорт переводов", 
                        command=self.import_translations)
        menu.add_command(label="📤 Экспорт переводов", 
                        command=self.export_translations)
        menu.add_separator()
        
        # Настройки
        menu.add_command(label="⚙️ Настройки переводов", 
                        command=self.show_translation_settings)
        menu.add_command(label="ℹ️ О системе переводов", 
                        command=self.show_translation_info)
        
        # Показываем меню рядом с кнопкой
        try:
            x = self.btn_translate.winfo_rootx()
            y = self.btn_translate.winfo_rooty() + self.btn_translate.winfo_height()
            menu.post(x, y)
        except Exception as e:
            print(f"Ошибка отображения меню: {e}")
            # Fallback - показываем в центре окна
            menu.post(self.root.winfo_x() + 100, self.root.winfo_y() + 100)

    def show_translation_settings(self):
        """Показывает настройки системы переводов"""
        if not self.translation_available:
            messagebox.showwarning("Переводы недоступны", "Система переводов не загружена.")
            return
        
        # 🔧 ИСПРАВЛЕНО: Проверяем тип системы переводов
        if TRANSLATION_SYSTEM_TYPE in ["advanced", "basic_integrated"]:
            try:
                # Пробуем открыть расширенный диалог настроек
                dialog = TranslationManagementDialog(self.root, self.translation_system)
                return
            except Exception as e:
                print(f"Ошибка открытия расширенного диалога: {e}")
        
        # Для встроенной системы или при ошибке - показываем простые настройки
        self.show_simple_translation_settings()

    def show_simple_translation_settings(self):
        """Простые настройки переводов"""
        settings_window = Toplevel(self.root)
        settings_window.title("Настройки переводов")
        settings_window.geometry("400x300")
        settings_window.transient(self.root)
        settings_window.grab_set()
        
        # Настройки автоперевода
        auto_frame = LabelFrame(settings_window, text="Автоперевод", padx=10, pady=10)
        auto_frame.pack(fill=X, padx=10, pady=10)
        
        auto_var = BooleanVar()
        if hasattr(self.translation_system, 'auto_translate'):
            auto_var.set(self.translation_system.auto_translate)
        else:
            auto_var.set(True)
            
        auto_check = Checkbutton(auto_frame, text="Включить автоперевод", variable=auto_var)
        auto_check.pack(anchor=W)
        
        # Кнопки
        btn_frame = Frame(settings_window)
        btn_frame.pack(fill=X, padx=10, pady=10)
        
        def save_settings():
            if hasattr(self.translation_system, 'auto_translate'):
                self.translation_system.auto_translate = auto_var.get()
            settings_window.destroy()
            # Обновляем основную таблицу с новыми настройками
            self.update_main_tree_with_mappings()
        
        Button(btn_frame, text="Сохранить", command=save_settings).pack(side=RIGHT, padx=5)
        Button(btn_frame, text="Отмена", command=settings_window.destroy).pack(side=RIGHT)

    def show_translation_info(self):
        """Показывает информацию о системе переводов"""
        if not self.translation_available:
            messagebox.showwarning("Переводы недоступны", "Система переводов не загружена.")
            return
        
        # Собираем информацию о системе
        info_lines = [
            "🌐 СИСТЕМА ПЕРЕВОДОВ",
            "",
            f"📚 Доступно переводов: {len(getattr(self.translation_system, 'translation_dict', {}))}"
        ]
        
        if hasattr(self.translation_system, 'auto_translate'):
            status = "Включен" if self.translation_system.auto_translate else "Отключен"
            info_lines.append(f"🎯 Автоперевод: {status}")
        
        if hasattr(self.translation_system, 'fuzzy_match_threshold'):
            info_lines.append(f"🔍 Нечеткий поиск: {self.translation_system.fuzzy_match_threshold}%")
        
        info_lines.extend([
            "",
            "📋 Статистика сопоставлений:",
            f"• Всего сопоставлений: {len(self.mappings)}",
            f"• Обработано строк: {len(self.tree.get_children()) if hasattr(self, 'tree') else 0}"
        ])
        
        messagebox.showinfo("О системе переводов", "\n".join(info_lines))

    def translate_current_parameters(self):
        """Переводит текущие параметры в маппингах"""
        if not self.translation_available or not self.translation_system:
            messagebox.showwarning("Переводы недоступны", "Система переводов не загружена.")
            return
        
        if not self.mappings:
            messagebox.showinfo("Информация", "Нет параметров для перевода. Сначала обработайте таблицу.")
            return
        
        translated_count = 0
        for original_param in list(self.mappings.keys()):
            translated = self.translation_system.translate_text(original_param, is_parameter=True)
            if translated != original_param:
                # Заменяем ключ в маппингах
                self.mappings[translated] = self.mappings.pop(original_param)
                translated_count += 1
        
        if translated_count > 0:
            self.refresh_mapping_table()
            # 🔧 ИСПРАВЛЕНО: Автоматически обновляем основную таблицу
            self.update_main_tree_with_mappings()
            messagebox.showinfo("Перевод завершен", f"Переведено параметров: {translated_count}")
        else:
            messagebox.showinfo("Перевод завершен", "Новых переводов не найдено.")

    def translate_current_values(self):
        """Переводит текущие значения в маппингах"""
        if not self.translation_available or not self.translation_system:
            messagebox.showwarning("Переводы недоступны", "Система переводов не загружена.")
            return
        
        if not self.mappings:
            messagebox.showinfo("Информация", "Нет значений для перевода. Сначала обработайте таблицу.")
            return
        
        translated_count = 0
        for param, values in self.mappings.items():
            if isinstance(values, list):
                new_values = []
                for value in values:
                    translated = self.translation_system.translate_text(str(value), is_parameter=False)
                    new_values.append(translated)
                    if translated != str(value):
                        translated_count += 1
                self.mappings[param] = new_values
        
        if translated_count > 0:
            self.refresh_mapping_table()
            # 🔧 ИСПРАВЛЕНО: Автоматически обновляем основную таблицу
            self.update_main_tree_with_mappings()
            messagebox.showinfo("Перевод завершен", f"Переведено значений: {translated_count}")
        else:
            messagebox.showinfo("Перевод завершен", "Новых переводов не найдено.")

    def export_translations(self):
        """Экспортирует пользовательские переводы"""
        if not self.translation_available or not self.translation_system:
            messagebox.showwarning("Переводы недоступны", "Система переводов не загружена.")
            return
        
        filename = filedialog.asksaveasfilename(
            title="Экспорт переводов",
            defaultextension=".json",
            filetypes=[("JSON files", "*.json"), ("All files", "*.*")]
        )
        
        if filename:
            try:
                export_data = {
                    "custom_translations": getattr(self.translation_system, 'custom_translations', {}),
                    "settings": {
                        "auto_translate": getattr(self.translation_system, 'auto_translate', True),
                        "fuzzy_match_threshold": getattr(self.translation_system, 'fuzzy_match_threshold', 80),
                        "translate_values": getattr(self.translation_system, 'translate_values', True),
                        "translate_parameters": getattr(self.translation_system, 'translate_parameters', True)
                    }
                }
                
                with open(filename, 'w', encoding='utf-8') as f:
                    json.dump(export_data, f, ensure_ascii=False, indent=2)
                
                messagebox.showinfo("Экспорт завершен", f"Переводы сохранены в {filename}")
            except Exception as e:
                messagebox.showerror("Ошибка экспорта", f"Не удалось сохранить переводы: {e}")

    def import_translations(self):
        """Импортирует пользовательские переводы"""
        if not self.translation_available or not self.translation_system:
            messagebox.showwarning("Переводы недоступны", "Система переводов не загружена.")
            return
        
        filename = filedialog.askopenfilename(
            title="Импорт переводов",
            filetypes=[("JSON files", "*.json"), ("All files", "*.*")]
        )
        
        if filename:
            try:
                with open(filename, 'r', encoding='utf-8') as f:
                    import_data = json.load(f)
                
                # Импортируем пользовательские переводы
                if "custom_translations" in import_data:
                    if hasattr(self.translation_system, 'custom_translations'):
                        self.translation_system.custom_translations.update(import_data["custom_translations"])
                        if hasattr(self.translation_system, 'save_custom_translations'):
                            self.translation_system.save_custom_translations()
                
                # Импортируем настройки
                if "settings" in import_data:
                    settings = import_data["settings"]
                    if hasattr(self.translation_system, 'auto_translate'):
                        self.translation_system.auto_translate = settings.get("auto_translate", True)
                    if hasattr(self.translation_system, 'fuzzy_match_threshold'):
                        self.translation_system.fuzzy_match_threshold = settings.get("fuzzy_match_threshold", 80)
                    if hasattr(self.translation_system, 'translate_values'):
                        self.translation_system.translate_values = settings.get("translate_values", True)
                    if hasattr(self.translation_system, 'translate_parameters'):
                        self.translation_system.translate_parameters = settings.get("translate_parameters", True)
                
                messagebox.showinfo("Импорт завершен", f"Переводы загружены из {filename}")
            except Exception as e:
                messagebox.showerror("Ошибка импорта", f"Не удалось загрузить переводы: {e}")

    def apply_auto_translation(self, text, is_parameter=True):
        """Применяет автоматический перевод к тексту, если включено"""
        if not self.can_auto_translate():
            return text
            
        # Пробуем разные методы перевода в зависимости от системы
        if hasattr(self.translation_system, 'translate_text'):
            translated = self.translation_system.translate_text(text, is_parameter=is_parameter)
            # Показываем только если перевод действительно произошел
            if translated != text:
                print(f"🌐 Перевод: '{text}' → '{translated}'")
            return translated
        elif hasattr(self.translation_system, 'basic_translate'):
            translated = self.translation_system.basic_translate(text)
            if translated != text:
                print(f"🌐 Перевод: '{text}' → '{translated}'")
            return translated
            
        return text

    def can_auto_translate(self):
        """Проверяет, доступен ли автоперевод в текущей системе переводов"""
        if not self.translation_available or not self.translation_system:
            return False
        
        # Для встроенной системы проверяем атрибут auto_translate
        if hasattr(self.translation_system, 'auto_translate'):
            return self.translation_system.auto_translate
        
        # Для IntegratedTranslationSystem считаем, что автоперевод всегда доступен
        return True

    def show_hotkeys_dialog(self):
        """Показывает список горячих клавиш"""
        hotkeys_text = """Горячие клавиши:

📁 Файлы:
Ctrl+O - Загрузить файл
Ctrl+P - Обработать таблицу  
Ctrl+S - Сохранить результат
Ctrl+Q - Выход

⌨️ Управление:
Ctrl+C - Копировать
Ctrl+V - Вставить
Ctrl+Z - Отмена
Ctrl+A - Выбрать все
Delete - Удалить выбранное

🌐 Переводы:
Меню "🌐 Переводы" → "⚙️ Настройки переводов"
Меню "🌐 Переводы" → "🔄 Перевести параметры"
Меню "🌐 Переводы" → "🔄 Перевести значения"
Кнопка "🌐 Переводы" на панели инструментов

🖱️ Интерфейс:
ПКМ - Контекстное меню
F1 - Справка
Esc - Закрыть диалог"""
        
        messagebox.showinfo("Горячие клавиши", hotkeys_text)


if __name__ == "__main__":
    root = Tk()
    app = AttributeEditor(root)
    root.mainloop()

если ответ не будет укладываться в одно сообщение , напиши его несколькими сообщениями