# -*- coding: utf-8 -*-
"""
通用单位转换器 - Universal Unit Converter v2.0
支持 9 大类、123 个单位，GUI + CLI 双模式
"""

from decimal import Decimal, getcontext, InvalidOperation
from typing import Dict, Tuple, Optional, List, Union
import sys
import os
import json
import math
import re
import pickle
import time
from datetime import datetime

# PyQt5 - 必须安装，否则直接报错提示
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QGridLayout, QLabel, QLineEdit, QPushButton, QComboBox,
    QTabWidget, QTextEdit, QTableWidget, QTableWidgetItem,
    QHeaderView, QFrame, QSplitter, QGroupBox, QSpinBox,
    QCheckBox, QMessageBox, QFileDialog, QInputDialog, QMenu,
    QAction, QSystemTrayIcon, QStyle, QProgressBar, QDoubleSpinBox,
    QListWidget, QListWidgetItem, QToolBar, QStatusBar, QShortcut,
    QDialog, QFormLayout, QDialogButtonBox
)
from PyQt5.QtCore import Qt, QTimer, QSize, pyqtSignal
from PyQt5.QtGui import QIcon, QFont, QKeySequence, QPalette, QColor

getcontext().prec = 40


# ============================================================
# 换算引擎
# ============================================================

class ConversionEngine:
    """基准单位中心制换算引擎"""

    CATEGORIES = {
    
        "长度": {
            "基准": "米",
            "单位": {
                "毫米": Decimal("0.001"),
                "厘米": Decimal("0.01"),
                "分米": Decimal("0.1"),
                "米": Decimal("1"),
                "千米": Decimal("1000"),
                "公里": Decimal("1000"),
                "英寸": Decimal("0.0254"),
                "英尺": Decimal("0.3048"),
                "码": Decimal("0.9144"),
                "英里": Decimal("1609.344"),
                "海里": Decimal("1852"),
                "光年": Decimal("9460730472580800"),
                "天文单位": Decimal("149597870700"),
                "埃": Decimal("1e-10"),
            }
        },

        "重量": {
            "基准": "克",
            "单位": {
                "微克": Decimal("1e-6"),
                "毫克": Decimal("0.001"),
                "克": Decimal("1"),
                "千克": Decimal("1000"),
                "吨": Decimal("1e6"),
                "克拉": Decimal("0.2"),
                "格令": Decimal("0.06479891"),
                "盎司": Decimal("28.349523125"),
                "磅": Decimal("453.59237"),
                "英石": Decimal("6350.29318"),
                "市斤": Decimal("500"),
                "两": Decimal("50"),
            }
        },

        "面积": {
            "基准": "平方米",
            "单位": {
                "平方毫米": Decimal("1e-6"),
                "平方厘米": Decimal("1e-4"),
                "平方分米": Decimal("0.01"),
                "平方米": Decimal("1"),
                "公亩": Decimal("100"),
                "公顷": Decimal("10000"),
                "平方公里": Decimal("1e6"),
                "平方英寸": Decimal("0.00064516"),
                "平方英尺": Decimal("0.09290304"),
                "平方码": Decimal("0.83612736"),
                "英亩": Decimal("4046.8564224"),
                "平方英里": Decimal("2589988.110336"),
                "亩": Decimal("666.6666666667"),
            }
        },

        "体积": {
            "基准": "立方米",
            "单位": {
                "毫升": Decimal("1e-6"),
                "升": Decimal("0.001"),
                "立方米": Decimal("1"),
                "立方厘米": Decimal("1e-6"),
                "立方英寸": Decimal("1.6387064e-5"),
                "立方英尺": Decimal("0.028316846592"),
                "加仑(美)": Decimal("0.003785411784"),
                "加仑(英)": Decimal("0.00454609"),
                "品脱(美)": Decimal("0.000473176473"),
                "品脱(英)": Decimal("0.00056826125"),
                "桶(石油)": Decimal("0.158987294928"),
            }
        },

        "温度": {
            "基准": "K",
            "单位": {
                "C": "celsius",
                "F": "fahrenheit",
                "K": "kelvin",
                "摄氏度": "celsius",
                "华氏度": "fahrenheit",
                "开尔文": "kelvin",
            }
        },

        "时间": {
            "基准": "秒",
            "单位": {
                "毫秒": Decimal("0.001"),
                "秒": Decimal("1"),
                "分钟": Decimal("60"),
                "小时": Decimal("3600"),
                "天": Decimal("86400"),
                "周": Decimal("604800"),
                "月(30天)": Decimal("2592000"),
                "年(365天)": Decimal("31536000"),
                "世纪": Decimal("3153600000"),
            }
        },

        "速度": {
            "基准": "米/秒",
            "单位": {
                "米/秒": Decimal("1"),
                "千米/小时": Decimal("0.27777777777778"),
                "英里/小时": Decimal("0.44704"),
                "节": Decimal("0.51444444444444"),
                "英尺/秒": Decimal("0.3048"),
                "马赫": Decimal("343"),
                "光速": Decimal("299792458"),
            }
        },

        "数据存储": {
            "基准": "字节",
            "单位": {
                "比特": Decimal("0.125"),
                "字节": Decimal("1"),
                "KB": Decimal("1024"),
                "MB": Decimal("1048576"),
                "GB": Decimal("1073741824"),
                "TB": Decimal("1099511627776"),
                "PB": Decimal("1125899906842624"),
            }
        },

        "压力": {
            "基准": "帕斯卡",
            "单位": {
                "帕斯卡": Decimal("1"),
                "千帕": Decimal("1000"),
                "兆帕": Decimal("1e6"),
                "巴": Decimal("100000"),
                "毫巴": Decimal("100"),
                "大气压": Decimal("101325"),
                "毫米汞柱": Decimal("133.322387415"),
                "psi": Decimal("6894.757293168"),
            }
        }
    }
            
        
    

    @staticmethod
    def _temp_to_base(value: Decimal, unit: str) -> Decimal:
        unit = unit.lower()
        if unit in ("k", "kelvin", "开尔文"):
            return value
        elif unit in ("c", "celsius", "摄氏度"):
            return value + Decimal("273.15")
        elif unit in ("f", "fahrenheit", "华氏度"):
            return (value - Decimal("32")) * Decimal("5") / Decimal("9") + Decimal("273.15")
        elif unit in ("r", "rankine"):
            return value * Decimal("5") / Decimal("9")
        raise ValueError(f"未知温度单位: {unit}")

    @staticmethod
    def _temp_from_base(value: Decimal, unit: str) -> Decimal:
        unit = unit.lower()
        if unit in ("k", "kelvin", "开尔文"):
            return value
        elif unit in ("c", "celsius", "摄氏度"):
            return value - Decimal("273.15")
        elif unit in ("f", "fahrenheit", "华氏度"):
            return (value - Decimal("273.15")) * Decimal("9") / Decimal("5") + Decimal("32")
        elif unit in ("r", "rankine"):
            return value * Decimal("9") / Decimal("5")
        raise ValueError(f"未知温度单位: {unit}")

    @classmethod
    def convert(cls, value, from_unit: str, to_unit: str, category: str, precision: int = 10):
        if isinstance(value, str):
            value = cls._evaluate_expression(value.strip())
        value = Decimal(str(value))

        cat = cls.CATEGORIES.get(category)
        if not cat:
            raise ValueError(f"未知类别: {category}")

        if category == "温度":
            base_val = cls._temp_to_base(value, from_unit)
            result = cls._temp_from_base(base_val, to_unit)
        else:
            units = cat["单位"]
            if from_unit not in units or to_unit not in units:
                raise ValueError(f"单位不支持: {from_unit} 或 {to_unit}")
            base_val = value * units[from_unit]
            result = base_val / units[to_unit]

        return float(round(result, precision))

    @classmethod
    def _evaluate_expression(cls, expr: str) -> Decimal:
        expr = expr.replace("^", "**").replace("×", "*").replace("÷", "/")
        if not re.match(r'^[\d\s\.\+\-\*\/\(\)\*\*]+$', expr):
            raise ValueError(f"表达式包含非法字符: {expr}")
        try:
            result = eval(expr, {"__builtins__": {}}, {})
            return Decimal(str(result))
        except Exception as e:
            raise ValueError(f"表达式求值失败: {e}")

    @classmethod
    def get_all_units(cls, category: str) -> List[str]:
        cat = cls.CATEGORIES.get(category)
        if not cat:
            return []
        return list(cat["单位"].keys())

    @classmethod
    def get_categories(cls) -> List[str]:
        return list(cls.CATEGORIES.keys())


# ============================================================
# 智能解析器
# ============================================================

class SmartParser:
    ALIASES = {
        "mm": "毫米", "cm": "厘米", "m": "米", "km": "千米",
        "in": "英寸", "ft": "英尺", "yd": "码", "mi": "英里",
        "nm": "海里", "g": "克", "kg": "千克", "lb": "磅", "oz": "盎司",
        "mg": "毫克", "t": "吨", "jin": "斤", "c": "摄氏度", "f": "华氏度",
        "s": "秒", "min": "分钟", "h": "小时", "d": "天",
        "b": "B", "kb": "KB", "mb": "MB", "gb": "GB",
    }

    @classmethod
    def parse_input(cls, text: str):
        text = text.strip()
        temp_match = re.match(
            r'^([\d\.\+\-\*/\(\)]+)\s*°?\s*(c|f|k|摄氏度|华氏度|开尔文)$',
            text.lower())
        if temp_match:
            val_str, unit = temp_match.groups()
            try:
                val = cls._safe_eval(val_str)
                return val, cls.ALIASES.get(unit, unit)
            except Exception:
                pass

        match = re.match(r'^([\d\.\+\-\*/\(\)\s]+)\s*([a-zA-Z°μ中文]+)$', text)
        if match:
            val_str, unit = match.groups()
            try:
                val = cls._safe_eval(val_str.strip())
                unit_norm = cls.ALIASES.get(unit.lower().strip(), unit.strip())
                return val, unit_norm
            except Exception:
                pass

        try:
            val = cls._safe_eval(text)
            return val, None
        except Exception:
            pass

        return None, None

    @classmethod
    def _safe_eval(cls, expr: str) -> Decimal:
        expr = expr.replace("^", "**").replace("×", "*").replace("÷", "/")
        if not re.match(r'^[\d\s\.\+\-\*\/\(\)\*\*]+$', expr):
            raise ValueError("非法表达式")
        result = eval(expr, {"__builtins__": {}}, {})
        return Decimal(str(result))


# ============================================================
# 历史记录管理
# ============================================================

class HistoryManager:
    HISTORY_FILE = os.path.join(os.path.expanduser("~"), ".unit_converter_history.pkl")
    MAX_HISTORY = 500

    @classmethod
    def load(cls) -> List[Dict]:
        try:
            if os.path.exists(cls.HISTORY_FILE):
                with open(cls.HISTORY_FILE, 'rb') as f:
                    return pickle.load(f)
        except Exception:
            pass
        return []

    @classmethod
    def save(cls, history: List[Dict]):
        try:
            history = history[-cls.MAX_HISTORY:]
            with open(cls.HISTORY_FILE, 'wb') as f:
                pickle.dump(history, f)
        except Exception:
            pass

    @classmethod
    def add(cls, entry: Dict):
        history = cls.load()
        entry["timestamp"] = datetime.now().isoformat()
        history.append(entry)
        cls.save(history)

    @classmethod
    def clear(cls):
        try:
            if os.path.exists(cls.HISTORY_FILE):
                os.remove(cls.HISTORY_FILE)
        except Exception:
            pass


# ============================================================
# 自定义单位管理
# ============================================================

class CustomUnitManager:
    FILE = os.path.join(os.path.expanduser("~"), ".unit_converter_custom_units.json")

    @classmethod
    def load(cls) -> Dict:
        try:
            if os.path.exists(cls.FILE):
                with open(cls.FILE, 'r', encoding='utf-8') as f:
                    return json.load(f)
        except Exception:
            pass
        return {}

    @classmethod
    def save(cls, data: Dict):
        try:
            with open(cls.FILE, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
        except Exception:
            pass

    @classmethod
    def add_unit(cls, category: str, name: str, factor: str, base_unit: str):
        data = cls.load()
        if category not in data:
            data[category] = {}
        data[category][name] = {
            "factor": factor,
            "base_unit": base_unit,
            "created": datetime.now().isoformat()
        }
        cls.save(data)
        return True

    @classmethod
    def get_units(cls, category: str) -> Dict:
        data = cls.load()
        return data.get(category, {})

    @classmethod
    def apply_to_engine(cls):
        data = cls.load()
        for cat_name, units in data.items():
            if cat_name in ConversionEngine.CATEGORIES:
                for unit_name, info in units.items():
                    try:
                        ConversionEngine.CATEGORIES[cat_name]["单位"][unit_name] = \
                            Decimal(str(info["factor"]))
                    except Exception:
                        pass


# ============================================================
# 货币换算
# ============================================================

class CurrencyConverter:
    CACHE_FILE = os.path.join(os.path.expanduser("~"), ".unit_converter_rates.json")
    CACHE_DURATION = 3600

    CURRENCIES = {
        "USD": "美元", "EUR": "欧元", "JPY": "日元", "GBP": "英镑",
        "CNY": "人民币", "AUD": "澳元", "CAD": "加元", "CHF": "瑞郎",
        "HKD": "港币", "SGD": "新加坡元", "SEK": "瑞典克朗", "KRW": "韩元",
        "INR": "印度卢比", "BRL": "巴西雷亚尔", "RUB": "俄罗斯卢布"
    }

    @classmethod
    def get_rates(cls, base: str = "USD") -> Optional[Dict]:
        try:
            if os.path.exists(cls.CACHE_FILE):
                with open(cls.CACHE_FILE, 'r') as f:
                    cache = json.load(f)
                if time.time() - cache.get("timestamp", 0) < cls.CACHE_DURATION:
                    if cache.get("base") == base:
                        return cache.get("rates", {})
        except Exception:
            pass

        try:
            import urllib.request
            url = f"https://api.exchangerate-api.com/v4/latest/{base}"
            req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
            with urllib.request.urlopen(req, timeout=5) as response:
                data = json.loads(response.read().decode())
                rates = data.get("rates", {})
                cache_data = {"base": base, "rates": rates, "timestamp": time.time()}
                with open(cls.CACHE_FILE, 'w') as f:
                    json.dump(cache_data, f)
                return rates
        except Exception:
            pass

        try:
            with open(cls.CACHE_FILE, 'r') as f:
                cache = json.load(f)
                return cache.get("rates", {})
        except Exception:
            pass

        return None

    @classmethod
    def convert(cls, value: float, from_curr: str, to_curr: str):
        rates = cls.get_rates("USD")
        if not rates:
            return None
        if from_curr == "USD":
            usd = value
        else:
            usd = value / rates.get(from_curr, 1)
        if to_curr == "USD":
            return usd
        else:
            return usd * rates.get(to_curr, 1)


# ============================================================
# GUI 对话框
# ============================================================

class AboutDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("关于")
        self.setFixedSize(400, 300)
        layout = QVBoxLayout(self)
        layout.addWidget(QLabel("<h2>通用单位转换器</h2>"))
        layout.addWidget(QLabel("版本 2.0"))
        layout.addWidget(QLabel("支持 9 大类、123 个单位"))
        layout.addWidget(QLabel("功能：实时换算、批量换算、历史记录、"))
        layout.addWidget(QLabel("自定义单位、货币换算、可视化图表"))
        layout.addWidget(QLabel(""))
        layout.addWidget(QLabel("基于 Python + PyQt5"))
        btn = QDialogButtonBox(QDialogButtonBox.Ok)
        btn.accepted.connect(self.accept)
        layout.addWidget(btn)


class CustomUnitDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("添加自定义单位")
        self.setFixedSize(350, 200)
        layout = QFormLayout(self)

        self.cat_combo = QComboBox()
        self.cat_combo.addItems(ConversionEngine.get_categories())
        layout.addRow("类别:", self.cat_combo)

        self.name_edit = QLineEdit()
        self.name_edit.setPlaceholderText("如: 里")
        layout.addRow("单位名称:", self.name_edit)

        self.factor_edit = QLineEdit()
        self.factor_edit.setPlaceholderText("相对于基准单位的系数，如: 500")
        layout.addRow("换算系数:", self.factor_edit)

        self.base_label = QLabel()
        layout.addRow("基准单位:", self.base_label)

        self.cat_combo.currentTextChanged.connect(self._update_base)
        self._update_base(self.cat_combo.currentText())

        btns = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        btns.accepted.connect(self.accept)
        btns.rejected.connect(self.reject)
        layout.addRow(btns)

    def _update_base(self, cat):
        base = ConversionEngine.CATEGORIES.get(cat, {}).get("基准", "")
        self.base_label.setText(base)

    def get_data(self):
        return {
            "category": self.cat_combo.currentText(),
            "name": self.name_edit.text().strip(),
            "factor": self.factor_edit.text().strip(),
            "base_unit": self.base_label.text()
        }


# ============================================================
# GUI 主窗口
# ============================================================

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("通用单位转换器 v2.0")
        self.setMinimumSize(1000, 700)

        CustomUnitManager.apply_to_engine()
        self.history = HistoryManager.load()
        self.dark_mode = False

        self.convert_timer = QTimer()
        self.convert_timer.setSingleShot(True)
        self.convert_timer.timeout.connect(self._do_convert)

        self._setup_ui()
        self._setup_shortcuts()
        self._setup_tray()

        self.cat_combo.setCurrentIndex(0)
        self._on_category_changed(0)

    def _setup_ui(self):
        central = QWidget()
        self.setCentralWidget(central)
        main_layout = QVBoxLayout(central)

        # 工具栏
        toolbar = QToolBar()
        toolbar.setIconSize(QSize(16, 16))
        self.addToolBar(toolbar)

        act_new = QAction("新建", self)
        act_new.setShortcut(QKeySequence.New)
        act_new.triggered.connect(self._reset_form)
        toolbar.addAction(act_new)

        act_conv = QAction("换算", self)
        act_conv.setShortcut(Qt.CTRL + Qt.Key_R)
        act_conv.triggered.connect(self._do_convert)
        toolbar.addAction(act_conv)

        toolbar.addSeparator()

        act_theme = QAction("切换主题", self)
        act_theme.setShortcut(Qt.Key_F5)
        act_theme.triggered.connect(self._toggle_theme)
        toolbar.addAction(act_theme)

        act_about = QAction("关于", self)
        act_about.setShortcut(Qt.Key_F1)
        act_about.triggered.connect(self._show_about)
        toolbar.addAction(act_about)

        # 顶部设置
        top_frame = QFrame()
        top_layout = QHBoxLayout(top_frame)

        top_layout.addWidget(QLabel("精度:"))
        self.precision_spin = QSpinBox()
        self.precision_spin.setRange(0, 20)
        self.precision_spin.setValue(6)
        self.precision_spin.valueChanged.connect(self._on_setting_changed)
        top_layout.addWidget(self.precision_spin)

        self.realtime_cb = QCheckBox("实时换算")
        self.realtime_cb.setChecked(True)
        self.realtime_cb.toggled.connect(self._on_realtime_toggled)
        top_layout.addWidget(self.realtime_cb)

        top_layout.addStretch()
        self.status_label = QLabel("就绪")
        top_layout.addWidget(self.status_label)
        main_layout.addWidget(top_frame)

        # 主分割器
        splitter = QSplitter(Qt.Horizontal)
        main_layout.addWidget(splitter)

        # 左侧换算面板
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)

        cat_layout = QHBoxLayout()
        cat_layout.addWidget(QLabel("类别:"))
        self.cat_combo = QComboBox()
        self.cat_combo.addItems(ConversionEngine.get_categories())
        self.cat_combo.currentIndexChanged.connect(self._on_category_changed)
        cat_layout.addWidget(self.cat_combo)
        cat_layout.addStretch()
        left_layout.addLayout(cat_layout)

        val_layout = QHBoxLayout()
        val_layout.addWidget(QLabel("数值:"))
        self.value_edit = QLineEdit()
        self.value_edit.setPlaceholderText("输入数值或表达式，如: 1, 2")
        self.value_edit.textChanged.connect(self._on_input_changed)
        self.value_edit.returnPressed.connect(self._do_convert)
        val_layout.addWidget(self.value_edit)
        left_layout.addLayout(val_layout)

        unit_layout = QGridLayout()
        unit_layout.addWidget(QLabel("从:"), 0, 0)
        self.from_combo = QComboBox()
        self.from_combo.currentIndexChanged.connect(self._on_input_changed)
        unit_layout.addWidget(self.from_combo, 0, 1)

        self.swap_btn = QPushButton("⇅ 交换")
        self.swap_btn.clicked.connect(self._swap_units)
        unit_layout.addWidget(self.swap_btn, 0, 2)

        unit_layout.addWidget(QLabel("到:"), 1, 0)
        self.to_combo = QComboBox()
        self.to_combo.currentIndexChanged.connect(self._on_input_changed)
        unit_layout.addWidget(self.to_combo, 1, 1)
        left_layout.addLayout(unit_layout)

        btn_layout = QHBoxLayout()
        self.convert_btn = QPushButton("换算 (Ctrl+R)")
        self.convert_btn.clicked.connect(self._do_convert)
        btn_layout.addWidget(self.convert_btn)

        self.clear_btn = QPushButton("清空")
        self.clear_btn.clicked.connect(self._reset_form)
        btn_layout.addWidget(self.clear_btn)
        left_layout.addLayout(btn_layout)

        result_group = QGroupBox("结果")
        result_layout = QVBoxLayout(result_group)
        self.result_label = QLabel("--")
        self.result_label.setFont(QFont("Consolas", 16, QFont.Bold))
        self.result_label.setAlignment(Qt.AlignCenter)
        result_layout.addWidget(self.result_label)
        self.formula_label = QLabel("")
        self.formula_label.setAlignment(Qt.AlignCenter)
        result_layout.addWidget(self.formula_label)
        left_layout.addWidget(result_group)
        left_layout.addStretch()

        splitter.addWidget(left_widget)

        # 右侧 Tabs
        right_tabs = QTabWidget()
        splitter.addWidget(right_tabs)

        # 快捷换算
        quick_widget = QWidget()
        quick_layout = QVBoxLayout(quick_widget)
        self.quick_table = QTableWidget()
        self.quick_table.setColumnCount(2)
        self.quick_table.setHorizontalHeaderLabels(["单位", "结果"])
        self.quick_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.quick_table.itemDoubleClicked.connect(self._on_quick_item_clicked)
        quick_layout.addWidget(QLabel("双击可载入数值"))
        quick_layout.addWidget(self.quick_table)
        right_tabs.addTab(quick_widget, "⚡ 快捷换算")

        # 单位对照表
        table_widget = QWidget()
        table_layout = QVBoxLayout(table_widget)
        self.full_table = QTableWidget()
        self.full_table.setColumnCount(2)
        self.full_table.setHorizontalHeaderLabels(["单位", "换算系数"])
        self.full_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        table_layout.addWidget(self.full_table)
        right_tabs.addTab(table_widget, "📋 单位对照表")

        # 历史
        hist_widget = QWidget()
        hist_layout = QVBoxLayout(hist_widget)
        self.hist_list = QListWidget()
        self.hist_list.itemDoubleClicked.connect(self._on_hist_clicked)
        hist_layout.addWidget(QLabel("双击载入"))
        hist_layout.addWidget(self.hist_list)
        hist_btn_layout = QHBoxLayout()
        self.clear_hist_btn = QPushButton("清空历史")
        self.clear_hist_btn.clicked.connect(self._clear_history)
        hist_btn_layout.addWidget(self.clear_hist_btn)
        hist_btn_layout.addStretch()
        hist_layout.addLayout(hist_btn_layout)
        right_tabs.addTab(hist_widget, "🕐 历史")

        # 批量
        batch_widget = QWidget()
        batch_layout = QVBoxLayout(batch_widget)
        batch_layout.addWidget(QLabel("输入多个数值（每行一个）:"))
        self.batch_input = QTextEdit()
        self.batch_input.setMaximumHeight(150)
        batch_layout.addWidget(self.batch_input)
        self.batch_btn = QPushButton("批量换算")
        self.batch_btn.clicked.connect(self._batch_convert)
        batch_layout.addWidget(self.batch_btn)
        batch_layout.addWidget(QLabel("结果:"))
        self.batch_output = QTextEdit()
        self.batch_output.setReadOnly(True)
        batch_layout.addWidget(self.batch_output)
        right_tabs.addTab(batch_widget, "📦 批量")

        # 自定义单位
        custom_widget = QWidget()
        custom_layout = QVBoxLayout(custom_widget)
        self.custom_btn = QPushButton("添加自定义单位")
        self.custom_btn.clicked.connect(self._add_custom_unit)
        custom_layout.addWidget(self.custom_btn)
        self.custom_list = QListWidget()
        custom_layout.addWidget(QLabel("已添加的自定义单位:"))
        custom_layout.addWidget(self.custom_list)
        right_tabs.addTab(custom_widget, "➕ 自定义")

        # 货币
        currency_widget = QWidget()
        currency_layout = QVBoxLayout(currency_widget)
        curr_top = QHBoxLayout()
        curr_top.addWidget(QLabel("金额:"))
        self.curr_value = QLineEdit("1")
        curr_top.addWidget(self.curr_value)
        curr_top.addWidget(QLabel("从:"))
        self.curr_from = QComboBox()
        self.curr_from.addItems(CurrencyConverter.CURRENCIES.keys())
        curr_top.addWidget(self.curr_from)
        curr_top.addWidget(QLabel("到:"))
        self.curr_to = QComboBox()
        self.curr_to.addItems(CurrencyConverter.CURRENCIES.keys())
        self.curr_to.setCurrentIndex(4)
        curr_top.addWidget(self.curr_to)
        self.curr_btn = QPushButton("换算")
        self.curr_btn.clicked.connect(self._currency_convert)
        curr_top.addWidget(self.curr_btn)
        currency_layout.addLayout(curr_top)
        self.curr_result = QLabel("--")
        self.curr_result.setFont(QFont("Consolas", 14, QFont.Bold))
        self.curr_result.setAlignment(Qt.AlignCenter)
        currency_layout.addWidget(self.curr_result)
        currency_layout.addStretch()
        right_tabs.addTab(currency_widget, "💱 货币")

        # 图表
        viz_widget = QWidget()
        viz_layout = QVBoxLayout(viz_widget)
        self.viz_btn = QPushButton("生成对比图表")
        self.viz_btn.clicked.connect(self._generate_chart)
        viz_layout.addWidget(self.viz_btn)
        self.viz_label = QLabel("点击上方按钮生成当前类别的对比图表")
        self.viz_label.setAlignment(Qt.AlignCenter)
        viz_layout.addWidget(self.viz_label)
        viz_layout.addStretch()
        right_tabs.addTab(viz_widget, "📊 图表")

        splitter.setSizes([450, 550])

        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_bar.showMessage("就绪 | 支持 9 大类 123 个单位")

        self._refresh_history_list()
        self._refresh_custom_list()
        self._refresh_full_table()

    def _setup_shortcuts(self):
        QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
        QShortcut(QKeySequence("Ctrl+N"), self, self._reset_form)
        QShortcut(QKeySequence("Ctrl+S"), self, self._do_convert)
        QShortcut(QKeySequence("Ctrl+U"), self, lambda: self.value_edit.setFocus())
        for i, cat in enumerate(ConversionEngine.get_categories()[:9]):
            QShortcut(QKeySequence(f"Ctrl+{i+1}"), self,
                      lambda i=i: self.cat_combo.setCurrentIndex(i))

    def _setup_tray(self):
        if not QSystemTrayIcon.isSystemTrayAvailable():
            return
        self.tray = QSystemTrayIcon(self)
        icon = self.style().standardIcon(QStyle.SP_ComputerIcon)
        self.tray.setIcon(icon)
        tray_menu = QMenu()
        show_action = tray_menu.addAction("显示")
        show_action.triggered.connect(self.show)
        quit_action = tray_menu.addAction("退出")
        quit_action.triggered.connect(QApplication.quit)
        self.tray.setContextMenu(tray_menu)
        self.tray.show()

    def _on_category_changed(self, index):
        cat = self.cat_combo.currentText()
        units = ConversionEngine.get_all_units(cat)
        self.from_combo.clear()
        self.to_combo.clear()
        self.from_combo.addItems(units)
        self.to_combo.addItems(units)
        if len(units) >= 2:
            self.to_combo.setCurrentIndex(1)
        self._refresh_full_table()
        self._refresh_quick_table()
        if self.realtime_cb.isChecked():
            self._schedule_convert()

    def _on_input_changed(self):
        if self.realtime_cb.isChecked():
            self._schedule_convert()

    def _on_setting_changed(self):
        if self.realtime_cb.isChecked():
            self._schedule_convert()

    def _on_realtime_toggled(self, checked):
        if checked:
            self._schedule_convert()

    def _schedule_convert(self):
        self.convert_timer.stop()
        self.convert_timer.start(300)

    def _swap_units(self):
        f_idx = self.from_combo.currentIndex()
        t_idx = self.to_combo.currentIndex()
        self.from_combo.setCurrentIndex(t_idx)
        self.to_combo.setCurrentIndex(f_idx)
        if self.realtime_cb.isChecked():
            self._schedule_convert()

    def _do_convert(self):
        value_str = self.value_edit.text().strip()
        if not value_str:
            self.result_label.setText("--")
            self.formula_label.setText("")
            return

        cat = self.cat_combo.currentText()
        from_unit = self.from_combo.currentText()
        to_unit = self.to_combo.currentText()
        precision = self.precision_spin.value()

        try:
            result = ConversionEngine.convert(
                value_str, from_unit, to_unit, cat, precision
            )
            self.result_label.setText(f"{result}")

            if cat == "温度":
                self.formula_label.setText(f"{value_str} {from_unit} → {result} {to_unit}")
            else:
                factor = ConversionEngine.CATEGORIES[cat]["单位"][to_unit] / \
                         ConversionEngine.CATEGORIES[cat]["单位"][from_unit]
                self.formula_label.setText(
                    f"1 {from_unit} = {float(factor):.{min(precision, 10)}f} {to_unit}"
                )

            entry = {
                "category": cat, "value": value_str,
                "from": from_unit, "to": to_unit, "result": result
            }
            HistoryManager.add(entry)
            self._refresh_history_list()
            self._refresh_quick_table()

        except Exception as e:
            self.result_label.setText("错误")
            self.formula_label.setText(str(e))
            self.status_bar.showMessage(f"换算失败: {e}")

    def _refresh_quick_table(self):
        cat = self.cat_combo.currentText()
        units = ConversionEngine.get_all_units(cat)
        from_unit = self.from_combo.currentText()
        value_str = self.value_edit.text().strip() or "1"
        precision = self.precision_spin.value()

        self.quick_table.setRowCount(len(units))
        for i, unit in enumerate(units):
            self.quick_table.setItem(i, 0, QTableWidgetItem(unit))
            try:
                result = ConversionEngine.convert(
                    value_str, from_unit, unit, cat, precision
                )
                self.quick_table.setItem(i, 1, QTableWidgetItem(f"{result}"))
            except Exception:
                self.quick_table.setItem(i, 1, QTableWidgetItem("N/A"))

    def _refresh_full_table(self):
        cat = self.cat_combo.currentText()
        units = ConversionEngine.get_all_units(cat)
        self.full_table.setRowCount(len(units))
        for i, unit in enumerate(units):
            self.full_table.setItem(i, 0, QTableWidgetItem(unit))
            if cat == "温度":
                self.full_table.setItem(i, 1, QTableWidgetItem("-"))
            else:
                factor = ConversionEngine.CATEGORIES[cat]["单位"][unit]
                self.full_table.setItem(i, 1, QTableWidgetItem(f"{float(factor)}"))

    def _refresh_history_list(self):
        self.hist_list.clear()
        for entry in reversed(self.history[-50:]):
            text = f"{entry.get('value', '?')} {entry.get('from', '?')} → {entry.get('result', '?')} {entry.get('to', '?')} ({entry.get('category', '?')})"
            item = QListWidgetItem(text)
            item.setData(Qt.UserRole, entry)
            self.hist_list.addItem(item)

    def _refresh_custom_list(self):
        self.custom_list.clear()
        data = CustomUnitManager.load()
        for cat, units in data.items():
            for name, info in units.items():
                self.custom_list.addItem(f"{cat}: {name} = {info['factor']} × {info['base_unit']}")

    def _on_quick_item_clicked(self, item):
        row = item.row()
        unit = self.quick_table.item(row, 0).text()
        self.to_combo.setCurrentText(unit)
        self._do_convert()

    def _on_hist_clicked(self, item):
        entry = item.data(Qt.UserRole)
        if entry:
            idx = self.cat_combo.findText(entry.get("category", ""))
            if idx >= 0:
                self.cat_combo.setCurrentIndex(idx)
            self.value_edit.setText(str(entry.get("value", "")))
            self.from_combo.setCurrentText(entry.get("from", ""))
            self.to_combo.setCurrentText(entry.get("to", ""))
            self._do_convert()

    def _clear_history(self):
        HistoryManager.clear()
        self.history = []
        self._refresh_history_list()

    def _reset_form(self):
        self.value_edit.clear()
        self.result_label.setText("--")
        self.formula_label.setText("")
        self.batch_input.clear()
        self.batch_output.clear()
        self.status_bar.showMessage("已重置")

    def _batch_convert(self):
        cat = self.cat_combo.currentText()
        from_unit = self.from_combo.currentText()
        to_unit = self.to_combo.currentText()
        precision = self.precision_spin.value()
        lines = self.batch_input.toPlainText().strip().split("\n")

        results = []
        for line in lines:
            line = line.strip()
            if not line:
                continue
            try:
                result = ConversionEngine.convert(
                    line, from_unit, to_unit, cat, precision
                )
                results.append(f"{line} {from_unit} = {result} {to_unit}")
            except Exception as e:
                results.append(f"{line}: 错误 - {e}")

        self.batch_output.setPlainText("\n".join(results))

    def _add_custom_unit(self):
        dialog = CustomUnitDialog(self)
        if dialog.exec() == QDialog.Accepted:
            data = dialog.get_data()
            if not data["name"] or not data["factor"]:
                QMessageBox.warning(self, "错误", "请填写完整信息")
                return
            try:
                Decimal(data["factor"])
            except Exception:
                QMessageBox.warning(self, "错误", "换算系数必须是有效数字")
                return
            CustomUnitManager.add_unit(
                data["category"], data["name"], data["factor"], data["base_unit"]
            )
            CustomUnitManager.apply_to_engine()
            self._refresh_custom_list()
            self._on_category_changed(self.cat_combo.currentIndex())
            QMessageBox.information(self, "成功", f"已添加单位: {data['name']}")

    def _currency_convert(self):
        try:
            value = float(self.curr_value.text())
        except ValueError:
            self.curr_result.setText("金额无效")
            return
        from_curr = self.curr_from.currentText()
        to_curr = self.curr_to.currentText()
        result = CurrencyConverter.convert(value, from_curr, to_curr)
        if result is not None:
            self.curr_result.setText(f"{value} {from_curr} = {result:.4f} {to_curr}")
        else:
            self.curr_result.setText("汇率获取失败（请检查网络连接）")

    def _generate_chart(self):
        try:
            import matplotlib
            matplotlib.use('Agg')
            import matplotlib.pyplot as plt
            from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
        except ImportError:
            self.viz_label.setText("需要安装 matplotlib: pip install matplotlib")
            return

        cat = self.cat_combo.currentText()
        units = ConversionEngine.get_all_units(cat)
        from_unit = self.from_combo.currentText()
        value_str = self.value_edit.text().strip() or "1"
        precision = self.precision_spin.value()

        values = []
        labels = []
        for unit in units[:15]:
            try:
                result = ConversionEngine.convert(
                    value_str, from_unit, unit, cat, precision
                )
                values.append(abs(result) if result != 0 else 0.0001)
                labels.append(unit)
            except Exception:
                values.append(0)
                labels.append(unit)

        # 清除旧画布
        for child in self.viz_label.parentWidget().findChildren(FigureCanvas):
            child.setParent(None)

        if not any(v > 0 for v in values):
            self.viz_label.setText("无有效数据可绘图")
            return

        # 判断是否需要对数坐标
        max_v = max(values)
        min_v = min(v for v in values if v > 0)
        use_log = (max_v / min_v > 1000) if min_v > 0 else True

        fig, ax = plt.subplots(figsize=(10, 5))

        if use_log:
            # 对数坐标：所有单位都能看清
            y_pos = range(len(labels))
            bars = ax.barh(y_pos, values, color='steelblue', edgecolor='navy')
            ax.set_yticks(list(y_pos))
            ax.set_yticklabels(labels, fontsize=9)
            ax.set_xscale('log')
            ax.set_xlabel(f'{from_unit} → 目标单位（对数坐标）')
            ax.invert_yaxis()
        else:
            # 线性坐标：正常柱状图
            y_pos = range(len(labels))
            bars = ax.bar(y_pos, values, color='steelblue', edgecolor='navy')
            ax.set_xticks(list(y_pos))
            ax.set_xticklabels(labels, rotation=45, ha='right')
            ax.set_ylabel(f'换算结果（{from_unit} → 各单位）')

        ax.set_title(f'{cat} 换算对比（{value_str} {from_unit} → 各单位）', fontsize=12)

        # 在柱子/条上标注数值
        for i, (bar_val, bar) in enumerate(zip(values, bars)):
            if bar_val <= 0:
                continue
            if use_log:
                ax.text(bar_val * 1.1, bar.get_y() + bar.get_height() / 2,
                        f'{bar_val:.4g}', va='center', fontsize=8)
            else:
                if bar_val < max_v * 0.05:
                    ax.text(bar.get_x() + bar.get_width() / 2, bar_val + max_v * 0.02,
                            f'{bar_val:.4g}', ha='center', va='bottom', fontsize=8)

        plt.tight_layout()

        canvas = FigureCanvas(fig)
        self.viz_label.parentWidget().layout().insertWidget(1, canvas)
        self.viz_label.setText("")

    def _toggle_theme(self):
        self.dark_mode = not self.dark_mode
        if self.dark_mode:
            palette = QPalette()
            palette.setColor(QPalette.Window, QColor(53, 53, 53))
            palette.setColor(QPalette.WindowText, Qt.white)
            palette.setColor(QPalette.Base, QColor(25, 25, 25))
            palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
            palette.setColor(QPalette.Text, Qt.white)
            palette.setColor(QPalette.Button, QColor(53, 53, 53))
            palette.setColor(QPalette.ButtonText, Qt.white)
            palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
            QApplication.setPalette(palette)
            self.status_bar.showMessage("已切换为暗色主题")
        else:
            QApplication.setPalette(QApplication.style().standardPalette())
            self.status_bar.showMessage("已切换为亮色主题")

    def _show_about(self):
        dialog = AboutDialog(self)
        dialog.exec()

    def closeEvent(self, event):
        if hasattr(self, 'tray'):
            self.tray.hide()
        event.accept()


# ============================================================
# CLI 模式
# ============================================================

def cli_mode(args):
    if len(args) < 4:
        print("用法: python main.py <类别> <数值> <源单位> <目标单位>")
        print(f"类别: {', '.join(ConversionEngine.get_categories())}")
        return 1

    category = args[0]
    value = args[1]
    from_unit = args[2]
    to_unit = args[3]

    try:
        result = ConversionEngine.convert(value, from_unit, to_unit, category)
        print(f"{value} {from_unit} = {result} {to_unit}")
        return 0
    except Exception as e:
        print(f"错误: {e}", file=sys.stderr)
        return 1


# ============================================================
# 入口
# ============================================================

def main():
    if len(sys.argv) > 1:
        return cli_mode(sys.argv[1:])
    else:
        app = QApplication(sys.argv)
        app.setApplicationName("通用单位转换器")
        app.setApplicationVersion("2.0")

        font = QFont("Microsoft YaHei", 9)
        app.setFont(font)

        window = MainWindow()
        window.show()
        return app.exec()


if __name__ == "__main__":
    sys.exit(main())